new

源码篇-JVM调优(一)-认识垃圾,垃圾回收算法和垃圾回收器

2021-03-24  本文已影响0人  秃头猿猿

1 垃圾

1.1 前言

众所周知,JVM拥有着垃圾回收器,自动回收内存,让开发人员只专注于编程,而不需要手动进行内存管理。

那么到底什么样的对象才会被定义为垃圾,且被JVM的垃圾回收器回收掉

1.2 定位

1.2.1 简介

JVM中当一个对象没有任何引用指向他的时候就会被认为是一个垃圾,例如下图中

当栈里面的person引用不再指向堆里面的Person对象时,Person对象就会被认为是一个垃圾对象,那么就会被回收掉

image-20210317110245223.png

但有时候在堆里面,对象相互指向,但是栈里面并没有任何一个引用指向对象,那么那些对象会被认为是一堆垃圾。如下图所示

image-20210317110743157

栈里面没有一个引用指向堆里面的对象,那么对象1 对象2 对象3 就会被认为是一块垃圾,从而被回收掉

1.2.2 寻找

那么JVM是如何寻找到垃圾对象,并且进行回收掉的呢? 在JVM有两种算法去定位垃圾的存在:

接下来就来仔细探讨这两种算法的区别是什么


引用计数

所谓的引用计数指的的就是记录一个对象有多少个引用指向该对象。当引用为0,即没有任何引用指向该对象时,就认为该对象为垃圾。

如图

image-20210319095808432.png

当没有任何引用指向对象时,即对象存储的计数为0时,则认为是一个垃圾

image-20210319100015328

注意:这种方式的坏处就是当几个对象相互指向的时候,是没有办法通过引用计数来判断对象是否为垃圾

因此JVM则采用另外一个算法(根可达算法)来确定垃圾


根可达算法

所谓的根可达算法就是一系列称为GCRoots的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(ReferenceChain)。

如果某个对象到GCRoots间没有任何引用链相连,或者用图论的话来说就是从GCRoots到这个对象不可达时,则证明此对象是不可能再被使用的

如图所示

image-20210319102446798

图中,可以通过根“找到”对象1,对象2,对象3,就认为这三个对象不是垃圾

图中,对象10,对象20,对象30 通过 根引用无法“找到”,所以这三个对象是垃圾

JVM规范中以下对象会认为是根:

2. 回收

JVM中关于常见垃圾回收的算法,一共有三个:

下面是对这三种算法的详细描述和对比

2.1 Mark-Sweep

JVM中最早出现的垃圾回收算法,算法主要分为标记和清除两部分,简单了解就是先标记所有的垃圾对象,然后统一回收掉所有的标记对象。如下图所示

image-20210319115502139 image-20210319115543244

注意

这种方式回收垃圾,会产生碎片化的内容,从而有可能导致无法分配连续的内存空间

2.2 Mark-Copying

标记-复制算法也叫复制算法,这种算法的出现主要是为了解决标记-清除算法的缺陷。标记-复制算法就是将一个可用内存划分为两块等同的区域。

每次都使用其中某一块区域,当一块区域(A)使用完毕就会把存活的对象复制到另外一块区域(B),复制完成,再将之前的区域(A)进行清空。如图

image-20210323094343538 image-20210323094522866

这种算法好处如下:

但是缺点也显而易见,如下:

纵观有这样那样的缺陷,但是现在商用的JVM,例如HotSpot,也是有限采用这种算法去对新生代的对象进行垃圾回收

2.3 Mark-Compact

标记-压缩算法也是针对标记-清除算法,进行改进,当标记的对象清除以后,存货的对象进行移动,从而防止碎片化的内存出现。具体如下:

image-20210323100228305 image-20210323100418347

这种算法与之前的标记-清除算法本质的区别就是,标记-清除算法只是清除,而不会移动存活的对象。标记-压缩则是清除后会进行移动,从而防止碎片化的内存出现

这种算法也有很大的缺陷:

3. 分代

在经典的垃圾回收器中,JVM采用了分代模型(主要是针对jdk1.7 - jdk1.9),JVM将堆分为了两部分

注意:如果JVM采用最新的垃圾回收器,则不区分新生代和老年代了

堆内存模型图如下:

image-20210323103748384

3.1 新生代

新生代主要分为三个区域:

其中他们的比例为 8 :1 : 1,同时,新生代采用的是复制算法进行垃圾回收。如下:

image-20210323104114939

新生成的对象优先存放在新生代中,新生代对象朝生夕死,存活率很低,在新生代中,常规应用进行一次垃圾收集一般可以回收70% ~ 95% 的空间,回收效率很高。

HotSpot将新生代划分为三块,一块较大的Eden空间和两块较小的Survivor空间,默认比例为8:1:1。划分的目的是因为HotSpot采用复制算法来回收新生代,设置这个比例是为了充分利用内存空间,减少浪费。新生成的对象在Eden区分配(大对象除外,大对象直接进入老年代),当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC

GC开始时,对象只会存在于Eden区和Survivor 0区,S1区是空的(作为保留区域)。GC进行时,Eden区中所有存活的对象都会被复制到S1区,而在S0区中,仍存活的对象会根据它们的年龄值决定去向,年龄值达到年龄阀值(默认为15,新生代中的对象每熬过一轮垃圾回收,年龄值就加1,GC分代年龄存储在对象的header中)的对象会被移到老年代中。

没有达到阀值的对象会被复制到s1区。接着清空Eden区和s0区,新生代中存活的对象都在s1区。接着, s0区和s1区会交换它们的角色,也就是新的s1区就是上次GC清空的s0区,新的s0区就是上次GC的s1区,总之,不管怎样都会保s1区在一轮GC后是空的。GC时当s1区没有足够的空间存放上一次新生代收集下来的存活对象时,需要依赖老年代进行分配担保,将这些对象存放在老年代中。

3.2 老年代

老年代与新生代比例为3:1,如下:

image-20210323104315585

在新生代中经历了多次(具体看虚拟机配置的阀值)GC后仍然存活下来的对象会进入老年代中。老年代中的对象生命周期较长,存活率比较高,在老年代中进行GC的频率相对而言较低,而且回收的速度也比较慢。

注意:当较大的对象无法在新生代分配内存时,则会直接进入老年代

注意:老年代的垃圾回收称为FullGC,老年代频繁垃圾回收则会出现STW现象,因此JVM调优主要就是尽量减少FGC

4. 回收器

上述都是对JVM进行理论阐述,而垃圾回收器则是对上述理论的实现。在《JVM规范》中没有对垃圾回收器做各种规定,因此不同的厂商的不同虚拟机会存在不同的区别。

在这里主要是探讨HotSpot虚拟机的垃圾回收器,关于所有垃圾回收器(jdk1.0-jdk13)如下图

垃圾回收器

图中的G1,ZGC,Shenandoah回收器不再区分新生代和老年代,Epsilon是JDK内部调试的回收器

在目前的环境中,向后面三个用的还是比较少的,因此主要是还是集中讨论新生代和老年代的垃圾回收

图中虚线部分连接起来代表两个垃圾回收器可以结合使用

4.1 新生代

4.1.1 Serial

Serial回收器是一个用来新生代垃圾回收器,Serial old是一个用在老年代的垃圾回收器

这个回收器是一个单线程工程的垃圾回收器,这个“单线程”回收器是指使用一个处理器或者一条收集线程去执行。

当进行垃圾回收的时候,必须暂停其他所有的工作线程,直到回收结束。当然这样就会产生STW现象。如下:

image-20210324101151501

从JDK1.3开始,一直到现在最新的JDK13,HotSpot虚拟机开发团队为消除或者降低用户线程因垃圾收集而导致停顿的努力一直持续进行着,从Serial收集器到Parallel收集器,再到ConcurrentMarkSweep(CMS)和GarbageFirst(G1)收集器,最终至现在垃圾收集器的最前沿成果Shenandoah和ZGC等,我们看到了一个个越来越构思精巧,越来越优秀,也越来越复杂的垃圾收集器不断涌现,用户线程的停顿时间在持续缩短,但是仍然没有办法彻底消除(这里不去讨论RTSJ中的收集器),探索更优秀垃圾收集器的工作仍在继续(出自《深入理解JVM第三版》)

4.1.2 ParNew

ParNew垃圾回收器本质上相当于Serial垃圾回收器的多线程并行版本,除了在回收的时候使用多线程并行回收,其他的与Serial回收器一摸一样。

具体工作流程如下:

image-20210324102804638

注意:上图中的老年代垃圾回收器采用的还是Serial Old,当然也可以与CMS回收器配合使用,当然也推荐与CMS配合使用

4.1.3 Parallel Scavenge

Parallel ScavengeParNew回收器类似,也是一个新生代的垃圾回收器,也是基于标记-复制算法实现的垃圾回收器。同时也是一款并行的垃圾回收器,

只不过Parallel Scavenge更加专注于一个可以达到的吞吐量

吞吐量 = 用户代码运行时间 / (用户代码运行时间 + 垃圾回收时间)

吞吐量越高就意味着垃圾回收时间越短,例如用户代码运行时间是20s,垃圾回收时间1s,那么意味着吞吐量为20/21 =95%

当然也可以通过一些参数去设置用户的吞吐量:

工作流程图如下:

image-20210324104302407

4.2 老年代

4.2.1 Serial Old

Serial Old就相当于Serial垃圾回收器的老年代版本,同样的是单线程,采用标记-压缩算法。这里就不再追诉了

4.2.2 Parallel Old

Parallel Old 回收器相当于是Parallel Scavenge回收器的老年代版本,这个回收器同样是一个基于标记-压缩算的多线程垃圾回收器。

这个垃圾回收器是在jdk1.6以后才开始提供,之前Parallel Scavenge垃圾回收器都是于Serial Old垃圾回收器配合使用。

直到Parallel Old垃圾回收器的出现,老年代才慢慢开始以吞吐量优先饿垃圾回收器

工作流程图如下:

image-20210324105144299

4.2.3 CMS

这个回收器是以最短回收停顿时间 为目标的垃圾回收器,从上述的介绍中可以得到老年代的的垃圾回收器,都需要暂停用户线程在进行垃圾回收,因此都会产生STW现象

而当今大部分Java程序都是以B/S架构为主,在开发这类程序时,更加关注服务端的响应时间,也就说尽量减少系统的停顿时间。

那么CMS回收器就是一个尽量减少停顿时间的的垃圾回收器。

5. 指令

JVM的指令主要分为三大类:

所有详细命令可以参考以下网址:

http://www.oracle.com/technetwork/java/javase/documentation/index.html

5.1 非标准

输入java -X即可看到常用的非标准指令,如下:

    -Xmixed           混合模式执行 (默认)
    -Xint             仅解释模式执行
    -Xbootclasspath:<用 ; 分隔的目录和 zip/jar 文件>
                      设置搜索路径以引导类和资源
    -Xbootclasspath/a:<用 ; 分隔的目录和 zip/jar 文件>
                      附加在引导类路径末尾
    -Xbootclasspath/p:<用 ; 分隔的目录和 zip/jar 文件>
                      置于引导类路径之前
    -Xdiag            显示附加诊断消息
    -Xnoclassgc       禁用类垃圾收集
    -Xincgc           启用增量垃圾收集
    -Xloggc:<file>    将 GC 状态记录在文件中 (带时间戳)
    -Xbatch           禁用后台编译
    -Xms<size>        设置初始 Java 堆大小
    -Xmx<size>        设置最大 Java 堆大小
    -Xss<size>        设置 Java 线程堆栈大小
    -Xprof            输出 cpu 配置文件数据
    -Xfuture          启用最严格的检查, 预期将来的默认值
    -Xrs              减少 Java/VM 对操作系统信号的使用 (请参阅文档)
    -Xcheck:jni       对 JNI 函数执行其他检查
    -Xshare:off       不尝试使用共享类数据
    -Xshare:auto      在可能的情况下使用共享类数据 (默认)
    -Xshare:on        要求使用共享类数据, 否则将失败。
    -XshowSettings    显示所有设置并继续
    -XshowSettings:all
                      显示所有设置并继续
    -XshowSettings:vm 显示所有与 vm 相关的设置并继续
    -XshowSettings:properties
                      显示所有属性设置并继续
    -XshowSettings:locale
                      显示所有与区域设置相关的设置并继续

-X 选项是非标准选项, 如有更改, 恕不另行通知。

例如在这里可以设置堆的初始化内存大小和最大堆

java -Xms128m -Xmx1024m

5.2 高级运行时指令

常用的高级指令如下:

-XX:+PrintFlagsFinal
# 设置最终生效值


-XX:+PrintFlagsInitial
# 查看默认值

-XX:+PrintCommandLineFlags
# 查看命令行参数

例如输入

java -XX:PrintCommandLineFlags
image-20210324125033378

上图中选中的代表现在HotSpot虚拟机采用的是Parallel Scavenage + Parallel Old垃圾回收器

上一篇 下一篇

猜你喜欢

热点阅读