JVM垃圾回收

-# 如何判断对象可回收

引用计数法

概念:当垃圾回收机制在回收垃圾的时候,对象如果被引用的情况下,对象引用次数就会加1,如果该对象没有被引用的情况下则引用次数就会减1,如果该对象引用次数为0的情况下,则认为该对象是可回收对象
缺陷:引用计数法在java虚拟机没有被使用过,存在循环引用的问题,比如A依赖B,B依赖A,A和B的引用次数都是为1,这时候没有其他的对象依赖于A和B,引用计数法永远不会回收A和B对象。

可达性分析算法

通过一系列的称为”GC Roots“的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

哪些可以作为GC Roots

虚拟机栈(栈帧中的本地变量表)中引用的对象
元空间中类静态属性引用的对象
本地方法栈中JNI引用的对象

四种引用

强引用

被引用关联的对象永远不会被垃圾收集器回收,如Object object=new Object(),那object就是一个强引用了,如果一个对象具有强引用,垃圾回收器绝不会回收它,当内存空间不足,Java虚拟机就会抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题

软引用

软引用关联的对象,在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统就会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。

弱引用

无论内存是否足够,只要JVM开始垃圾回收,那些被弱引用关联的对象都会被回收

虚引用

如果一个对象仅持有虚引用,那么它就和没有任何引用一样,随时可能会被回收

引用队列

软弱对应引用的指针放入到引用队列中,实现清理。

回收算法

标记清除算法

根据GCRoot对象的引用链,发现该对象没有被引用的情况下,则标记为可回收对象,清除
优点:算法简单
缺点:内存空间不连续,空间利用率不高,容易产生碎片化

标记整理算法

根据GCRoot对象的引用链,发现对象没有被引用的情况下,则标记为可回收对象,标记清除算法和标记整理区别在于:避免标记清除算法产生的碎片问题,清除的垃圾过程中,会将可用的对象实现移动,内存空间更加具有连续性。
优点:没有内存的碎片问题
缺点:整理过程中会产生内存地址移动,效率可能偏低。

标记复制算法

根据GCRoot对象的引用链,发现该对象没有被引用的情况下,将正在被引用的对象拷贝到to区中,然后再清理from区,再交换位置。
优点:不会产生内存碎片
缺点:比较占内存空间

分代算法

分为新生代和老年代
新生代:刚创建的对象一般的情况下都是放在新生代中,新生代分为eden区、from区、to区,刚创建的对象会放在eden区,当新生代eden区空间满的情况下,会将引用的对象拷贝到to区中,如果gc回收15次的时候,发现该对象还一直被使用的情况下,该对象就会晋升为老年代中。

哪些对象会晋升到老年代中

  1. 年限达到当gc多次回收的时候,如果能够一直引用到下一个阈值的情况下,直接晋升到老年代中。
  2. 直接是大对象。
    程序发生内存溢出:存放的对象空间大小大于我们老年代的空间大小。

为啥在分代算法中,新生代中有from和to区大小一样

因为在新生代gc触发非常频繁,为了能够更加高效清理垃圾,所以采用标记复制算法。
在这里插入图片描述

Stop-The-World

在垃圾回收过程中经常涉及到对对象的挪动,进而导致需要对对象的引用进行更新,为了保证引用更新的正确性,Java将暂停所有其他的线程,这种情况被称为“stop-the-world”,导致系统全局停顿,stop-the-world对系统性能存在影响,因为垃圾回收的一个原则是尽量减少“stop-the-world”的时间

GC核心参数

-Xms100 -Xmx
1、-Xms
初始大小内存,默认为物理内存 1/64,等价于 -XX:InitialHeapSize
2、-Xmx
最大分配内存,默认为物理内存的 1/4,等价于 -XX:MaxHeapSize
3、-Xss
设置单个线程栈的大小,一般默认为 512-1024k,等价于 -XX:ThreadStackSize
4、-Xmn
设置年轻代的大小
整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小
持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
5、-XX:MetaspaceSize
设置元空间大小
元空间的本质和永久代类似,都是对 JVM 规范中的方法区的实现。
元空间与永久代之间最大区别:元空间并不在虚拟机中,而是使用本地内存
因此默认情况下,元空间的大小仅受本地内存限制,元空间默认比较小,我们可以调大一点
6、-XX:+PrintGCDetails
输出详细GC收集日志信息
7、-XX:SurvivorRatio
设置新生代中 eden 和 S0/S1 空间比例,默认 -XX:SurvivorRatio=8,Eden : S0 : S1 = 8 : 1 : 1
8、-XX:NewRatio
配置年轻代和老年代在堆结构的占比,默认 -XX:NewRatio=2 新生代占1,老年代占2,年轻代占整个堆的 1/3
9、-XX:MaxTenuringThreshold
设置垃圾最大年龄

总结

  1. 对象首先分配到eden区,新生代GC(Minor GC)
  2. 新生代空间不足时,触发MinorGC,将eden区存活的对象采用标记复制算法放入到to区中,并且该对象的寿命+1,MinorGC采用的是标记复制算法,会触发stop the world暂停其他用户的线程,等待垃圾回收结束之后,其他的用户线程才会继续执行
  3. 如果该对象的寿命>15的情况下,则将该对象放入到老年代中,对象寿命放在对象的请求头中。
  4. 当老年代空间不足的时候,会触发FullGC采用标记清理/整理算法
  5. 新生代GC非常频繁,速度比老年代GC要高

垃圾回收器

触发gc()回收的时候会回调finalize的方法
public class Test009 {
    public static void main(String[] args) {
        new Test009();
        //提醒gc回收清理垃圾,但不是立马回收垃圾
        System.gc();
        //强制调用已经失去引用的对象的finalize方法
        System.runFinalization();
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("重写finalize方法>>>>");
    }
}

如何避免触发垃圾收集器频繁回收

  1. 堆内存空间设置比较大
  2. 堆内存初始化和最大值一定保持一致
  3. 生产环境不要去调用System.gc

垃圾收集器和垃圾算法区别

垃圾收集器:串行、并行收集、cms、G1、ZGC收集器,能够降低对我们用户线程暂停的时间或者用户线程和GC线程的同时运行

串行垃圾收集器

只有一个GC线程清理堆内存垃圾,堆内存不是很大的,有SerialGC

并行垃圾收集器

同时开启多个GC线程清理堆内存垃圾
串行和并行都会触发stw

并发垃圾收集器

并发是允许用户线程和gc线程同时进行的,降低stw的时间,有CMS和G1垃圾收集器

java8默认的垃圾收集器

UseParallerGC并行收集器

CMS垃圾收集器(Concurrent Mark Sweep)

CMS(Concurrent Mark Sweep)收集器,以获取最短回收停顿时间(stw)为目标,多数应用于互联网站或者B/S系统的服务器端上,CMS是基于”标记-清除“算法实现的,整个过程分为四个步骤:

  1. 初始标记
  2. 并发标记
  3. 重新标记
  4. 并发清除
    ”标记“是指将存活的对象和要回收的对象都给标记出来,而”清除“是指清除掉将要回收的对象。其中,初始标记、重新标记这两个步骤仍然需要”stop the world“
    初始标记只是标记一下GCRoots能直接关联到的对象,速度很快。
    并发标记阶段,用户线程和GC线程同时进行,不会阻碍业务线程继续执行,找到GCRoots所有引用的对象。
    重新标记阶段则是为了修正并发标记期间因用户程序继续执行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段长一些,但远比并发标记的时间短。
    并发清除阶段用户线程和GC同时运行,
    浮动垃圾:CMS将无法对这些垃圾对象进行标记,在用户线程和GC同时运行的时候,用户线程运行的过程中可能会产生垃圾对象,但这个时候没有进行标记,没有被及时的回收,从而只能再下一次GC时才能回收。

为什么CMS收集器采用标记清除算法而不是标记整理算法

因为CMS收集器采用并发清除,清除垃圾和用户线程可以同时进行,为了保证用户线程和GC线程同时运行,所以采用标记清除算法,如果采用标记整理算法或者标记复制算法的话,有可能会导致移动内存地址,会发生stw的问题。

CMS垃圾收集器的优点和缺点

优点:

  1. 并发收集器GC线程可以与用户线程同时并发执行
  2. 降低用户线程等待时间
    缺点:
  3. 会发生内存碎片化(标记清除算法)
  4. 用户线程空间不足,无法存放大对象的情况下,有可能会触发FULLGC
  5. 消耗CPU资源(与用户线程同时执行)
  6. CMS收集器无法处理浮动垃圾

CMS与传统的收集器存在缺点

  1. cms收集器只适合于使用老年代,采用标记清除算法,可以实现GC线程与用户线程同时执行,减少stw时间
  2. 标记清除算法会产生大量的碎片化的问题,如果存放一个大对象的时候,有可能会频繁的引发FullGC,使用串行老年机收集器单线程清理堆垃圾
  3. 在传统的收集器中,如果存放的对象大于新生代内存空间,则直接晋升到老年代,如果改对象不是很频繁使用,会非常浪费堆内存空间

G1收集器(Garbage First)

概念

  1. G1是一个并行/并发回收器,它把堆内存分割为很多不相关的区域(Region)物理上不连续的,使用不同的Region来表示Eden、幸存者s0区、幸存者s1区、老年代
  2. 由于这种方式的侧重点在于回收垃圾最大量的区间(Region),所以给G1一个名字:垃圾优先
  3. G1中的的GC有计划避免在整个Java堆中进行全区域的垃圾收集,G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region

使用场景

G1是一款面向服务端应用的垃圾收集器,主要针对配备多核CPU及大容量内存的机器,以极高概率满足GC停顿时间的同时,还兼备高吞吐量的性能特征,JDK9中默认使用G1收集器,可以全功能的垃圾收集器,采用标记整理算法避免堆空间冗余性问题。

GC如何整体评估指标说明

吞吐量

运行用户代码占总时间的比例,如用户线程的运行时间(100S)+GC内存回收的时间(1S) 则100/101=99%

GC负荷

与吞吐量相反,指应用花在GC上的时间百分比,1/101=1%

暂停时间

应用线程花在GC stop-the-world的时间,暂停时间越小越好

GC频率

GC次数频率越多,stw暂停时间越短,GC频率次数越少,stw暂停时间越长

反应速度

从一个对象变成垃圾对象时被回收的时间

吞吐量优先的回收器

Parallel(多线程)并行收集器,jdk8默认收集器

响应时间优先的收集器

CMS收集器、ParallelNew(新生代手机),响应时间优先(低延迟),stw时间短

G1/ZGC注重吞吐量和响应时间优先

如果收集器注重吞吐量优先:相当于减少GC的回收频率,GC耗时比较长,导致用户线程stw也会比较长
如果收集器注重响应时间优先(低延迟):GC回收垃圾的时间比较短(用户线程暂停时间比较短),垃圾回收频率次数比较多。

Jvm参数性能调优

如何避免用户线程暂停时间STW比较短

  1. 堆内存空间一定要充足
  2. 项目启动堆内存初始值与最大值一定要保持一致
  3. 不建议调用System.gc(),这个有可能会触发FullGC
  4. 不要在堆内存中存放大对象和全局变量,会触发FullGC
  5. 合理根据项目堆内存情况,选择收集器。

项目如何合理设定堆的初始大小和选择合理的垃圾收集器

  1. 适用于起步阶段的个人网站 建议堆内存1GB 可以串行SerialGC 建议使用并行Parallel GC
  2. 有一定访问量的网站或APP 建议堆内存2g 建议使用Parallel GC
  3. 并发适中的APP或普通数据处理 建议堆内存4g 老年代CMS/新生代parnew
  4. 适用于并发要求较高的APP 建议堆内存8G 16Gw 建议G1收集器 注重低延迟和吞吐量

JVM实战参数性能调优

  1. 查看垃圾回收清理垃圾的频率 jstat
  2. 排查生产环境内存溢出问题
  3. 使用jvisualvm 监听生产环境虚拟机堆内存情况

参考:蚂蚁课堂

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值