前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java底层-GC子系统

Java底层-GC子系统

作者头像
每天学Java
发布2020-07-21 10:18:45
4890
发布2020-07-21 10:18:45
举报
文章被收录于专栏:每天学Java每天学Java

在前面HotSpot组成的文章中提到HotSpot是由三大子系统和两大组件组成,其中三大子系统中的类加载子系统和执行引擎子系统已经做过介绍,这一篇我们来看最后一个子系统:GC子系统。

在HotSpot虚拟机中,三大子系统核心基本都是为运行时数据区服务,类加载子系统负责将字节码文件加载到运行时数据区, 执行引擎将执行线程中虚拟机栈的栈帧存储的指令集进行执行,而GC子系统(垃圾回收子系统)的目的是对运行时数据区的数据进行回收, 关于GC的知识,我想大多数Java开发都有所了解,毕竟这是面试中很常见的问题。下面我们步入正题:

虚拟机为什么需要GC子系统呢?我们首先想一下运行时数据区中哪些模块会产生OOM

  • 方法区
  • 虚拟机栈

在这些会产生OOM的区域中,GC主要负责的是方法区和堆,在JDK8以前,永久代也是在回收范围内,因为其使用的内存是堆区域内存,但是JDK8中HotSpot对于方法区的实现(元空间)使用了直接内存,虽然元空间也在GC回收范围内,但是其出现OOM的可能性比较低、虚拟机栈由于其数据结构的特效且与线程绑定所以GC并不负责这一部分,所以GC子系统的的核心就是堆区域,在前面我们也提到堆中主要存储的是对象,这些对象的来源大概有两种,一种是类加载的初始化过程中,静态成员变量 创建的对象,这类对象由于被方法区Class对象所引用,所以往往不会被回收(类卸载后或设置为null后会被回收),而另一类 则是虚拟机栈指令执行过程中产生的对象(实例化对象等方式),这些对象引用在虚拟机栈中,当线程执行完成,方法栈销毁之后(或者栈帧出栈后),那么这些对象可能就会成为没有引用的对象,不被引用的对象实际上已经没有存在的必要了,不进行回收就会浪费堆区域内存,这就是为什么我们需要GC回收子系统,此外在程序执行的方法中创建的对象 往往是很多的,但是随着栈帧的出栈对象也就无用了起来(不存在引用),所以我们说大多数对象朝生夕死。

既然需要GC子系统对堆内存的无用对象进行回收,那么GC子系统又是如何判断一个对象是否有用呢

在JDK1.2以前主要使用的方法为引用计数,当我们创建一个对象时,会为该对象分配一个计数器,当引用增加或引用减少的时候, 计数器就会增1或者减1,当计数器为0的时候,那么可以认为该对象属于无用对象,这种方式虽然有效,但是却忽略一个问题,那就是 两个对象相互引用,但两者都没有被其他的对象引用,但由于相互引用两个对象的引用计数都为1,无法进行回收。如下图:

所以JDK2的后续版本,增加了 可达性分析,可达性分析指的是通过GC ROOT对象作为起始点, 从这些节点开始向下搜索,当一个对象到 GC Roots 没有任何引用链相连(不可达时) 则证明此对象是不可用的。通常可以认为是GC ROOT的对象有:

  • Class对象。系统类加载器加载的Class对象,这类对象通常不会被回收
  • 活动线程。
  • 虚拟机栈中的参数或变量
  • 正在被用于同步的各种锁对象(Monitor Used)
  • JVM自身持有的对象,比如系统类加载器,异常类等等

判断对象是否有用的方法知道了,那下面就是在堆中寻找这些对象,如果每次都对内存区域的对象进行遍历,显然成本太高,效率太低。在Hotspot虚拟中,堆被划分为新生代和老年代(JDK8之前的永久代使用的也是堆内存),新生代分为Eden区(伊甸区)和两个Survivor区(幸存区),它们默认比例为8:1,如下图:

对于一些刚创建的对象通常是放在新生代的Eden区(部分大对象会直接进入老年代,可通过-XX:PretenureSizeThreshold参数指定大小),在Eden区这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到n时,就会被移动到年老代中,先看下一Minor GC和Full GC概念

  • 从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。
  • Full GC 是清理整个堆空间—包括年轻代和老年代。

年龄n的数值可通过如下参数设置

  • -XX:MaxTenuringThreshold=n  新生代的对象最多经历n次GC, 就能晋升到老年代, 但不是必要条件
  • -XX:TargetSurvivorRatio=n  用于设置Survivor区的目标使用率,即当survivor区GC后使用率超过这个值, 就可能会使用较小的年龄作为晋升年龄

那么Survivor区有什么用呢?在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”(复制算法),而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向,当这次GC结束后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是说Survivor区总有一个内存是闲置的。

分代之后,我们回收的对象范围就确定了,那JVM用什么回收呢?不同版本的HotSpot回收工具是不一样,这里我们列举一下HotSpot的GC回收器(截止到JDK14) 在JDK1.7之前回收是分代,新生代和老年代分别有相应的回收器,首先是新生代:在JDK1.3前堆内存中新生代的回收器只有Serial,直到JDK1.4增加了Parallel和ParNew回收器可供选择, 而在老年代中和Serial Old配合Serial,在JDK1.4后引入新的回收算法,在JDK1.5出现了CMS,在JDK1.6时候增加了Parallel Old。

而在JDK1.7之后提出了G1回收器,该回收器不区分老年代和新生代,后续在JDK11的Epsilon,JDK12中提出了Shenandoah,JDK14中提出了ZGC垃圾回收器的概念(低延迟垃圾回收器)。

回收并不是毫无规律的,每种回收器都有回收算法

  • 标记清除算法
  • 复制算法
  • 标记清除压缩算法

关于GC回收子系统就谈到这里,关于每种垃圾回收器以及回收算法我们在后面的文章继续探讨。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-07-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 每天学Java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档