写点什么

🏆「作者推荐」【JVM 性能调优】JVM 分析与调优技巧分析(原理篇)

发布于: 2 小时前
🏆「作者推荐」【JVM 性能调优】JVM分析与调优技巧分析(原理篇)

JVM 空间说明

  • 在 JDK1.7 及以前,HotSpot 虚拟机将 java 类信息、常量池、静态变量、即时编译器编译后的代码等数据,存储在 Perm(永久带)里(对于其他虚拟机如 BEA JRockit、IBM J9 等是不存在永久带概念的),类的元数据和静态变量在类加载的时候被分配到 Perm 里,当常量池回收或者类被卸载的时候,垃圾收集器会回收这一部分内存,但效果不太理想。



  • JDK1.8 时,HotSpot 虚拟机对 JVM 模型进行了改造,将类元数据放到了本地内存中,将常量池和静态变量放到了 Java 堆里,HotSpot VM 将会为类的元数据明确的分配与释放本地内存,在这种架构下,类元数据就突破了-XX:MaxPermSize 的限制,所以此配置已经失效,现在可以使用更多的本地内存。这样一定程度上解决了原来在运行时生成大量的类,从而经常 Full GC 的问题——如运行时使用反射、代理等。

干货要点

可以发现最明显的一个变化是元空间从虚拟机转移到了本地内存。默认情况下,元数据空间大小仅受限于本地内存,这意味着以后不会因为永久代大小不够而抛出 OOM 异常了。


jdk1.8 以前,HotSpot VM 将 class 和类的 jar 包数据存储在 PermGen 里,PermGen 大小是固定的,而且项目之间无法公用公有的 class,所以很容易碰到 OOM 异常。改成 MetaSpace 后。


各个项目会共享同样的 class 空间。比如多个项目都引用了 apache-common 包,在 MetaSpace 中只会存储一份的 apache-common 的 class,提高了内存的利用率,垃圾回收更有效。

Minor GC—复制算法具体过程:

  • 将 Eden 和 S0 中还存活着的对象一次性的复制到 S1 中,并且清理掉 Eden 与 S0 的空间。如果 S1 放不下还存活着的对象,那这些对象将通过分配担保机制进入老年代。【原理上随时保持 S0 和 S1 有一个是空的,用来存下一次的对象】

  • Eden 区快满的时候,会进行上一步类似操作,将 Eden 和 S1 区的年纪大的对象放到 S0 区【此时 S1 区就是空的】

  • 直到 Eden 区快满,S0 或者 S1 也快满的时候,这时候就把这两个区的年纪大的对象放到 Old 区。

  • 依次循环,直到 Old 区也快满的时候,Eden 区也快满的时候,会对整个这一块内存区域进行一次大清洗(FullGC),腾出内存,为之后的对象创建,程序运行腾地方。




  • 新生代 GC(Minor GC):指发生在新生代的垃圾回收动作,因为 java 对象大多具备朝生夕灭的特征,所以 Minor GC 发生的特别频繁,一般回收速度也很快。

  • 老年代 GC(Major GC/Full GC):指发生在老年代的 GC,出现了 Major GC,至少会伴随一次的 MinorGC(但非绝对,在 Parallel Scavenge 收集器的收集策略里就有直接进行 Minor GC 的策略选择过程)。

  • Major GC 的速度一般比 Minor GC 慢 10 倍以上。




升级 JDK1.8 之后,上面的 perm 配置已经变成


-XX:MetaspaceSize=512M XX:MaxMetaspaceSize=1024M
复制代码


MetaspaceSize 如果不做配置,通过 jinfo 查看默认 MetaspaceSize 大小(约 21M),MaxMetaspaceSize 很大很大,前面说过 MetaSpace 只受本地内存大小限制。


结果为:-XX:MetaspaceSize=21807104jinfo -flag MetaspaceSize 1234
结果为:-XX:MaxMetaspaceSize=18446744073709547520jinfo -flag MaxMetaspaceSize 1234
复制代码


干货: MetaspaceSize 为触发 FullGC 的阈值,默认约为 21M,如做了配置,最小阈值为自定义配置大小。空间使用达到阈值,触发 FullGC,同时对该值扩大。当然如果元空间实际使用小于阈值,在 GC 的时候也会对该值缩小。MaxMetaspaceSize 为元空间的最大值,如果设置太小,可能会导致频繁 FullGC,甚至 OOM。




-XX 参数被称为不稳定参数,之所以这么叫是因为此类参数的设置很容易引起 JVM 性能上的差异,使 JVM 存在极大的不稳定性。如果此类参数设置合理将大大提高 JVM 的性能及稳定性。

不稳定参数语法规则:

布尔类型参数值

  • -XX:+ '+'表示启用该选项

  • -XX:- '-'表示关闭该选项

数字类型参数值:

-XX:= 给选项设置一个数字类型值,可跟随单位,例如:'m’或’M’表示兆字节;'k’或’K’千字节;'g’或’G’千兆字节。32K 与 32768 是相同大小的。

字符串类型参数值:

-XX:= 给选项设置一个字符串类型值,通常用于指定一个文件、路径或一系列命令列表。


-XX:HeapDumpPath=./dump.core
复制代码

JVM 参数示例

-Xmx4g –Xms4g –Xmn1200m –Xss512k -XX:NewRatio=4 -XX:SurvivorRatio=8 -XX:PermSize=100m -XX:MaxPermSize=256m -XX:MaxTenuringThreshold=15
复制代码

解析:

  • -Xmx4g:堆内存最大值为 4GB。

  • -Xms4g:初始化堆内存大小为 4GB 。

  • -Xmn1200m:设置年轻代大小为 1200MB。增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun 官方推荐配置为整个堆的 3/8。

  • -Xss512k:设置每个线程的堆栈大小。JDK5.0 以后每个线程堆栈大小为 1MB,以前每个线程堆栈大小为 256K。应根据应用线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在 3000~5000 左右。

  • -XX:NewRatio=4:设置年轻代(包括 Eden 和两个 Survivor 区)与年老代的比值(除去持久代)。设置为 4,则年轻代与年老代所占比值为 1:4,年轻代占整个堆栈的 1/5

  • -XX:SurvivorRatio=8:设置年轻代中 Eden 区与 Survivor 区的大小比值。设置为 8,则两个 Survivor 区与一个 Eden 区的比值为 2:8,一个 Survivor 区占整个年轻代的 1/10

  • -XX:PermSize=100m:初始化永久代大小为 100MB。

  • -XX:MaxPermSize=256m:设置持久代大小为 256MB。

  • -XX:MaxTenuringThreshold=15:设置垃圾最大年龄。如果设置为 0 的话,则年轻代对象不经过 Survivor 区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在 Survivor 区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。

JVM 调优目标

何时需要做 jvm 调优?


heap 内存(老年代)持续上涨达到设置的最大内存值;Full GC 次数频繁;GC 停顿时间过长(超过 1 秒);应用出现 OutOfMemory 等内存异常;应用中有使用本地缓存且占用大量内存空间;系统吞吐量与响应性能不高或下降。JVM 调优原则


  1. 多数的 Java 应用不需要在服务器上进行 JVM 优化;

  2. 多数导致 GC 问题的 Java 应用,都不是因为我们参数设置错误,而是代码问题;

  3. 在应用上线之前,先考虑将机器的 JVM 参数设置到最优(最适合);

  4. 减少创建对象的数量;

  5. 减少使用全局变量和大对象;

  6. JVM 优化是到最后不得已才采用的手段;

  7. 在实际使用中,分析 GC 情况优化代码比优化 JVM 参数更好;

JVM 调优目标

  • GC 低停顿;

  • GC 低频率;

  • 低内存占用;

  • 高吞吐量;

JVM 调优量化目标(示例):

  1. Heap 内存使用率 <= 70%;

  2. Old generation 内存使用率<= 70%;

  3. avgpause <= 1 秒;

  4. Full gc 次数 0 或 avg pause interval >= 24 小时 ;


注意:不同应用,其 JVM 调优量化目标是不一样的。

JVM 调优经验

JVM 调优经验总结

JVM 调优的一般步骤为:
  • 第 1 步:分析 GC 日志及 dump 文件,判断是否需要优化,确定瓶颈问题点;

  • 第 2 步:确定 JVM 调优量化目标;

  • 第 3 步:确定 JVM 调优参数(根据历史 JVM 参数来调整);

  • 第 4 步:调优一台服务器,对比观察调优前后的差异;

  • 第 5 步:不断的分析和调整,直到找到合适的 JVM 参数配置;

  • 第 6 步:找到最合适的参数,将这些参数应用到所有服务器,并进行后续跟踪。

JVM 调优重要参数解析

注意:不同应用,其 JVM 最佳稳定参数配置是不一样的。

配置:

-server
-Xms12g -Xmx12g -XX:PermSize=500m -XX:MaxPermSize=1000m -Xmn2400m -XX:SurvivorRatio=1 -Xss512k -XX:MaxDirectMemorySize=1G
-XX:+DisableExplicitGC -XX:CompileThreshold=8000 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+UseCompressedOops -XX:CMSInitiatingOccupancyFraction=60 -XX:ConcGCThreads=4
-XX:MaxTenuringThreshold=10 -XX:ParallelGCThreads=8
-XX:+ParallelRefProcEnabled -XX:+CMSClassUnloadingEnabled -XX:+CMSParallelRemarkEnabled
-XX:CMSMaxAbortablePrecleanTime=500 -XX:CMSFullGCsBeforeCompaction=4
XX:+UseCMSInitiatingOccupancyOnly -XX:+UseCMSCompactAtFullCollection
-XX:+HeapDumpOnOutOfMemoryError -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/weblogic/gc/gc_$$.log
复制代码


重要参数(可调优)解析:


-Xms12g:初始化堆内存大小为12GB。
-Xmx12g:堆内存最大值为12GB 。
-Xmn2400m:新生代大小为2400MB,包括 Eden区与2个Survivor区。
-XX:SurvivorRatio=1:Eden区与一个Survivor区比值为1:1。
-XX:MaxDirectMemorySize=1G:直接内存。报java.lang.OutOfMemoryError: Direct buffer memory 异常可以上调这个值。
-XX:+DisableExplicitGC:禁止运行期显式地调用 System.gc() 来触发fulll GC。
注意: Java RMI的定时GC触发机制可通过配置-Dsun.rmi.dgc.server.gcInterval=86400来控制触发的时间。
-XX:CMSInitiatingOccupancyFraction=60:老年代内存回收阈值,默认值为68。
-XX:ConcGCThreads=4:CMS垃圾回收器并行线程线,推荐值为CPU核心数。
-XX:ParallelGCThreads=8:新生代并行收集器的线程数。
-XX:MaxTenuringThreshold=10:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
-XX:CMSFullGCsBeforeCompaction=4:指定进行多少次fullGC之后,进行tenured区 内存空间压缩。
-XX:CMSMaxAbortablePrecleanTime=500:当abortable-preclean预清理阶段执行达到这个时间时就会结束。
复制代码

触发 Full GC 的场景及应对策略

年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC,对老年代 GC 称为 MajorGC,而 Full GC 是对整个堆来说的,在最近几个版本的 JDK 里默认包括了对永生带即方法区的回收(JDK8 中无永生带了),出现 Full GC 的时候经常伴随至少一次的 Minor GC,但非绝对的。MajorGC 的速度一般会比 Minor GC 慢 10 倍以上。


触发 Full GC 的场景及应对策略:


  1. System.gc()方法的调用,应对策略:通过-XX:+DisableExplicitGC 来禁止调用 System.gc ;

  2. 老年代代空间不足,应对策略:让对象在 Minor GC 阶段被回收,让对象在新生代多存活一段时间,不要创建过大的对象及数组;

  3. 永生区空间不足,应对策略:增大 PermGen 空间

  4. GC 时出现 promotionfailed 和 concurrent mode failure,应对策略:增大 survivor space

  5. Minor GC 后晋升到旧生代的对象大小大于老年代的剩余空间,应对策略:增大 Tenured space 或下调 CMSInitiatingOccupancyFraction=60

  6. 内存持续增涨达到上限导致 Full GC ,应对策略:通过 dumpheap 分析是否存在内存泄漏

Gc 日志分析工具

借助 GCViewer 日志分析工具,可以非常直观地分析出待调优点。

可从以下几方面来分析:

  1. Memory,分析 Totalheap、Tenuredheap、Youngheap 内存占用率及其他指标,理论上内存占用率越小越好;

  2. Pause ,分析 Gc pause、Fullgc pause、Total pause 三个大项中各指标,理论上 GC 次数越少越好,GC 时长越小越好;

发布于: 2 小时前阅读数: 3
用户头像

🏆2021年InfoQ写作平台-签约作者 🏆 2020.03.25 加入

👑【酷爱计算机技术、醉心开发编程、喜爱健身运动、热衷悬疑推理的”极客狂人“】 🏅 【Java技术领域,MySQL技术领域,APM全链路追踪技术及微服务、分布式方向的技术体系等】 我们始于迷惘,终于更高水平的迷惘

评论

发布
暂无评论
🏆「作者推荐」【JVM 性能调优】JVM分析与调优技巧分析(原理篇)