JVM垃圾回收

一、垃圾回收判断

  引用计数法:每次引用+1,解引用-1,引用为0可被回收,解决不了循环引用问题,会造成内存泄漏

  可达性分析算法:被根对象直接或间接引用的对象都不能回收

Q:那些对象可以作为GC Root?

  Memory Analyzer工具可以作为堆内存分析工具,可以找到作为GC Root的对象

  抓取内存快照jmap -dump:format-b,live,file-1.bin pid

  a、System class 系统核心类,Object类,String类,HashMap等等

  b、Native Stack 操作引用的

  c、Thread 活动线程使用的对象,例如被局部变量引用的对象

  d、Business Monitor  被加锁的对象

二、四种引用

  1、强引用 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收

  2、软引用(SoftReference) 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用 对象 可以配合引用队列来释放软引用自身

  3、弱引用(WeakReference) 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象 可以配合引用队列来释放弱引用自身

  4、虚引用(PhantomReference) 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队, 由 Reference Handler 线程调用虚引用相关方法释放直接内存

  5、终结器引用(FinalReference) 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象 暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象

例如:创建ByteBuffer的时候会创建一个名为Cleaner的虚引用对象,当ByteBuffer没有被强引用所引用就会被jvm垃圾回收,虚引用Cleaner就会进入引用队列,会有专门的线程扫描引用队列,被发现后会调用直接内存地址的方法将直接内存释放掉,保证直接内存不会导致内存泄漏

是所有引用类型中最弱的,一个对象是否有虚引用的存在,完全不会对其生命周期构成影响,也无法通过虚引用获得一个对象实例。

  终结器引用:创建的时候会关联一个引用队列,当A4对象没有被强引用所引用时,A4被垃圾回收的时候,会将终结器引用放入到一个引用队列(被引用对象暂时还没有被垃圾回收),有专门的线程(优先级较低,可能会造成对象迟迟不被回收)扫描引用队列并调用finallize()方法,第二次GC的时候才能回收掉被引用对象

三、垃圾回收算法

  标记清除:速度快,但会产生内存碎片

  标记整理:解决内存碎片问题,但效率低,牵扯到内存地址的变动

  复制:两块区域FROM和TO,首先标记,再进行复制从FROM到TO,最后进行交换,不会产生碎片问题,但会产生两块区域浪费内存空间

  分代垃圾回收机制:新生代(eden,s1,s2),老年代

新的对象先被放入eden区,如果eden内存空间不够,会进行一次Minor GC,先标记出不能回收的对像,复制到TO区,对象年龄+1,再与FROM交换位置,

当年龄大于15的时候就会被移到老年代,当老年代内存空间不足的时候,会先进行至少一次Minor GC,如果内存还是不足则会进行一次Full GC,此时如果内存还是不足则会产生内存溢出。

GC会产生Stop The World,根据老年代的特点采用标记-整理-清除算法。

 四、垃圾回收器

1、串行:单线程回收 

  单线程

  堆内存较小,适合个人使用

  -XX:+UseSerialGC

2、吞吐量优先:基于标记-整理的算法来进行垃圾回收,运行到一个节点(安全点),STW所有线程(一般就是核心数)一起进行垃圾回收,cpu在这时会突然飙高到100%

  多线程

  堆内存较大,多核cpu

  让单位时间内,STW的时间最短

  并行的执行,会STW

  -XX:+UseParallelGC 作用在新生代 

  -XX:+UseParallelOldGC 作用在老年代

  -XX:UseAdaptiveSizePolicy 设置此项以后,并行收集器会自动选择年轻代大小和相应的Surivior区比例,以达到目标系统规定的最低响应时间或者收集频率等,此值建议使用并行收集器时一直打开。

  -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n) n一般设置19  一百分钟内允许五分钟的暂停

  -XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值

  -XX:ParallelGCThreads=n:设置并行收集器并行收集时,使用的线程数,一般等于CPU数

3、响应时间优先(CMS主要作用于老年代):基于标记-清除的算法并发(减少STW的时间)的进行垃圾回收,首先也是到达一个安全点,进行初始标记(STW,标记根对象),然后是一个并发标记(不会STW),

重新标记(STW,因为在并发标记的时候,用户线程也在执行),最后并发的清理。对cpu的占有率相对较低,响应时间降低了,吞吐量上升了一点。当内存碎片化较多的时候,不能够存储新的对象的时候,

会退化成单线程垃圾回收器,进行一次标记整理。

  多线程

  堆内存较大,多核cpu

  尽可能让单次STW的时间最短

  并发的执行,不会停掉用户线程(只是其中一些阶段)

  -XX:+UseParNewGC 作用在新生代 (基于标记-复制算法)

  -XX:+UseConcMarkSweepGC 作用在老年代 ,有时候并发失败会退化到-SerialOld(串行化)

  -XX:ParallelGCThreads=n:并行收集线程数

  -XX:ConcGCThreads=n:设置并发收集器收集时使用的线程数,建议并行收集线程数的1/4

  -XX:CMSFULLGCsBeforCompaction=n:由于并发收集器不对内粗空间进行压缩、整理,所以运行一段时间会产生“碎片”,使得运行效率低。此值设置运行n次GC以后对内训空间进行压缩、整理。

  -XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片。

  -XX:CMSInitiatingOccupancyFraction=70:当老年代内存使用达到n%,开始回收。CMSInitiatingOccupancyFraction = (100 - MinHeapFreeRatio) + (CMSTriggerRatio * MinHeapFreeRatio / 100)`

  -XX:+CMSScavengeBeforeRemark:重新标记之前先进行一次ygc

4、Garbage First(G1)

                     G1 垃圾回收阶段

  同时注重吞吐量和低延迟,并发的执行

  适合超大内存,划分多个相等的区域

  整体上使用的标记整理算法,两个区域之间使用的复制算法

配置参数参考:https://blog.csdn.net/megustas_jjc/article/details/105470675

java9之前,-XX:+UseG1GC,java9之后默认是G1

 -XX:MaxGCPauseMillis=n:设置最大GC 暂停时间。这是一个大概值,JVM 会尽可能的满足此值

-XX:G1HeapRegionSize=n:使用G1,Java堆被划分为大小均匀的区域。这个参数配置各个子区域的大小。此参数的默认值根据堆大小的人工进行确定。最小值为 1Mb 且最大值为 32Mb。

回收阶段:https://www.jianshu.com/p/989429f646af

Q:新生代垃圾回收,跨代引用问题?

  采用以卡表与Remembered Set的方法解决,https://blog.csdn.net/weixin_49193222/article/details/111313505

Q:Remark

  • 黑色:表示根对象,或者该对象与它引用的对象都已经被扫描过了。
  • 灰色:该对象本身已经被标记,但是它引用的对象还没有扫描完。
  • 白色:未被扫描的对象,如果扫描完所有对象之后,最终为白色的为不可达对象,也就是垃圾对象。

jdk8u20-XX:+UseStringDeduplication字符串去重功能(会占用cpu时间)

jdk8u40-XX:+ClassUnloadingWithConcurrentMark 在并发标记阶段结束后,JVM就进行类卸载

jdk8u60 回收巨型对象:不会进行copy,回收时优先考虑,会跟踪老年代所有的incoming引用,这样老年代incoming引用为0的巨型对象就可以在新生代垃圾回收时被回收

jdk9对G1的优化:

  并发标记必须在堆空间占满前完成,否则退化为fullGC

  jdk9之前需要使用XX:InitiatingHeapOccupancyPercent=n(启动并发GC周期时的堆内存占用百分比. G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示"一直执行GC循环". 默认值为 45.)设置,

  jdk9之后,可以动态调整这个值,会添加一个安全的空挡空间

五、JVM调优

  1、官网查看JVM参数

  2、java –XX:+PrintFlagsFinal -versio | findStr "GC" 查看GC配置情况

  3、内存

  4、锁竞争

  5、cpu

  6、吞吐量和响应时间的取舍

  7、ZGC是从JDK11中引入的一种新的支持弹性伸缩和低延迟垃圾收集器

  8、另一种虚拟机Zing

六、GC调优

  1、新生代内存调优

  Q:新生代越大越好吗?

    否,新生代太小,会频繁的发生minor gc,太大发生GC的时间会延后,并且会占用老年代的空间,使得发生full GC的几率变高,官网建议25%网上,50%往下,实际配置时要进行权衡

  Eden区:能容纳并发量*(请求-响应)的数据

  幸存区:大到能保存【当前活跃对象+需要晋升对象】,晋升阈值调配要得当

  2、老年代的内存调优

    a、越大越好

    b、先尝试不做调优,如果没有full GC,可以先尝试调优新生代

    c、观察发生full GC时 老年代内存占用,响应调大1/4-1/3

    d、调整老年代占用达到的阈值为75%-80%

  

  

原文地址:https://www.cnblogs.com/sglx/p/15215993.html