特注:本文为参加2018年12月SHLUG聚会的笔记。
https://www.kernel.org/doc/gorman/html/understand/understand005.html
Linux的内存模型总体上一句话总结叫做“分层,抽象”
从上(顶层更面向应用)到下(底层更物理)分为
-
memblock
-
mem\_section
-
node
-
zone
-
page
-
free list
-
slub
简单来说是为了以下需求来分的:
-
对物理进行抽象隔离,简化上层访问
-
管理方便,支持热插拔
-
预先索引好空闲的空间,便于加快分配速度
-
按不同应用情景分好,便于满足不同的申请要求
这个模型原则,实际上与网络协议、硬件总线的设计思路是一致的。
一句话调侃:没有什么问题是分层不能解决的,如果有,那就再分一层。
一些额外概念
-
struct page (这个可以去看内核源代码) 对于内存页的meta信息,用于追踪内存页的状态等
-
NUMA 一种计算机硬件的架构,特点是一组CPU与一组内存总线“直连”。在跨总线访问内存时存在性能差异。
-
对内存的申请有一个全局总线锁,在这条总线上的CPU都会受到影响,即瓶颈
题外话
关于内核的OOM Killer
问题:为什么程序申请“虚拟”内存的空间有2^64(64位系统中),而实际上还是会“因为内存不足被KILL”?
实际上内存分配成功失败,与 OOM Killer
是分开两个不同的事件,在事件顺序上没有固定关系。
-
申请内存失败的主要原因是,由于用户态程序向内核申请内存时,可能会触发内核内部其他的内存分配。导致实际上可能超过真正的可分配内存。
所以内核会有一个阈值,当接近这个阈值时,系统调用返回 NO MORE
,表示不可分配。
-
已分配的内存状态中分为(还有其他)可被回收(
reclaimable
)与不可回收(unreclaimable
),在申请内存时,实际上也会触发内核对可回收内存的回收,综合结果进行返回。
而实际上一些特殊的程序的内存一直会被标记为USED,即内存正在使用。当一个操作系统中物理内存都在使用时,那就无法继续分配。
-
当内存使用量接近那个阈值时,内核会启动一个OOM Killer进程,对当前用户态进程进行打分,根据某种算法计算出需要杀掉的进程,杀掉,然后在syslog中打日志。故两个动作其实是分开的。
-
提到SWAP做热交换,实际上这个问题比较玄学。并不是开了swap就一定会使用。也不是配置了不使用swap就不使用。具体需要去看swap的实现。
对于目前这个年代的应用,swap最大的应用可能只有hibernate(休眠到硬盘,断电)。
-
一部分涉密的内存内容是不可以写到硬盘(即SWAP出去)的,例如tty的login-manager,这部分内存就只能驻留内存。实际上可以申请内存为保留(reserved)。但是所有内存都常驻不回收的话,内存不足就只能系统崩溃了。
golang 的内存分配
源代码:runtime/malloc.go
壳叔的说法是256KiB,这里查到的资料,tiny<16字节,small是<32KiB,超过的都算大对象。golang vm会先留好几个池子。其中mspan是分配单位。
-
mspan
存放多个slot,针对tiny对象预先留好slot位置,分不同大小需求使用 -
mcache
是对mspan的快速索引 -
mcentral
保留一个mspan列表,其中有空闲空间提供使用 -
mheap
堆内存,最大的预分配池子
mspan有3种状态
-
idle
没有对象占用,可提供golang内部使用,或释放回OS,或者用于栈内存 -
in use
至少被一个堆对象占用,可能还有空间存放其他对象 -
stack
给goroutine使用的栈空间,可以被栈或者堆使用(两者其一)
-
分配tiny时
-
如果能找到合适的,与现有的挤在一起,放进mcache
-
如果没有合适的,就从mcache获取新的mspan,检查是否有可用的tiny slot,并放进去,这步操作不需要加锁
-
如果没有可用的tiny slot,则从mcentral的mspan list中取走一个新的mspan使用
-
如果mspan list已经空,则从mheap再申请新的mspan
-
如果mheap也用空,则向OS申请新的内存页
-
-
分配small时,跳过检查tiny slot的步骤,其他一样
-
分配其他大对象,会直接通过mheap,即堆内存操作。
这样设计的目的是,减少对OS频繁地内存申请,以及降低并发申请的瓶颈(小对象通过“可用内存slot索引”快速分配,不加锁)
JVM的内存分配
JVM开启时根据配置先分配好的内存进行JVM内部的分配,JVM内存模型是分代垃圾回收。当JVM的内存池不够时,再继续向OS申请。JVM内部的分配和操作系统的内存分配实际上没有必然关联。
\_\_END\_\_