0%

JVM基础大全-调优篇

一、JVM调优

性能调优包含多个方面:架构调优、代码调优、JVM调优、数据库调优、操作系统调优等。其中架构调优和代码调优是基础,架构的调优对系统影响最大。性能调优基本步骤:

  1. 明确优化目标
  2. 发现性能瓶颈
  3. 性能调优
  4. 监控及数据统计
  5. 确认是否达到目标

1.1 调优时机

  • 应用系统慢(响应性能下降,吞吐量下降)、卡顿(GC停顿时间长、次数频繁)
  • 应用出现OOM等内存异常(使用的堆内存过大、本地缓存过大;会发生OOM的区域:堆、元空间、虚拟机栈、本地方法栈、直接内存)

1.2 调优原则

JVM调优是一种手段,但并不一定所有问题都需要通过JVM调优解决,最有效的优化手段是架构和代码层面的优化。所以JVM优化是最后不得已的手段,在架构调优和代码调优后对服务器配置的最后一次”压榨”。

所以JVM调优应遵守的原则:

  • 上线之前应先将机器的JVM参数设置到最优;
  • 大多数的Java应用不需要进行JVM优化;
  • 大多数导致GC问题的原因是代码层面的问题导致的(代码层面);
  • 减少创建对象的数量、减少使用全局变量和大对象(代码层面);
  • 优先架构调优和代码调优,JVM优化是不得已的手段(代码、架构层面);
  • 分析GC情况优化代码比优化JVM参数更好(代码层面);

1.3 调优目的

  1. 吞吐量:用户代码运行时间 / (用户代码运行时间 + GC时间)
  2. 响应时间:STW越短,响应时间越好

二、JVM命令参数

  • 标准参数:- 开头,所有的Hot Spot都支持;
  • 非标准参数:-X 开头,特定版本的Hot Spot支持的特定命令;
  • 不稳定参数:-XX 开头,下个版本可能取消;

2.1 常用参数

  • 标准参数:- 开头,所有的Hot Spot都支持;
  • 非标准参数:-X 开头,特定版本的Hot Spot支持的特定命令;
  • 不稳定参数:-XX 开头,下个版本可能取消;

输入:Java -XX:+PrintFlagsInitial 查看默认参数及值

输入:Java -XX:+PrintFlagsFinal 查看最终生效参数及值

输入:Java -XX:+PrintCommandLineFlags 查看启动时的命令行参数

2.2 GC常用参数

  • -Xms:设置堆的初识内存大小,包含年轻代和老年代,JVM内存设置默认单位为Byte,也可以用k/K、m/M、g/G来声明其他单位。
  • -Xmx:设置堆的最大内存大小,-Xms和-Xmx一样时,可以避免内存不够时动态调整内存带来的内存波动
  • -Xmn:设置年轻代大小,包括Eden区和
  • -Xss:设置线程最大栈空间,JDK5以后每个线程堆栈大小为1M,直接决定了函数可调用的最大深度。在相同物理内存下,减小这个值能生成更多的线程,但一个进程内的线程是有限的,也不是越多效率越高,经验值在3000~5000左右。
  • -XX:MetaspaceSize:设置方法区(元空间)初始值,可动态扩展,如果没有设置元空间的上限那么他可以扩大到整个内存。64位的JVM元空间默认大小是21M,达到该值就会FGC,同时收集器会对该值进行调整,如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,提升该值,但最到不超过-XX:MaxMetaspaceSize设置的值。
  • -XX:MaxMetaspaceSize:设置方法区(元空间)最大值,如图默认为18446744073709547520接近无限大,也有说默认是-1,即不限制,受限于本地内存。8G内存的机器,初始和最大值一般都设置为256M或者512M。
  • -XX:+UseTLAB:打开本地线程分配缓存区(Thread Local Allocation Buffer),用于线程上新对象的分配,默认是打开的
  • -XX:+PrintTLAB:打印TLAB的使用情况
  • -XX:TLABSize:设置TLAB的大小-XX:TLABSize=256k
  • -XX:+DisableExplictGC:屏蔽代码显示调用GC,如System.gc()
  • -XX:CompileThreshold:JIT热点代码编译的阀值
  • -XX:+PrintGC:打印简单的GC日志信息
  • -XX:+PrintGCDetails:打印详细的GC日志信息,不同垃圾回收器的GC信息格式不一样,同一垃圾回收器不同版本JDK也可能不一样
  • -XX:+PrintHeapAtGC:每次GC前/后堆内存的使用情况,已经FGC次数(full 0)
  • -XX:+PrintGCTimeStamps:打印进程启动到现在GC所运行的时间
  • -XX:+PrintGCApplicationConcurrentTime:打印上次GC后停顿到现在过去了多少时间,当GC后时间置为0
  • -XX:+PrintGCApplicationStoppedTime:打印GC时应用停顿的时间
  • -XX:+PrintReferenceGC:打印强、软、弱、虚引用各个引用的数量以及时长
  • -verbose:class:打印类加载情况
  • -XX:+PrintVMOptions
  • -XX:PreBlockSpin:设置锁自旋次数
  • -XX:+UseSerialGC:新生代使用Serial,老年代使用Serial Old;
  • -XX:+UseParallelGC/-XX:UseParallelOldGC:新生代使用Parallel Scavenge,老年代使用Parallel Old
  • -XX:+UseParNewGC:新生代使用ParNew,老年代自动使用Serial Old;
  • -XX:+UseConMarkSweepGC:新生代使用ParNew,老年代使用CMS + Serial Old;
  • -XX:+UseG1GC:使用G1;

2.3 Parallel常用参数

Parallel GC是JVM中的并行垃圾收集器

  • -XX:SurvivorRatio=n:设置年轻代中Eden区和Survivor的比例,为Eden: S0: S1 = n:1:1
  • -XX:NewRatio=n:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为n,则年轻代与年老代的比值为1 : n;
  • -XX:PreTenureSizeThreshold:设置判定大对象的内存大小,大于该值直接进入老年代,只对Serial和ParNew两款新生代收集器有效
  • -XX:MaxTenuringThreshold:对象进入老年代的年龄,默认为15。设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。设置值大,可以让增加对象在年轻代的停留时间,增加对象在年轻代回收的概率。
  • -XX:ParallelGCThreads=n:配置并行收集器的垃圾收集线程数,即:同时多少个线程一起进行垃圾回收。
  • -XX:+UseAdaptiveSizePolicy:自动选择堆内各区大小的比例

2.4 CMS常用参数

  • -XX:UseConcMarkSweepGC
  • -XX:ParallelCMSThreads:CMS线程数量
  • -XX:CMSInitiatingOccupancyFraction:老年代使用内存回收阈值,超过该阀值后开始CMS收集,默认是68%。
  • -XX:+UseCMSCompactAtFullCollection:在FGC时压缩整理内存
  • -XX:CMSFullGCsBeforeCompaction:多少次FGC之后进行压缩整理
  • -XX:+CMSClassUnloadingEnabled:使用CMS回收Perm区需要卸载的类
  • -XX:CMSInitiatingPermOccupancyFraction:达到什么比例时进行Perm回收
  • -XX:GCTimeRatio:设置GC时间占用程序运行时间的百分比,建议比例,CMS会根据这个值调整堆空间
  • -XX:MaxGCPauseMillis:GC停顿建议时间,GC会尝试各种手段达到这个时间,比如减少年轻代

2.5 G1常用参数

  • -XX:+UseG1GC:使用G1垃圾收集器
  • -XX:ParallelGCThreads:知道垃圾收集线程数
  • -XX:+G1HeapRegionSize:指定分区大小(1M~32M,必须是2的N次幂),建议逐渐增大该值。随着size增加,垃圾存活时间更长,GC间隔更长,每次GC时间也会变长
  • -XX:MaxGCPauseMillis:最大GC停顿时间建议值,G1会尝试调整Young区的块数来达到这个值,
  • -XX:GCPauseIntervalMillis:GC的间隔时间
  • -XX:G1NewSizePercent:新生代初识内存空间比例,默认堆的5%
  • -XX:G1MaxNewSizePercent:新生代最大内存空间比例,默认60%
  • -XX:GCTimeRatio:设置GC时间占用程序运行时间的百分比,建议比例,G1会根据这个值调整堆空间
  • -XX:ConcGCThreads:并发线程数
  • -XX:InitiatingHeapOccupancyPercent:设置触发GC标记周期的 Java 堆占用率阈值。默认占用率是整个 Java 堆的 45%

三、JVM调优工具

3.1 软件工具

  • Jconsole
  • JVisualVM
  • Jprofiler
  • GCViewer
  • GC Easy
  • Java Flight Recorder

3.2 命令行工具

  • top:linux命令,可以观察哪个进程PID和内存过高
  • jps [options] [hostid]
    • jps -p:禁止输出main方法的类或者jar文件的名称和参数
    • jps -m:输出main方法的参数,JVM自带参数不会输出
    • jps -l:输出应用主类的完整包路径或者jar文件的全路径名称
    • jps -v:输出对象进程的JVM的参数
  • jinfo [option] [pid]
    • jinfo -flags pod:输出JVM的全部参数
    • jinfo -sysprops pid:输出系统的全部参数
  • jstate [generalOption | outputOptions pid [interval[s|ms] [count]]]
    • jstat -class:类加载行为的统计信息
    • jstat -compiler:即时编译热点行为的统计
    • jstat -gc:JVM中堆的垃圾收集情况的统计
    • jstat -gccapacity:显示各代的容量以及使用情况
  • jstack [option] pid
    • jstack pid:列出当前进程的各个线程信息
    • jstack pid > /Users/shaotuo/threadDump.txt:生成Thread Dump文件
  • jmap:列表指定java进程的堆内存信息,对象的统计信息、ClassLoader的信息,可以使用jmap生成Heap dump文件

四、相关问题

4.1 CPU占用高

4.2 OOM日志错误排查

  • java.lang.OutOfMemoryError:…java heap space…
    • 堆栈溢出,代码问题可能性很大;
    • 线程池运用不当OOM,不断往list加对象,导致回收不了;
    • 重写finalize引发频繁GC:finalize方法内会拯救对象,导致对象不被回收;重写finalize但不拯救 ,也会将对象推到F-Queue队列,等待下次GC才回收;
  • java.lang.OutOfMemoryError:GC over head limit exceeded
    • 系统处于高频GC状态,且回收效果作用不大;有可能内存不够导致,某引用使用不当,导致不能被回收
  • java.lang.OutOfMemoryError: Direct buffer memory
    • 直接内存不够,JVM不会回收直接内存,查看是否是使用ByteBuffer.allocateDirect方法,没有clear导致;一般为使用NIO的问题
  • java.lang.OutOfMemoryError: unable to create new native thread
    • 堆外内存不够,导致无法为线程分配内存
  • java.lang.OutOfMemoryError: request {} byte for {} out of swap
    • 地址空间不够
  • java.lang.StackOverflowError
    • 线程栈溢出,由-Xss控制线程栈大小,一般是代码里面循环调用的问题