JVM层GC调优

1. JVM的内存结构

在这里插入图片描述

JVM堆内存和非堆内存

  • Matespace:Class、Package、Method、Field、字节码、常量池、符号引用
  • CSS:默认1G;短指针,32位指针的Class,使用-XX:+UseCompressedClassPointers启用;(只有在64位server模式下的JDK才有该参数,此处使用32位)

-server -Xms128M -Xint -Xmx128M -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=128M

使用jstat -gc PID查看GC信息

 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
4352.0 4352.0 1968.1  0.0   34944.0   6660.2   87424.0    16777.5   22808.0 22026.6  0.0    0.0       16    0.224   0      0.000    0.224

可以看出CCSC和CCSU都是0,则说明未开启压缩类指针。

  • CodeCache:JIT编译后的本地(native)代码、JNI使用的C代码
[-Xcomp 完全以编译的方式来执行(执行第一次编译成native代码)]
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
4352.0 4352.0  0.0   2305.9 34944.0  18887.7   87424.0    17132.0   27928.0 27234.4  0.0    0.0       15    0.238   0      0.000    0.238

[-Xint 完全以解释方式来执行(不生成JIT代码)]
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
4352.0 4352.0  0.0   2727.7 34944.0  23833.3   87424.0    15767.5   20888.0 20034.7  0.0    0.0       17    0.155   0      0.000    0.155

-Xint的MC(Metaspace capacity)区域比-Xcomp的少了近7M,这7M就是CodeCache

  • S0和S1大小相等, 也就是所谓的from区和to区,同一个时间点只有一个区域有数据。

1.1 运行时数据区

        在程序执行的时候,JVM定义不同的运行时数据区。有一些在JVM启动的时候创建,并于虚拟机销毁的时候退出。其他数据区域是每个线程。每个线程的数据区域在创建线程时创建,在线程退出时销毁。

1.1.1 方法区

        与Java堆一样,是各个线程 共享 的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范将方法区描述为堆的一个逻辑部分,但是它有一个别名叫做 Non-Heap(f非堆) ,目的是与Java堆区分开来。(JDK8中的matespace区)

1.1.1.1 常量池(Run-Time Constant Pool)

        是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译器生成的各种字面量(字面量,常量和变量之间的区别?)和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

1.1.2 堆

        是Java虚拟机所管理的内存中最大的一块。堆是被所有 线程共享 的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是 存放对象实例,几乎所有的对象实例都在这里分配内存。
        Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。

1.1.3 虚拟机栈

        是 线程私有 的,它的生命周期与线程相同。虚拟机栈描述的是 Java方法执行的内存模型:每个方法在执行的时候都会创建一个 栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。
        

1.1.4 程序计数器

        JVM支持多线程同时访问,每一个线程都有自己的PC Register(PC寄存器),线程正在执行的方法叫做当前方法,如果是java代码,PC Register里面存放的就是当前正在执行的指令的地址,如果是C代码,则为空。

1.1.5 本地方法栈

        与虚拟机栈所发挥的作用非常相似,他们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。

1.2 常用参数

  • -XX:NewSize -XX:MaxNewSize

新生代(Young区)的大小;

  • -XX:NewRatio

new 区和Old区的比例

  • -XX:SurvivorRatio

Eden区和Survivor区的比例

  • -XX:MataspaceSize-XX:MaxMataspaceSize

  • -XX:+UseCompressedClassPointers

  • -XX:CompressedClassSpaceSize

  • -XX:InitialCodeCacheSize

  • -XX:ReservedCodeCacheSize:CodeCache最大的Size

2. 垃圾回收算法

2.1 思想

        枚举根节点,做可达性分析
        根节点: 类加载器、Thread、虚拟机栈的本地变量表、static成员、常量引用、本地方法栈的变量等等

2.2 标记清除

2.2.1 算法

        算法分为"标记"和"清除"两个阶段:首先标记所有需要回收的对象,在标记完成后统一回收。

2.2.2 缺点

        效率不高。标记和清除两个过程的效率都不高;
        产生碎片。碎片太多都会导致提前GC。

2.3 复制

2.3.1 算法

        它将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把使用过的内存空间一次清理掉。

2.3.2 优缺点

        实现简单,运行高效,但是空间利用率低。

2.4 标记整理

2.4.1 算法

        标记过程任然与"标记-清除"算法一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

2.4.2 优缺点

        没有了内存碎片,但是整理内存比较耗时。

2.5 分代垃圾回收

  • Young区采用复制算法
  • Old区采用标记清除或标记整理

2.6 对象分配

  • 对象优先分配在Eden区分配
  • 大对象直接进入老年代:-XX:PretenureSizeThreshold(通过该参数设置)
  • 长期存活对象进入老年代:-XX:MaxTenuringThreshold-XX:+PrintTenuringDistribution-XX:TargetSurvivorRatio

3. 垃圾收集器

        评估垃圾回收器:吞吐量最大,停顿时间最小。

3.1 串行收集器Serial:SerialSerial Old

3.1.1 开启

-XX:+UseSerialGC -XX:+UseSerialOldGC

3.2 并行收集器Parallel:Parallel ScavengeParallel Old吞吐量优先

        并行: 指多条垃圾收集线程并行工作,但是此时用户线程仍处于等待状态。适合科学计算、后台处理等弱交互场景。

        吞吐量:花在垃圾收集的时间和花在应用时间的占比。-XX:GCTimeRatio=<n>,垃圾收集时间占:1/1+n

3.2.1 开启

-XX:+UseParallerGC,-XX:+UseParallerOldGC

        Server模式下的默认收集器。

3.3 并发收集器Concurrent:CMSG1,停顿时间优先

        并发 :指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),垃圾收集线程在执行的时候不会停顿用户程序的运行。适合对响应时间有要求的场景,比如Web。

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

3.3.1 开启

CMS:	-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
G1:	-XX:+UseG1GC

在这里插入图片描述

3.4 如何选择垃圾收集器

  • 优先调整堆的大小让服务器自己来选择
  • 如果内存小于100M,使用串行收集器
  • 如果是单核,并且没有停顿时间的要求,串行或者JVM自己选
  • 如果允许停顿时间超过1秒,选择并行或者JVM自己选
  • 如果响应时间最重要,并且不超过1秒,使用并发收集器

GC调优指南
如何选择垃圾收集器

G1最佳实践

G1 GC的一些关键技术

3.5 垃圾收集器

3.5.1 Parallel Collector

        当内存不够时,暂停应用程序,启动多个垃圾回收线程来进行垃圾回收,回收完之后再启动应用程序。

-XX:+UseParallelGC手动开启,Server默认开启

-XX:ParallelGCThreads=<N>多少个GC线程
CPU > 8 N = 5/8
CPU < 8 N = CPU

并行垃圾收集器有一个特点:自适应

-XX:MaxGCPauseMillis=<N>
-XX:GCTimeRatio =<N>
-Xmx<N>

动态内存调整

-XX:YoungGenerationSizeIncrement=<Y> #默认值是20%
-XX:TenuredGenerationSizeIncrement=<T> #old区
-XX:AdaptiveSizeDecrementScaleFactor=<D>

3.5.2 CMS Collector(并发收集器)

        低停顿、低延迟

3.5.2.1 CMS垃圾收集过程
  • CMS initial mark:初始化标记Root,STW(stop the world:停止应用程序)
  • CMS concurrent mark:并发标记
  • CMS-concurrent-preclean:并发预清理
  • CMS remark:重新标记,STW
  • CMS concurrent sweep:并发清除
  • CMS-concurrent-reset:并发重置
3.5.2.2 缺点
  • CPU敏感
  • 浮动垃圾:在垃圾回收的同时,应用程序会分配内存
  • 空间碎片
3.5.2.3 CMS的相关参数

-XX:ConcGCThreads:并发的GC线程数

-XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩
-XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次

-XX:CMSInitiatingOccupancyFraction:触发FullGC

-XX:+UseCMSInitiatingOccupancyOnly:是否是动态可调

-XX:+CMSScavengeBeforeRemark:FullGC之前先做YGC

-XX+CMSClassUnloadingEnabled:启用回收Perm区

3.5.3 G1 Collector

3.5.3.1 简介

        garbage - first (G1)垃圾收集器是一种服务器风格的垃圾收集器,目标是具有大内存的多处理器机器。它试图以高概率满足垃圾收集(GC)暂停时间目标,同时实现高吞吐量。整个堆操作,例如全局标记,是与应用程序线程并发执行的。这可以防止与堆或实时数据大小成比例的中断。
        G1的第一个重点是为运行需要大堆且GC延迟有限的应用程序的用户提供解决方案。这意味着堆大小约为6gb或更大,稳定且可预测的暂停时间低于0.5秒。

3.5.3.2 用途

        新生代和老年代收集器

3.5.3.3 G1的几个概念

Region:堆被划分为一组大小相同的堆区域,每个区域都是连续的虚拟内存范围。

SATB:Snapshot-At-The-Beginning,它是通过Root Tracing得到的,GC开始时候存活对象的快照。

RSet:记录了其它Region中的对象引用本Region中对象的关系,属于points-into结构(谁引用了我的对象)

3.5.3.4 YoungGC
  • 新对象进入Eden区
  • 存活对象拷贝到Survivor区
  • 存活时间达到年龄阈值时,对象晋升到Old区
3.5.3.5 MixedGC
  • 不是FullGC,回收所有的Young和部分Old
  • global concurrent marking(全局并发标记)
    • Initial marking phase:标记GC Root,STW
    • Root region scanning phase:标记存活Region
    • Concurrent marking phase[阶段]:标记存活的对象
    • Remark phase:重新标记,STW
    • Cleanup phase:部分STW
3.5.3.6 MixedGC时机

InitiatingHeapOccupancyPercent:堆占有率达到这个数值则触发global concurrent marking,默认45%

G1HeapWastePercent: 在global concurrent marking结束之后,可以知道区有多少空间要被回收,在每次YGC之后和再次发生MixedGC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会发生MixedGC

G1MixedGCLiveThresholdPercent:Old区region被回收时候的存活对象占比。

G1MixedGCCountTarget:一次global concurrent marking之后,最多执行Mixed GC的次数。

G1OldCSetRegionThresholdPrecent:一次Mixed GC中能被选入CSet的最多Old区的region数量

3.5.3.7 常用参数

-XX:+UseG1GC开启

-XX:G1HeapRegionSize=n,region的大小,1-32M,2048个。

-XX:MaxGCPauseMillis=200最大停顿时间

-XX:G1NewSizePercent-XX:G1MaxNewSizePercent

-XX:G1ReservePercent=10保留防止to space溢出

-XX:ParallelGCThreads=nSWT线程数

-XX:ConcGCThreads=n并发线程数=1/4*并行

3.5.3.8 最佳实践

年轻代大小:避免-Xmn、-XX:NewRatio等显式设置Young区大小,会覆盖暂停时间目标。

暂停时间目标:暂停时间不要太严苛,其吞吐量目标是90%的应用程序时间和10%的垃圾回收时间,太严苛会直接影响到吞吐量。

3.5.3.9 是否需要切换到G1
  • 50%以上的堆被存活对象占用
  • 对象分配和晋升的速度变化非常大
  • 垃圾回收时间特别长,超过了1秒

4. 可视化GC日志分析工具

4.1 工具

4.2 打印日志相关参数(bin/catalina.sh)

  • -XX:+PrintGCDetails
  • -XX:+PrintGCTimeStamps
  • -XX:+PrintGCDateStamps
  • -Xloggc:$CATALINE_HOME/logs/gc.log
  • -XX:+PrintHeapAtGC
  • -XX:+PrintTenuringDistribution:young区年龄分布信息

5. Tomcat 的GC调优实战

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值