Java堆中内存区分
Java的堆由新生代(Young Generation)和老年代(Old Generation)组成。新生代存放新分配的对象,老年代存放长期存在的对象。
新生代(Young)由年轻区(Eden)、Survivor区组成(From Survivor、To Survivor)。默认情况下,新生代的Eden区和Survivor区的空间大小比例是8:2,可以通过-XX:SurvivorRatio参数调整。
一次完整的GC流程
-
对象分配:
- 新创建的对象通常首先分配在新生代的Eden区。如果对象体积过大,直接分配到老年代(大对象直接分配)。
-
Minor GC(新生代垃圾回收):
- 当Eden区空间不足时,会触发一次Minor GC。这个过程中,Eden区中不再被引用的对象将被回收。
- 存活下来的对象会被移动到Survivor区的一个(假设为S1),如果S1区也满了,则这些对象和S1中已有的、经历过一次GC的对象一起被移到另一个Survivor区(S2)或者直接晋升到老年代,具体取决于对象的年龄和Survivor区的配置。
- 每次Minor GC后,对象的年龄增加1,当对象年龄达到预设阈值(默认15)时,该对象会被晋升到老年代。
-
Survivor区调整:
- Survivor区之间的对象会在几次Minor GC后进行交换,目的是尽可能地利用Survivor空间来保留更多可幸存的对象,减少晋升到老年代的对象数量。
-
Full GC(老年代垃圾回收):
- 当老年代空间不足,或者满足其他触发条件(比如元数据空间不足、System.gc()被显式调用等)时,会触发Full GC。
- Full GC是一个更耗时的过程,因为它涉及整个堆(包括新生代和老年代)以及方法区(元数据区)的垃圾回收。
- Full GC使用标记-清除、标记-整理(也称为标记-压缩)或其他组合算法来回收空间,这一步可能包括所有存活对象的移动和空间碎片的整理。
-
标记过程:
- 不论是Minor GC还是Full GC,都会经历一个标记过程,用来识别哪些对象是可达的(即从GC Roots开始可以访问到的对象)。
- 这个过程通常包括两个阶段:初始标记和并发标记,后者可以在程序运行时并发执行以减少暂停时间。
-
清理与压缩:
- 清理阶段会删除已被标记为不可达的对象。
- 在某些情况下,特别是Full GC,还会进行压缩操作,移动存活对象以消除碎片,优化内存布局。
YoungGC和FullGC的触发条件是什么
YoungGC的触发条件比较简单,那就是当年轻代中的eden区分配满的时候就会触发。
FullGC的触发条件比较复杂也比较多,主要以下几种:
1. 老年代空间不足创建一个大对象,超过指定阈值会直接保存在老年代当中,如果老年代空间也不足,会触发Full GC。
2.当准备要触发一次YoungGC时,会进行空间分配担保,在担保过程中,发现虚拟机会检查老年 代最大可用的连续空间小于新生代所有对象的总空间,但是HandlePromotionFailure=true,继续检查发现老年代最大可用连续空间小于历次晋升到老年代的对象的平均大小时,会触发一次FullGC
3.永久代空间不足如果有永久代的话,当在永久代分配空间时没有足够空间的时候,会触发FullGC
4.代码中执行System.gc()
代码中执行System.gc()的时候,会触发FullGC,但是并不保证一定会立即触发。