5

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

 2 years ago
source link: https://www.modb.pro/db/26526
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
小白是怎么搞懂GC全过程? - 墨天轮GC相关的历史文章,《一个Full GC次数过多导致系统CPU 100%的案例排查》《Java GC的基础知识》冯大师在架构师进阶之路写了一篇文章,通俗易懂地介绍了什么是GC,受益匪浅,学习一下。GC-垃圾回收,是Java程序员长聊的话题,理解JVM垃圾回收的原理和过程,不但有助于写出高质量高性能的代码,也可以帮你在面试官面前侃侃而谈。读完本文,对垃圾回收过程、以及回收算法在垃圾回收中的应用,将会有一个全新的认识和理解。
modb_20200618_092816.png堆内存结构
我们以Java官方的HotSpot JVM为例,在描述GC过程前,先了解一下堆内存的结构。

modb_20200618_092817.png

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这种由于永久代空间不足导致的内存溢出的问题了。

modb_20200618_092816.png

垃圾回收全过程


modb_20200618_092819.png

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

modb_20200618_092820.png

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

modb_20200618_092822.png

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

modb_20200618_092823.png

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

modb_20200618_092824.png

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

modb_20200618_092826.png

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

modb_20200618_092827.png

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

modb_20200618_092828.png

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

modb_20200618_092829.png

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

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

modb_20200618_092830.png

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

modb_20200618_092832.png

实际上,上面对动态对象年龄判定的描述并不精确。上图的场景也会导致相关对象晋升到老年代。年龄为1的对象加上年龄为2的对象超过了半数,这时包括年龄为2的对象以及年龄更大的对象都会被晋升到老年代。所以上图中年龄为2和3的对象都会被晋升到老年代。
老年代垃圾回收。随着年轻代对象的不断晋升,老年代的对象变得越来越多,达到容量阈值后老年代也会发生垃圾回收,我们称之为Major GC或者Full GC,Full GC并不是全局GC,它只发生在老年代。虽然年轻代和老年代都会发生GC,但是每次GC的时间和成本却大不相同。由于老年代空间大小一般是年轻代的几倍,再加上老年代对象存活率很高,所以整个标记过程比较慢,GC成本也非常高。我们经常说的JVM调优,主要是为了尽量减少老年代Full GC的时间和频次。老年代垃圾回收器,很少使用复制算法,主要为了避免大量对象的内存复制带来的时间和空间上的开销,一般采用标记清除、标记整理算法,就地标记回收。例如,老年代垃圾收集器CMS就采用了标记清除算法。对于标记清除算法带来的内存碎片问题,CMS提供了两个参数做碎片整理,-XX:+UseCMSCompactAtFullCollection和-XX:CMSFullGCsBeforeCompaction。

近期的热文:

Gdevops峰会:一起探讨国产分布式数据库的选型与应用

海底的下面究竟有什么?

几种去重的SQL写法

打造国产技术产品的必要性

六一儿童节带给我们的思考

SQL查询总是先执行SELECT语句么?

Oracle删除字段的方式和风险,你都了解么?

最烧脑的珠峰高程测算过程

了解阿克曼转向原理的作用

《你就是孩子最好的玩具》学习笔记 - 第一章

登录缓慢的诡异问题

不可不知的7个JDK命令

一个Full GC次数过多导致系统CPU 100%的案例排查

Java GC的基础知识

Linux下的^M困惑

Oracle相关提问的智慧技巧

很久以前的一篇对初学Oracle建议的文章

PLSQL Developer几个可能的隐患

从70万字SRE神作提炼出的7千字精华文章

从数据误删到全量恢复的惊险记录》《公众号600篇文章分类和索引


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK