谈谈实战中的JVM优化(三:GC优化)

269 阅读9分钟

以下无特殊说明,都是针对于HotSpot虚拟机。

GC的定义

垃圾回收,也就是在我们JVM程序中新创建的不作为任何用途去使用的对象。

怎么确认是垃圾?

可达性分析算法: 如果某个对象到达"GC Roots"根对象没有任何引用链相关联,也就是从GC Roots到这个对象不可达,则该对象被认为不可能再被使用。

GC Roots对象包含:虚拟机栈中引用的对象、方法区中静态属性引用的对象、方法区中常量引用的对象等等。另外还有“计数算法”,感兴趣的朋友可以自行了解。

强弱软虚

按照可达性算法,没有GC Roots的引用就是垃圾,这样对于程序设计来说,有点太过绝对,尤其是我们需要保留这些垃圾在内存足够的时候不回收,在特别紧张的时候才回收(常见如:缓存数据),JDK1.2后,扩展了引用的概念,主要包含以下四类:

1:强引用

是程序中普遍存在的引用赋值(比如:Object o = new Object()),这种对象,垃圾回收器永远不会回收。

2:软引用

用来描述一些还有用,但并非必须的对象。

系统将要发生内存溢出的时候,会对这些对象进行回收,如果这次回收还异常,才会报内存异常。(jdk1.2提供SoftReference来实现软引用)

3:弱引用

它的强度比软引用更弱,它是在垃圾回收开始工作,无论内存是否足够,都会被回收掉。(jdk1.2提供WeakReference来实现)

4:虚引用

是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用,主要是为了能在这个对象被收集器回收时能收到一个系统通知。

finalize()方法

对象可达性分析后发现没有“GC Roots”的引用链后,会被第一次标记为可回收对象,随后会进行一次筛选,筛选此对象是否有必要执行“finalize()”方法,如果有就放置到一个名为 “F-Queue”的队列中,最后由一条JVM创建的、低调度优先级的线程去执行他们的"finalize()"方法,并且这里的执行是触发这个方法开始运行,并不承诺一定等待它运行结束(因为如果某个对象执行缓慢,甚至死循环,会导致FQueue其他对象无法运行,甚至导致整个内存回收子系统的崩溃),并且任何一个对象的finalize()调用只有一次

总之不推荐使用,因为不会管它是否允许结束,很不稳定。

方法区的垃圾

必须同时满足这三个条件,才被允许回收:

1:该类的所有实例已被回收。

2:加载该类的类加载器已被回收。

3:该类对应的java.lang.class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。

在大量反射、动态代理、CGlib等字节码框架,动态生成JSP及OSGi这类频繁自定义类加载器的场景中,需要对类卸载的能力。

常用的垃圾回收器与调优参数

Serial回收器

新生代单线程回收器,在它回收的时候,必须暂停所有工作线程。

-XX:+UseSerialGC

ParNew回收器

新生代的Serial的多线程并行版本

-XX:+UseParNewGC

Parallel Scavenge回收器

新生代,能多线程的收集器,他比其他收集器不同的是, 他目标是达到一个可控的吞吐量的目标。(开发设定一个堆内存大小后,其他的就设置停顿时间和吞吐量大小就好了,其他的都自适应)

-XX:+UseParallelGC -XX:+UseAdaptiveSizePolicy 打开自适应GC调节策略 -XX:MaxGCPauseMills 设置最大垃圾收集停顿时间,会根据这个值调整Java堆大小或者其他一些参数 -XX:GCTimeRatio 设置吞吐量大小,值是0-100的整数,如果设置的值是N,那么系统将花费不超过1/(1+n)的时间用于垃圾收集。

Serial Old回收器

老年代的Serial收集器,标记整理算法。

Parallel Old回收器

Parallel Scavenge的老年代版本(调优参数可参照上面ParallelGC)

-XX:+UseParallelOldGC

CMS回收器

老年代的,以获取最短回收停顿时间为目标的“标记清除”算法收集器。

优点:

并发低停顿收集器。

缺点:

1:产生浮动垃圾。GC的时候由新对象加入。

2:由于是标记清除算法,会产生空间碎片,需要配置处理。

3:会占用一部分线程资源而导致应用程序变慢,CMS默认启动的回收线程数是=(处理器核心线程数+3)/4,也就是25%的影响,如果处理器核心线程数不足4个的时候就对用户程序影响很大了。

-XX:+UseConcMarkSweepGC

-XX:ParallelGCThreads

CMS回收线程=(ParallecGCtThreads+3)/4

-XX:ParallelCMSThreads

CMS垃圾回收线程数

-XX:CMSInitiatingOccupancyFraction

当老年代空间使用率达到多少时,进行一次CMS垃圾回收,默认是68

-XX:+UseCMSCompactAtFullCollection

CMS垃圾回收后,进行一次内存碎片整理

-XX:CMSFullGCsBeforeCompation

多少次CMS回收后,进行一次内存压缩

-XX:CMSClassUnloadingEnabled

使用CMS进行元空间回收

-XX:CMSInitiatingPermOccupancyFraction

达到什么比例进行Perm回收

G1回收器

新型的垃圾回收器类别,思路是

1:优先级式的回收垃圾,而不是 一次性把整个JAVA堆回收干净

2:总体上看是“标记-整理”算法,局部是“标记-复制算法”,总之这两种算法在运行期间都不会产生内存空间碎片。

缺点:

1:G1的卡表更复杂,堆中每个Region都有一份卡表,这导致G1的记忆集占整个堆空间的20%甚至更多。

2:使用写屏障维护G1卡表,会增加更多的运算资源。

-XX:+UseG1GC

-XX:+G1HeapRegionSize

分区大小

-XX:G1NewSizePercent

新生代最小比例,默认5%

-XX:G1MaxNewSizePercent

新生代最大比例,默认60%

-XX:MaxGCPauseMills

最大GC停顿时间,如果任何一次停顿超过这个值,G1就会尝试调整新老年代的比例、调整堆大小、调整景升年龄等首都按,一般建议200-300MS。

-XX:ParallelGCThreads

设置并行回收,GC的工作线程数量=(ParallelGCThreads + 3)/4。

-XX:ConcGCThreads

设置并行回收,GC的工作线程数量。

-XX:InitiatingHeapOccupancyPercent

整个堆使用率达到多少时,执行并发标记周期。

ZGC

它把G1里面的卡表存在引用里面,染色指针。

1:能降低内存的损耗。

2:减少内存屏障的使用数量

3:一个某个region存活对象被移走,这个Region立即就能被释放和重用掉,而不用等待整个堆中指向该Region的引用修正后才能清理,它能"自愈"(并发重分配)。

4:性能,吞吐量目前都优于G1和Parallel Scavenge回收器。

缺点:

因为ZGC没有分代概念,虽然STW很短,但是整个执行过程还是很长,在这个过程中JAVA对象可能会创建大量新对象,这些对象只能变成浮动垃圾,等待下次回收,如果回收掉的空间持续小于产生的浮动垃圾,那可分配的空间会越来越少(似乎还是要分代?),目前的方法是尽可能增大堆空间。否则会FULLGC

-XX:+UseZGC

-XX:ZFramentationLimit

设置ZGC触发的堆碎片阈值

-XX:ZCollectionInterval

指定ZGC的回收间隔(单位:秒),有助于控制回收频率。

-XX:+UseNUMA

启用NUMA支持

-XX:ZAllocationSpikeTolerance

控制ZGC对内存分配突发情况的容忍度,数值越高,适应能力越强

其他JVM参数

常用调优参数

  • -Xmn -Xms -Xmx -Xss 年轻代 最小堆 最大堆 栈空间
  • -XX:+UseTLAB 使用TLAB,默认打开
  • -XX:+PrintTLAB 打印TLAB的使用情况
  • -XX:TLABSize 设置TLAB大小(TLAB (Thread Local Allocation Buffer,线程本地分配缓冲区)是 Java 中内存分配的一个概念,它是在 Java 堆中划分出来的针对每个线程的内存区域,专门在该区域为该线程创建的对象分配内存。它的主要目的是在多线程并发环境下需要进行内存分配的时候,减少线程之间对于内存分配区域的竞争,加速内存分配的速度。TLAB 本质上还是在 Java 堆中的,因此在 TLAB 区域的对象,也可以被其他线程访问。)
  • -XX:+DisableExplictGC System.gc()不管用 ,FGC
  • -XX:+PrintGC
  • -XX:+PrintGCDetails
  • -XX:+PrintHeapAtGC
  • -XX:+PrintGCTimeStamps
  • -XX:+PrintGCApplicationConcurrentTime (低) 打印应用程序时间
  • -XX:+PrintGCApplicationStoppedTime (低) 打印暂停时长
  • -XX:+PrintReferenceGC (重要性低) 记录回收了多少种不同引用类型的引用
  • -verbose:class 类加载详细过程
  • -XX:+PrintVMOptions
  • -XX:+PrintFlagsFinal -XX:+PrintFlagsInitial 必须会用
  • -Xloggc:opt/log/gc.log
  • -XX:MaxTenuringThreshold 升代年龄,最大值15
  • 锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold 逃逸分析 标量替换 ... 这些不建议设置

跟踪 GC 日志

  • -XX:+PrintGC:最简单的 GC 参数,每一行代表进行了一次 GC。
  • -XX:+PrintGCDetails:最常用的 GC 参数,可以在日志中打印 GC 详细信息。
  • -XX:+PrintHeapAtGC:详细的打印出 GC 前后的堆内存信息。
  • -XX:+PrintGCTimeStamps:输出每次 GC 发生的时间
  • -XX:+PrintGCApplicationConcurrentTime:记录 Java 程序执行时间,表示应用程序在两次垃圾回收之间运行多长时间
  • -XX:+PrintGCApplicationStoppedTime:记录 Java 程序因为 GC 而产生的停顿时间

跟踪类加载/卸载信息

  • -verbose:class:跟踪类加载和卸载

跟踪查看虚拟机参数

  • -XX:+PrintVMOptions:查看 Java 程序启动时设置的参数。

  • -XX:+PrintCommandLineFlags:打印虚拟机的显示和隐藏参数。

  • -XX:+PrintFlagsFinal:打印所有系统参数的值。