文章目录

  • JVM堆内存管理小总结
  • 1. JVM堆内存分代区域与大小比例
  • 2. 垃圾收集算法、垃圾收集器
  • 3. 一个对象在堆内存中的生命周期
  • 4. 补充:内存中实际建立一个对象的过程
  • 5. 自制:对象在内存中分配的流程图


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个块,和其他算法不同)
  • 示意图(来自互联网)
  • 垃圾收集器的特性表(来自互联网)
  • 垃圾回收时的串行、并行、并发
  • 串行:GC以单线程的方式回收对象,用户线程停止运行(STW)
  • 并行:GC以多线程的方式回收对象,用户线程停止运行(STW)
  • 并发:GC以多线程的方式回收对象,用户线程与GC线程交替执行
  • 举例:CMS中 (初始标记STW + 并发标记 + 重新标记STW + 并发清理)

3. 一个对象在堆内存中的生命周期

  • 判断-XX:PretenureSizeThreshold,决定是大对象还是小对象(默认为0,直接在eden区分配)
  • 小的对象 —> Eden区
  • —> Eden区内存足够 —> 正常运行,等待发生GC
  • —> Eden区内存不够 —> Minor GC
  1. —> 一次Minor GC后, 如果没被回收 —> survivor1区
  2. —> 一次Minor GC后, 如果没被回收 —> survivor2区
  3. —> 一次Minor GC后, 如果没被回收 —> survivor1区
  4. —> 一直这样交替循环, 达到某个条件 —> old区
  • 大的对象 —> Old区
  1. —> Old区满了—> Major GC
  2. —> 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;
}

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. 自制:对象在内存中分配的流程图

  • 自制流程图 ^_^

java堆内存使用量 java堆内存比例_Java