专栏首页bisal的个人杂货铺小白是怎么搞懂GC全过程?

小白是怎么搞懂GC全过程?

冯大师在架构师进阶之路写了一篇文章,通俗易懂地介绍了什么是GC,受益匪浅,学习一下。

GC-垃圾回收,是Java程序员长聊的话题,理解JVM垃圾回收的原理和过程,不但有助于写出高质量高性能的代码,也可以帮你在面试官面前侃侃而谈。

读完本文,对垃圾回收过程、以及回收算法在垃圾回收中的应用,将会有一个全新的认识和理解。

堆内存结构


我们以Java官方的HotSpot JVM为例,在描述GC过程前,先了解一下堆内存的结构。

JVM将堆内存分为了三部分:新生代(Young Generation),老年代(Old Generation),永久代(Permanent Generation)。其中新生代又分为三部分:伊甸园区(Eden),和两个幸存区S0和S1。

注:JDK1.8之后,Java官方的HotSpot JVM去掉了永久代,取而代之的是元数据区Metaspace。Metaspace使用的是本地内存,而不是堆内存,也就是说在默认情况下Metaspace的大小只与本地内存的大小有关。因此JDK1.8之后,就见不到java.lang.OutOfMemoryError: PermGen space这种由于永久代空间不足导致的内存溢出的问题了。

垃圾回收全过程


新创建的对象会先被分配到到Eden区。JVM刚启动时,Eden区对象数量较少,两个Survivor区S0、S1几乎是空的。

随着时间的推移,Eden区的对象越来越多。当Eden区放不下时(占用空间达到容量阈值),新生代就会发生垃圾回收,我们称之为Minor GC或者Young GC。

发生GC时,第一步会通过可达性分析算法找到可达对象。如上图,蓝色为可达对象,其他紫色为不可达对象。第二步,被标示的可达对象会被转移到S0(此时S0是From Survivor),此时存活对象年龄加1,三个对象年龄都变为1。第三步,清除Eden区所有对象。

GC后各区域对象占用情况,如上图所示。

程序继续运行,Eden区再次达到容量阈值时,会再次发生GC。这时S0(From Survivor)已经有了对象。还是同样的步骤,通过可达性分析算法找到可达对象,然后再将Eden和S0中的可达对象转移到S1(To Survivor),各存活对象年龄加1。最后将Eden和S0中的所有对象清除。

GC后S0区域被清空。如上图所示。S0和S1发生了互换,S1变成了From Survivor,S0变成了To Survivor。

注意,To Survivor区永远都为空。这实际上是垃圾回收算法-复制算法在年轻代的实际应用。把年轻代分为Eden,S0,S1三个区域,每次垃圾回收时把可达对象复制到S0或S1,然后再清除掉Eden和(S1或S0)中的所有对象。由于每次GC时,新生代的可达对象非常少(绝大部分对象要被回收掉),一般不会超过新生代总体空间的10%,所以搜寻可达对象以及复制对象的成本都会非常低。而且这种复制的方式还能避免产生堆内存碎片,提高内存利用率。很多年轻代垃圾收集器都采用复制算法,如ParNew。

在程序运行过程中,新生代GC会反复发生,长寿对象会在S0和S1之间反复交换,年龄也会越来越大,当对象达到年龄上限时,会被晋升到老年代。这个年龄上限默认是15,可以通过参数-XX:MaxTenuringThreshold设置。如下图,有些年轻代对象年龄达到了上限15,被转移到了老年代。

其他晋升方式。新生代对象晋升到老年代,除了根据年龄正常晋升外。为了提高JVM的性能,JVM设计者还考虑了其他晋升方式。

大对象直接晋升。大对象会跨过年轻代直接分配到老年代。可以通过-XX:PretenureSizeThreshold参数设置对象大小。如果参数被设置成5MB,超过5MB的大对象会直接分配到老年代。这样做的目的,是为了避免大对象在Eden区及两个Survivor区之间大量的内存复制,大对象的内存复制耗时比普通对象要高很多。

注意:PretenureSizeThreshold参数只对Serial和ParNew两种回收器有效。

动态对象年龄判定。如果在Survivor空间中相同年龄对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象会直接进入老年代,而不用等到MaxTenuringThreshold中设置的年龄上限。上图,年龄为1的对象超过了Survivor空间的一半,所以这几个对象会直接进入老年代。

实际上,上面对动态对象年龄判定的描述并不精确。上图的场景也会导致相关对象晋升到老年代。年龄为1的对象加上年龄为2的对象超过了半数,这时包括年龄为2的对象以及年龄更大的对象都会被晋升到老年代。所以上图中年龄为2和3的对象都会被晋升到老年代。

老年代垃圾回收。随着年轻代对象的不断晋升,老年代的对象变得越来越多,达到容量阈值后老年代也会发生垃圾回收,我们称之为Major GC或者Full GC,Full GC并不是全局GC,它只发生在老年代。

虽然年轻代和老年代都会发生GC,但是每次GC的时间和成本却大不相同。由于老年代空间大小一般是年轻代的几倍,再加上老年代对象存活率很高,所以整个标记过程比较慢,GC成本也非常高。我们经常说的JVM调优,主要是为了尽量减少老年代Full GC的时间和频次。

老年代垃圾回收器,很少使用复制算法,主要为了避免大量对象的内存复制带来的时间和空间上的开销,一般采用标记清除、标记整理算法,就地标记回收。例如,老年代垃圾收集器CMS就采用了标记清除算法。对于标记清除算法带来的内存碎片问题,CMS提供了两个参数做碎片整理,-XX:+UseCMSCompactAtFullCollection和-XX:CMSFullGCsBeforeCompaction。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java GC的基础知识

    最近碰到一些应用问题,涉及到了Java中的垃圾回收机制,Garbage Collection,简称GC,这其中的学问,还是不少的,有很多东西需要学习。

    bisal
  • 一个执行计划异常变更的案例 - 外传之rolling invalidation

    刚做完一次网络切换支持,得空写一篇,其实今儿取了巧,这篇文章是之前写过的,碰巧又是这次“执行计划异常变更”案例涉及的一个知识点,所以再次翻出来。

    bisal
  • rolling invalidation对子游标产生的影响

    一条SQL,使用了绑定变量,查看V$SQLAREA发现version_count是2,

    bisal
  • 透视JVM之垃圾回收

    我是攻城师
  • JVM 学习笔记(二)

    如果还不明白什么是栈帧,可以参考:https://www.jianshu.com/p/b666213cdd8a

    会说话的丶猫
  • 阿里高级Android面试题:Android(虚拟机)内存模型,垃圾回收机制是如何实现的?

    垃圾回收机制是如何实现的呢?其实从Jvm内存模型中入手对于理解GC会有很大的帮助,不过这里只需要了解一个大概,说多了反而混淆视线。

    Android技术干货分享
  • JVM--堆区

    Java堆是虚拟机所管理的内存中最大的一块,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这块区域是“线程共享”的。

    SuperHeroes
  • 一次线上JVM Young GC调优,搞懂了这么多东西!

    首先我们使用 jstat 查看了 GC 的情况。又通过查看GC log,分析了GC 的详细状况。

    用户7927337
  • JVM垃圾回收之垃圾收集算法,程序员必须掌握的知识

    解释下,堆大小=新生代+老年代,新生代与老年代的比例为1:2,新生代细分为一块较大的Eden空间和两块较小的Survivor空间,分别被命名为from和to。

    黎明大大
  • 年轻代与老年代的调优笔记

    年轻代(young 区) 从年轻代空间(包括Eden和Survivor 区域)回收内存被称为 Minor GC 空间太小可能导致对象直接进入 old区 。如果o...

    飞天小子

扫码关注云+社区

领取腾讯云代金券