JVM-G1 性能调优思路与实战

现在大多数公司的Java生产版本都是再JDK8,所以本次性能调优主要针对G1来展开

GC的主要回收区域就是年轻代(young gen)、老年代(tenured gen)、持久区(perm gen),在jdk8之后,perm gen消失,被替换成了元空间(Metaspace),元空间会在普通的堆区进行分配。垃圾收集为了提高效率,采用分代收集的方式,对于不同特点的回收区域使用不同的垃圾收集器。系统正常运行情况young是比较频繁的,full gc会触发整个heap的扫描和回收。在G1垃圾收集器中,最好的优化状态就是通过不断调整分区空间,避免进行full gc,可以大幅提高吞吐量

在说如何优化前需要先了解G1是怎么工作的~~~

垃圾回收器在执行回收时的步骤有2步:

  1. 查找内存中不再使用的对象
  2. 释放这些对象占用的内存

如何判断哪些对象不再被使用呢?

  1. 引用计数法

引用计数法就是如果一个对象没有被任何引用指向,则可视之为垃圾。这种方法的缺点就是不能检测到环的存在。

    2.  根搜索算法

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

开启使用G1

-XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=200

G1的特性

G1将新生代,老年代的物理空间划分取消了

JVM-G1 性能调优思路与实战_第1张图片

取而代之的是,G1算法将堆划分为若干个区域(Region),它仍然属于分代收集器。不过,这些区域的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。老年代也分成很多区域,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了。

JVM-G1 性能调优思路与实战_第2张图片

在G1中,还有一种特殊的区域,叫Humongous区域。 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。

对象分配策略

说起大对象的分配,我们不得不谈谈对象的分配策略。它分为3个阶段:

  1. TLAB(Thread Local Allocation Buffer)线程本地分配缓冲区
  2. Eden区中分配
  3. Humongous区分配

TLAB为线程本地分配缓冲区,它的目的为了使对象尽可能快的分配出来。如果对象在一个共享的空间中分配,我们需要采用一些同步机制来管理这些空间内的空闲空间指针。在Eden空间中,每一个线程都有一个固定的分区用于分配对象,即一个TLAB。分配对象时,线程之间不再需要进行任何的同步。

对TLAB空间中无法分配的对象,JVM会尝试在Eden空间中进行分配。如果Eden空间无法容纳该对象,就只能在老年代中进行分配空间。

G1 Young GC

Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。在这种情况下,Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间。Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。最终Eden空间的数据为空,GC停止工作,应用线程继续执行

JVM-G1 性能调优思路与实战_第3张图片

JVM-G1 性能调优思路与实战_第4张图片

Rset概念

如果仅仅GC 新生代对象,我们如何找到所有的根对象呢? 老年代的所有对象都是根么?那这样扫描下来会耗费大量的时间。于是,G1引进了RSet的概念。它的全称是Remembered Set,作用是跟踪指向某个heap区内的对象引用。

JVM-G1 性能调优思路与实战_第5张图片

在CMS中,也有RSet的概念,在老年代中有一块区域用来记录指向新生代的引用。这是一种point-out,在进行Young GC时,扫描根时,仅仅需要扫描这一块区域,而不需要扫描整个老年代。

但在G1中,并没有使用point-out,这是由于一个分区太小,分区数量太多,如果是用point-out的话,会造成大量的扫描浪费,有些根本不需要GC的分区引用也扫描了。于是G1中使用point-in来解决。point-in的意思是哪些分区引用了当前分区中的对象。这样,仅仅将这些对象当做根来扫描就避免了无效的扫描。由于新生代有多个,那么我们需要在新生代之间记录引用吗?这是不必要的,原因在于每次GC时,所有新生代都会被扫描,所以只需要记录老年代到新生代之间的引用即可。

如果引用的对象很多,赋值器需要对每个引用做处理,赋值器开销会很大,为了解决赋值器开销这个问题,在G1 中又引入了另外一个概念,卡表(Card Table)。一个Card Table将一个分区在逻辑上划分为固定大小的连续区域,每个区域称之为卡。卡通常较小,介于128到512字节之间。Card Table通常为字节数组,由Card的索引(即数组下标)来标识每个分区的空间地址。默认情况下,每个卡都未被引用。当一个地址空间被引用时,这个地址空间对应的数组索引的值被标记为”0″,即标记为脏被引用,此外RSet也将这个数组下标记录下来。一般情况下,这个RSet其实是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。

Young GC 阶段:

  • 阶段1:根扫描
    静态和本地对象被扫描
  • 阶段2:更新RS
    处理dirty card队列更新RS
  • 阶段3:处理RS
    检测从年轻代指向年老代的对象
  • 阶段4:对象拷贝
    拷贝存活的对象到survivor/old区域
  • 阶段5:处理引用队列
    软引用,弱引用,虚引用处理

JVM调优主要就是调整以下两个指标

停顿时间:垃圾收集器做垃圾回收中断应用执行的时间

吞吐量:垃圾收集的时间和总时间的占比: 1/(1+n) ;吞吐量为 1-1/(1+n); -XX:GCTimeRatio=99

GC调优的步骤

-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -xloggc:./gc.log  (打印gc日志)

-XX:G1HeapRegionSize=n (设置的 G1 区域的大小。值是 2 的幂,范围是 1 MB 到 32 MB 之间。目标是根据最小的 Java 堆大小划分出约 2048 个区域)

-XX:ParallelGCThreads=n(设置 STW 工作线程数的值。将 n 的值设置为逻辑处理器的数量。n 的值与逻辑处理器的数量相同,最多为 8。如果逻辑处理器不止八个,则将 n 的值设置为逻辑处理器数的 5/8 左右。这适用于大多数情况,除非是较大的 SPARC 系统,其中 n 的值可以是逻辑处理器数的 5/16 左右。)

-XX:ConcGCThreads=n(设置并行标记的线程数。将 n 设置为并行垃圾回收线程数 (ParallelGCThreads) 的 1/4 左右。)

-XX:InitiatingHeapOccupancyPercent=45(设置触发标记周期的 Java 堆占用率阈值。默认占用率是整个 Java 堆的 45%。)

避免使用以下参数:

避免使用 -Xmn 选项或 -XX:NewRatio 等其他相关选项显式设置年轻代大小。固定年轻代的大小会覆盖暂停时间目标。

触发Full GC

在某些情况下,G1触发了Full GC,这时G1会退化使用Serial收集器来完成垃圾的清理工作,它仅仅使用单线程来完成GC工作,GC暂停时间将达到秒级别的。整个应用处于假死状态,不能处理任何请求,我们的程序当然不希望看到这些。那么发生Full GC的情况有哪些呢?

  • 并发模式失败

G1启动标记周期,但在Mix GC之前,老年代就被填满,这时候G1会放弃标记周期。这种情形下,需要增加堆大小,或者调整周期(例如增加线程数-XX:ConcGCThreads等)。

  • 晋升失败或者疏散失败

G1在进行GC的时候没有足够的内存供存活对象或晋升对象使用,由此触发了Full GC。可以在日志中看到(to-space exhausted)或者(to-space overflow)。解决这种问题的方式是:

a,增加 -XX:G1ReservePercent 选项的值(并相应增加总的堆大小),为“目标空间”增加预留内存量。

b,通过减少 -XX:InitiatingHeapOccupancyPercent 提前启动标记周期。

c,也可以通过增加 -XX:ConcGCThreads 选项的值来增加并行标记线程的数目。

  • 巨型对象分配失败

当巨型对象找不到合适的空间进行分配时,就会启动Full GC,来释放空间。这种情况下,应该避免分配大量的巨型对象,增加内存或者增大-XX:G1HeapRegionSize,使巨型对象不再是巨型对象。

 

 

 

 

你可能感兴趣的:(后端技术,应用服务器技术,文档类,G1,性能调优,G1,性能调优实战,G1,性能调优分析,JVM性能调优)