文章目录
JVM堆内存管理小总结
1. JVM堆内存分代区域与大小比例
注:Java8常用比例
区域 eden survivor1 survivor2 tenured
比例 8 1 1 # 默认
---------young----------------- ---old---
比例 1 2 # 默认
比例 1 3
比例 3 8
2. 垃圾收集算法、垃圾收集器
- 如何判断一个对象是否可回收
- 引用计数(无法解决A与B对象相互引用的问题)
- 根可达分析(正向可达)(根:虚拟机栈中的对象、本地方法栈中的对象、运行时常量池中的对象、静态引用对象、Clazz对象等)
- 垃圾回收算法
- Mark-Sweep 标记清除(简单,但易产生碎片空间。没有足够的连续空间时,大对象不容易分配)
- Copying 复制(复制效率高,但会浪费一半空间)
- Mark-Compact 标记整理(有效解决空间碎片问题,但是整理需要耗时,效率比Copy略低)
- 垃圾收集器
- Young:
- Serial(串行垃圾回收)
- ParNew(多线程版的Serial)
- ParallelScavenge(带自适应的ParNew)
- Old:
- CMS(并发垃圾收集)
- SerialOld(老年代版的Serial)
- ParallelOld(老年代版的ParallelScavenge)
- Young-Old:
- G1 (堆内存分为N个块,和其他算法不同)
- 示意图(来自互联网)
- Young:
- 垃圾收集器的特性表(来自互联网)
- 垃圾回收时的串行、并行、并发
- 串行:GC以单线程的方式回收对象,用户线程停止运行(STW)
- 并行:GC以多线程的方式回收对象,用户线程停止运行(STW)
- 并发:GC以多线程的方式回收对象,用户线程与GC线程交替执行
- 举例:CMS中 (初始标记STW + 并发标记 + 重新标记STW + 并发清理)
3. 一个对象在堆内存中的生命周期
-
判断-XX:PretenureSizeThreshold,决定是大对象还是小对象(默认为0,直接在eden区分配)
-
小的对象 —> Eden区
- —> Eden区内存足够 —> 正常运行,等待发生GC
- —> Eden区内存不够 —> Minor GC
- —> 一次Minor GC后, 如果没被回收 —> survivor1区
- —> 一次Minor GC后, 如果没被回收 —> survivor2区
- —> 一次Minor GC后, 如果没被回收 —> survivor1区
- —> 一直这样交替循环, 达到某个条件 —> old区
- 进入Old区的条件: 1.长期存活的对象晋升到老年代(MaxTenuringThreshold) 2.动态对象年龄判定
-
大的对象 —> Old区
- —> Old区满了—> Major GC
- —> GC后,空间仍然不足 —> java.lang.OutOfMemoryError: Java heap space
-
【注意的点】
- 当一个对象需要进入Old区时,先判断“老年代最大可用的连续空间>新生代所有对象总空间”
- 是,那么安全,直接进入(在此前面会执行Minor GC)
- 否,看对象进入Old区是否需要做“空间分配担保”(-XX:+HandlePromotionFailure,JDK 6 Update 24后无效)
- 如果需要做担保,判断“老年代最大可用的连续空间>历次晋升到老年代对象的平均大小”
- 是,尝试进行Minor GC (如果失败,再进行Major GC)
- 否,进行Major GC
- 如果不需要担保,直接Major GC
- 如果需要做担保,判断“老年代最大可用的连续空间>历次晋升到老年代对象的平均大小”
- 相关源码(Java1.6后,满足其中一个条件即可)
// max_promotion_in_bytes 是新生代所有晋升对象总空间 bool TenuredGeneration::promotion_attempt_is_safe(size_t max_promotion_in_bytes) const { // 老年代最大可用的连续空间 size_t available = max_contiguous_available(); // 历次晋升到老年代对象的平均大小 size_t av_promo = (size_t)gc_stats()->avg_promoted()->padded_average(); // 满足其中一个条件即可 bool res = (available >= av_promo) || (available >= max_promotion_in_bytes); return res; }
- 当一个对象需要进入Old区时,先判断“老年代最大可用的连续空间>新生代所有对象总空间”
4. 补充:内存中实际建立一个对象的过程
注:默认开启栈优化、线程本地优化
- 创建新对象
- —> 如果栈空间够,那就分配到【栈】
- —> 如果不够,判断对象是否比PretenureSizeThreshold大
- —>—> 如果比PretenureSizeThreshold大,放【old区】
- —>—> 如果小,看TLAB是否有空间
- —>—>—> 如果TLAB够,那就分配到【TLAB】
- —>—>—> 如果不够,放【eden区】
- 备注:TLAB(Thread Local Allocation Buffer,它在eden区有自己的独立空间1%)
- 几个JVM参数(默认)
- 逃逸分析 -XX:+DoEscapeAnalysis
- 标量替换 -XX:+EliminateAllocations
- 线程专有对象分配 -XX:+UseTLAB
- -XX:PretenureSizeThreshold=0,表示直接分配到Eden
5. 自制:对象在内存中分配的流程图
- 自制流程图 ^_^