之前对垃圾回收的过程有了解,但是还是比较模糊只局限于零碎的知识点,整个流程没有串起来,今天有空详细了解了一下整个过程,在此记录一下。
回收目标
- 主要是堆内存垃圾回收
- 其次也包含方法区(1.8之后也叫源空间meta)垃圾回收
下面主要介绍堆内存的收集
堆内存的划分
盗取一张图
- 老年代:用来存储多次经历过Minor GC后仍然存活的对象(包括达到指定年龄代的对象以及Monior GC后无法放入suvivor区的对象,无法放入的部分才会放入老年代)
- 新生代:用来存储程序刚刚分配的对象,以及未达到指定年龄代的对象
新生代各个分区的含义
- eden:程序运行新产生的对象首先会进入eden区
- survivor:survivor有两部分,可以称为 s1, s2 ,其中一块用来存储MinorGC后的数据,比如开始是Eden+s1满了之后触发MinorGC,然后把存活的对象移入s2区,然后程序再往eden+s2区放入新对象,触发MinorGC后把存活的对象放入s1,依次循环
垃圾回收有哪些
- MinorGC: 回收新生代的垃圾,不会暂停用户线程
- FullGC:回收整个堆的垃圾,既包含老年代又包含新生代,会暂停用户线程
- Major GC:回收老年代中的垃圾,常用的如CMS垃圾回收器,当老年代的空间达到设定的阈值时(-XX:CMSInitiatingOccupancyFraction与-XX:+UseCMSInitiatingOccupancyOnly参数决定的),垃圾回收器会收集老年代中的垃圾,避免产生FullGC
从产生垃圾到垃圾回收产生的流程
- 程序运行产生对象存放到eden与s1中,当新创建的对象无法在eden+s1中找到空间则出发MinorGC
- 如果MinorGC之后存活的对象小于s2的空间,则放入s2同时存活对象的年龄代+1,下次是回收eden+s2,然后将存活对象放入到s1
- 重复步骤2,如果在某次回收中存活的对象的年龄代 > MaxTenuringThreshold设置的值,这部分对象直接进入老年代,剩下的进入s1或s2中的一个
- 随着运行,突然某一次MinorGC后存活的对象很多,多到整个s1或者s2区放不下,这时候会尽量往s1或s2中存储,存不下的进入到老年代,但是进入老年代前会进行一系列条件判断,如果老年代可用的连续空间大于那些需要进入到老年代,则直接进入,如果不大于,那么就需要有以下的处理
- 在JDK 6 Update 24之后,JVM直接进行一次FullGC,HandlePromotionFailure参数没有意义了
- 在JDK 6 Update 24之前,如果HandlePromotionFailure 为true,JVM会再次尝试进行一次MinorGC,但是尝试是有条件的,条件就是老年代的连续空间需要大于历次MinorGC晋升到老年对象的平均值,如果小于JVM就会放弃尝试直接进行FullGC。如果JVM尝试后还是无法将多于的对象放入老年代,那么JVM就会进行FullGC;如果HandlePromotionFailure为false,JVM则直接进行FullGC
据说在JDK 6 Update 24前后的变化是因为JVM能够更为智能的处理老年代晋升失败这种情况了
- 随着老年代的空间不断的减少,当达到某个阈值时,负责老年代的垃圾回收器就会进行垃圾回收,比如CMS,来降低老年代中的内存使用,尽量避免因为晋升老年代失败导致的FullGC。
- 以CMS垃圾回收器为例,CMS在初始标记与重新标记时是暂停所有线程的,但是在并发标记与并发清除时是与应用程序线程一起执行的,意味着在并发清除的过程中也会产生对象,随之产生MinorGC。因此如果在并发清除阶段,如果通过MinorGC进入老年代的对象大于老年代清理的速度,这是就会产生concurrent mode failure,意味着某次MinorGC后需要进入到老年代的对象比老年代最大的可用连续空间要多,无法放入老年代,此时JVM就会触发一次FullGC
以上是从产生对象到垃圾回收的整个过程,此过程没有详细介绍细节中的概念只是注重流程,具体每步骤的涉及的概念以及详细过程并未解决,可以网站上检索