JAVA 中 分堆内存 和 堆外内存
堆外内存不是由JVM控制的,这个得开发人员自己手工调用方法去释放
堆内内存是由JVM控制,可以由JVM 通过算法自动 GC
JAVA 在 JVM 启动的时候,就事先对堆内存进行了划分了几个模块
默认情况下 JVM 堆内存分配:
新生代 (占堆内存 1/3) | 老年代 (占堆内存 2/3) | |||
---|---|---|---|---|
Eden | Survivor0 | Survivor1 | ||
80% | 10% | 10% |
JVM 将堆内存划分了2个大模块
新生代: 当我们使用了一个新对象,申请内存的时候,就是先从新生代内存里申请
老年代: 当新生代的内存经过了15次 GC 还没被 GC 掉 或者 当新申请的内存比较大的时候,直接从老年代里分配
新生代 GC
新生代里又分划分了 Eden , Survivor0, Survivor1 三个区域
翻译过来的 就是 伊甸园 , 存活0区 , 存活1区
伊甸园是《圣经》故事中人类的始祖亚当和夏娃居住的乐园。所以作者也很意思。
实际 Eden , Survivor0, Survivor1 三个区域职责如下:
Eden : 当申请内存的时候,从 Eden 里申请(当申请的内存大于JVM启动参数 PretenureSizeThreshold 的值的时候,直接从 老年代里分配内存,默认值为0,所有都从Eden分配), 当 Eden 里不够内存的时候,会进行一次普通GC
Survivor : 当 Eden 内存不够的时候,普通GC, 将 Eden 里没被引用等的内存GC掉,把活下来的,转到 Survivor 区域
这里为什么不写是 Survivor0 或者 Survivor1
因为 Survivor0 和 Survivor1 两个区域干的活是一样, Survivor0 和 Survivor1 相交替干活的
可以理解为,每次GC,只有 其中一个可用。
例如,当前 Survivor0 和 Survivor1 都会空
第一次新生代GC
Eden 里GC存活来的内存,转移到 Survivor0, 并且给那些活下来的对象 “寿命”+1
第二次新生代GC
将Eden 和 Survivor0两个区域活下来的内存转移到 Survivor1
然后再将 Eden 和 Survivor0两个都区域的全清空
并且给那些活下来的对象 “寿命”+1
第三次新生代GC
将Eden 和 Survivor1两个区域活下来的内存转移到 Survivor0
然后再将 Eden 和 Survivor1 两个都区域的全清空
并且给那些活下来的对象 “寿命”+1
第 N 次新生代GC
将 “寿命” >= 15 的对象,转移到 老年代。
假如 Eden 和 Survivor 区域活下来的对象需要的空间 大于 Survivor0 的空间。则找出哪一个 寿命及以上的 总合大于 Survivor0 的空间,则将那个年龄及对上的 对象转移到 老年代(其实就是按年龄倒序排序,直接那个 Survivor 中其中一个 区域够存为止)
每次新生代GC 都是只用 Survivor 其中一个区域,把另一个空出来,待下一次的时候,这就是大家常说的 复制GC
但这样可以理解为至少有一个 Survivor 区域内存是没有真正存数据的
老年代 GC
新生代 GC 后 Survivor0或者Survivor1 不够存 新生代 GC 之后活下来的对象 的时候
会新生代 将 “寿命” >= 15 的对象 或者 Survivor0 不够存按年龄大小倒序排出来的对象 转移到 老年代
但是老年代的空间,也是有限的,默认情况下占当前 JVM 堆空间的 2/3
老年代普通GC
当老年代里的 连续内存空间 大于 新生代转移过来的内存块的时候,则会进行一次 老年代普通 GC
Full GC
当老年代里的 连续内存空间 小于 新生代转移过来的内存块的时候,则会进行 Full GC
Full GC 是对整个堆空间进行GC
如果Full GC后还是无法给新创建的对象分配内存,或者无法移动那些需要进入老年代中的对象,那么JVM抛出 Out Of Memory Error
小结
JVM 是中整个堆当内存不够的时候,会进行 Full GC , 当Full GC 内存还是不够的时候,会抛出 Out Of Memory Error
JVM 新生代,老年代内存分配比例是可以通过启动参数控制的
Map ,Set , ArrayList 等对象中存存储的数据,需要开发人员手动 remove 掉,要不然GC 可能不会释放(除非WeakHaspMap等对象),很容易让堆占满,导致Full GC