专栏首页每天学JavaJava底层-GC子系统

Java底层-GC子系统

在前面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回收子系统就谈到这里,关于每种垃圾回收器以及回收算法我们在后面的文章继续探讨。

本文分享自微信公众号 - 每天学Java(gh_fddfb9d03324),作者:每天学Java

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-07-19

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 探究Java8的Stream(一)

    “ Java8新特性中我们聊过Lambda表达式和方法引用,这一篇我们来看一下Java8中另一个特性:Stream。”

    每天学Java
  • Java设计模式(四)责任链模式

    在公众号的前面我们说策略模式的时候,我们说各种策略的对象和一个行为随着策略对象改变而改变的。换句话说,针对客户端传来不同的参数进行实例不同策略的对象,也就是说保...

    每天学Java
  • Spark读写MySQL数据

    执行的过程中,出现了很多次的jar冲突,我这边和Hadoop-common 以及 hadoop-dfs有依赖冲突,具体的根据自己实际情况去除

    每天学Java
  • Java GC的基础知识

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

    bisal
  • 知道如何优化垃圾回收机制吗?

    在 Java 开发中,开发人员是无需过度关注对象的回收与释放的,JVM 的垃圾回收机制可以减轻不少工作量。但完全交由 JVM 回收对象,也会增加回收性能的不确定...

    田维常
  • 笔者带你剖析大规模分布式Java平台JVM性能调优基础

    其实说到对JVM进行性能调优早已是一个老生常谈的话题,如果你所在的技术团队还暂时达不到淘宝团队那样的高度,无法满足在OpenJDK的基础之上根据自身业务进行针对...

    九州暮云
  • Java的分代式GC

    要说理解JVM的垃圾回收,什么引用计数,Copy GC,mark & compaction好像都不是必须要掌握的东西。真要说对普通的Java程序员比较重要的东西...

    海纳
  • JVM笔记九-GC收集器日志信息学习

    在上一篇文章中,我们通过代码运行结果,查看到JVM的堆内存逻辑上分区是三部分,物理上分区是2部分,以及是新生代分区三部分,占比分布是8/1/1。而且我们还通过代...

    凯哥Java
  • 面试高峰期,如何应对面试官的jvm刁难,特写一篇jvm面经(第一部)

    读者前言 已经进入三月份,正所谓金三银四,正是一年最好的招聘期,想必我的公号粉丝们一定有不少想要跳槽的吧,哈哈,/**偷偷告诉你们其实小编也准备...

    用户1257393
  • 面试杂谈 - 谈谈你对GC的理解

    接下来就对GC做一个全方位的总结,希望下次可以自信地回答面试官:我是可以被贵公司回收的那种。

    acupt

扫码关注云+社区

领取腾讯云代金券