Java核心09-JVM性能调优

本文详细阐述了JVM性能调优的步骤、原则,涉及架构、代码和JVM层面的优化,介绍了JVM参数、垃圾回收算法,以及如何使用JDK工具如jmap、jstat、jstack和VisualVM进行问题诊断和性能监控。
摘要由CSDN通过智能技术生成
 JVM性能调优
  1. 性能调优分为架构调优,代码调优,JVM调优,数据库调优,操作系统调优等;
  2. 性能调优的基本步骤是:
    1. 明确优化目标 --》发现性能瓶颈 --》性能调优 --》通过监控及数据统计工具获得数据 --》确认是否达到目标。
  3. 何时进行JVM调优
    1. 堆内存(老年代)空间持续上涨达到设置的最大内存值;
    2. Full GC次数频繁;
    3. GC停顿时间过长(超过1s);
    4. 应用出现OutOfMemory等内存异常;
    5. 应用中有使用本地缓存且占用大量内存空间;
    6. 系统吞吐量与响应性能不高或下降。
  4. JVM调优的基本原则
    1. 在进行GC优化之前,需要确认项目的架构和代码等已经没有优化的空间;大多数导致GC问题的原因是代码层面的问题导致;
    2. 代码层面,减少创建对象的数量;减少使用全局变量和大对象;
    3. 分析GC情况优化代码,比优化JVM参数更好;不要为了调优而调优;
    4. GC优化必须建立在深入理解各种垃圾回收器的基础上。
  5. JVM调优的步骤
    1. 分析GC日志及dump文件,判断是否需要优化,确定瓶颈问题点;
    2. 确定JVM调优量化目标;
    3. 依次调优内存、延迟、吞吐量等指标;
    4. 不断的分析和调整,直到找到合适的JVM参数配置;
  6. JVM判断对象是否已死亡
    1. 引用计数法
      1. 给对象中添加一个引用计数器,每当对象被引用,计数器加1;当引用失效,计数器减1;任何时候计数器为0,则对象就不可再被使用。无法解决对象之间相互循环引用问题。
    2. 可达性分析算法
      1. 通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。  
      2. GC Roots 的对象包括:
        1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
        2. 本地方法栈(Native 方法)中引用的对象
        3. 方法区中类静态属性引用的对象
        4. 方法区中常量引用的对象
        5. 所有被同步锁持有的对象
JVM性能调优工具
  • JVM参数

 参数名称

含义

说明

-Xms

堆最小值

默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.  

-Xmx

堆最大值

默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制  

-Xmn

年轻代大小

注意:此处的大小是(eden+ 2 survivor space).整个堆大小=年轻代大小 + 老年代大小 + 持久代(永久代)大小.增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8 。

-XX:NewSize

设置年轻代最小值

-XX:MaxNewSize

设置年轻代最大值

-XX:MetaspaceSize

设置元数据区最小值

jdk17 -XX:PermSize

-XX:MaxMetaspaceSize

设置元数据区最大值

jdk1.7 -XX:MaxPermSize

-Xss

线程栈空间大小

如果栈不是很深, 应该是128k够用的,大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。  

-XX:NewRatio

堆内存中,年轻代和老年代的比值

-XX:NewRatio=4,表示年轻代和老年代所占比值为1:4,年轻代占整个堆栈的1/5.

-XX:SurvivorRatio

Eden区与Survivor区的大小比值

-XX:Survivor=8,则两个Survivor区与一个Eden区的比值为2:8。一个Survivor区占整个年轻代的1/10.

-XX:+DisableExplicitGC

关闭System.gc()

-XX:PretenureSizeThreshold

对象超过多大时,直接在老年代中分配

-XX:MaxTenuringThreshold

设置分代年龄的阈值

经过一次Minor GC的对象仍然存活,则分代年龄+1,超过阈值则进入老年代

-XX:ParalleGCThreads

并行收集器的线程数

并行执行GC的线程数;此值最好配置与处理器数目相等 同样适用于CMS  

-XX:MaxGCPauseMills

每次年轻代垃圾回收的最长时间(最长暂停时间)

如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值.  

-XX:+UseSerialGC

串行垃圾收集器

单线程,采用复制算法实现,主要用于单CPU环境。简单,效率高;

-XX:UseG2GC

-XX:+UseParNewGC

并行多线程垃圾收集器

采用复制算法实现,主要用于多CPU环境,可充分利用CPU资源,减少回收时间;

-XX:ParallelGCThreads 指定线程数

cms

CMS垃圾收集器

-XX:UseG1GC

将整个Java堆划分为多个大小相等的独立区域(Region),可独立的管理整个堆内存,Region之间是基于复制算法实现,整体上是基于标记-整理算法实现,采用Remembered Set记录每个region的引用信息避免全堆扫描

特点:

  • 并行与并发:使用多个CPU缩短暂停时间;
  • 分代收集:保留分代概念,独立的管理整个堆内存;
  • 空间整合:复制算法和标记-整理算法相结合,不会产生内存碎片;
  • 可预测的停顿:建立可预测的停顿时间模型;

-XX:+PrintGCDetails

打印GC的详细信息

-XX:

+PrintCommandLineFlags

打印当前使用的JVM参数

  • JDK 命令行调优工具

说明:(在JDK安装目录的/bin目录下);vmid-应用进程Id

jps

JVM Process Status

查看所有Java进程的启动类、传入参数和Java虚拟机参数等信息

jps -q 输出进程的本地虚拟机唯一ID

jps -l 输出主类的全名

jps -v 输出虚拟机进程启动时JVM参数

jps -m 输出传递给Java进程main()函数的参数

jstat

监视虚拟机各种运行状态信息。可以显示本地或者远程虚拟机进程中的类信息、内存、垃圾收集、JIT编译等运行数据。是运行期间定位虚拟机性能问题的首选工具(无GUI界面工具时)

命令格式:

​jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]​

比如:

​jstat -gc -h3 31736 1000 10​

表示分析进程 id 为 31736 的 gc 情况,每隔 1000ms 打印一次记录,打印 10 次停止,每 3 行后打印指标头部。  

jstat -class vmid  显示ClassLoader的相关信息

jstat -compiler vmid  显示JIT编译的相关信息

jstat -gc vmid  显示与GC相关的堆信息

jstat -gccapacity vmid 显示各个代的容量及使用情况

jstat -gcnew vmid  显示新生代信息

jstat -gcnewcapacity vmid 显示新生代大小与使用情况

jstat -gcold vmid  显示老年代的行为统计

jstat -gcoldcapacity vmid  显示老年代的大小

jstat -gcpermcapaticy vmid  显示永久代大小(jdk1.8以前)

jstat -gcutil vmid  显示垃圾收集信息

jinfo

实时地查看和调整虚拟机各项参数。

jinfo可以在不重启虚拟机情况下,动态的修改JVM参数。

jinfo vmid  输出当前jvm进程的全部参数和系统属性

jinfo -flag "cmd-name" vmid  输出对应名称的参数的具体值

比如:

输出maxHeapSize大小:jinfo -flag MaxHeapSize vmid

查看是否开启打印GC日志:jinfo -flag PrintGC vmid

jinfo -flag [+|-]name vmid 动态修改jvm参数

比如:

开启打印GC日志详情:jinfo -flag +PrintGC vmid

jmap

JVM Memory Map for Java

生成堆转储快照,获取dump文件;

查询finalizer执行队列、Java堆和永久代详细信息

jhat

用于分析heapdump文件,它会建立一个HTTP/HTML服务器,让用户可以在浏览器上查看分析结果

jstack

生成虚拟机当前时刻的线程快照,即当前虚拟机内每一条线程正在执行的方法堆栈的集合。

目的主要是定位线程长时间出现停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等,都是线程长时间停顿的原因。

通过jstack 命令查看各个线程的调用堆栈,就可看到线程状态。

1、top命令,然后按shift+p按照CPU排序,找到占用CPU过高的进程的pid

2、top -H -p [进程id] 找到进程中消耗资源最高的线程的id

3、printf "%x\n" [线程id] 将需要的线程ID转换为16进制格式

4、jstack [进程id] |grep -A 10 [线程id的16进制]”

  • JDK 可视化分析工具
  1. JConsole Java监视与管理控制台
  2. 是基于JMX的可视化监视、管理工具。可以方便监视本地及远程服务器的Java进程的内存使用情况。在JDK目录下的/bin目录找到jconsole.exe运行。

        

        连接远程进程,需在远程Java程序启动时加上以下参数:

-Djava.rmi.server.hostname=                // 远程服务IP地址
Dcom.sun.management.jmxremote.port=60001   // 监控的端口号
Dcom.sun.management.jmxremote.authenticate=false   //关闭认证
Dcom.sun.management.jmxremote.ssl=false
  • 查看Java程序概况
    • 线程监控
    • 内存监控
    • Visual VM 多合一故障处理工具
      • VisualVM 提供在 Java 虚拟机 (Java Virutal Machine, JVM) 上运行的 Java 应用程序的详细信息。  
      • 显示虚拟机进程以及进程的配置、环境信息(jps、jinfo)。
      • 监视应用程序的 CPU、GC、堆、方法区以及线程的信息(jstat、jstack)。
      • dump 以及分析堆转储快照(jmap、jhat)。
      • 方法级的程序运行性能分析,找到被调用最多、运行时间最长的方法。
      • 离线程序快照:收集程序的运行时配置、线程 dump、内存 dump 等信息建立一个快照,可以将快照发送开发者处进行 Bug 反馈。
CPU 占用过高,分析定位到哪一行代码存在bug

总的来说,是利用jstack 命令打印堆栈信息,而jstack 需要线程id,因为第一步是先分析系统中占用cpu过高的程序是哪一个进程;

  • 第一步定位进程:找到CPU占用最高的进程id,通过top命令找到cpu 占用最高的进程;

    找到进程后,再找进程中的哪个线程占用cpu最高;

top
  • 第二步定位是哪个Java 程序
jps -l | grep 进程Id
  • 第三步定位线程的tid
ps -mp 进程pid -o THREAD,tid,tim
  • 第四步根据tid 得到16进制的线程id
printf "%x\n" 上一步得到的线程ti
  • 第五步通过jstack 定位到具体的代码
jstack 进程id | grep 16进制的tid -A60

打印指定进程的堆栈信息,然后通过 grep 对应的tid进行过滤(找到cpu占用最高的线程),-A60是打印前面60行堆栈信息

  • 第六步分析西昌信息,定位代码

这个时候就会发现报错的信息,就好比在写程序中控制台打印出来的异常一样,然后找到对应的类第几行代码就可以了。

CPU 飙升100% 问题排查
  1. 问题复现:线上系统突然运行缓慢,CPU 飙升,甚至到100%,以及Full GC 次数过多;
  2. 问题排查步骤(既然是CPU 飙升,肯定要查一下耗CPU 的线程,然后看GC):
    1. 执行 jmap -dump:format=b,file=filename 进程Id :导出某进程下内存的heap 输出到文件中。
    2. 执行 jstat -gcutil 进程Id 统计间隔毫秒 统计次数(缺省则一直统计):查看某进程GC持续变化情况,如果发现返回中FullGC很大且一直增大-》确认Full GC! 也可以使用jmap -heap 进程ID查看一下进程的堆内从是不是要溢出了,特别是老年代内从使用情况一般是达到阈值(具体看垃圾回收器和启动时配置的阈值)就会进程Full GC。
    3. 执行 jstack 进程Id | grep 线程Id 命令:查找某进程下-》线程ID(jstack堆栈信息中的nid)=0xa的线程状态。定位到具体的代码行;
    4. 执行 printf "%x\n" 线程Id 命令:后续查看线程堆栈信息展示的都是十六进制,为了找到咱们的线程堆栈信息,咱们需要把线程号转成16进制
    5. 执行 top -Hp 进程Id 命令:查看Java 进程下的所有线程占CPU 的情况;
    6. 执行 top 命令:查看所有进程占系统CPU 的排序;
  3. 原因分析
    1. 内存消耗过大,导致Full GC 次数过多;
    2. 代码中有大量消耗CPU 的操作,导致CPU 过高,系统运行缓慢;比如:某些复杂算法,无线循环递归等;
    3. 由于锁使用不当,导致死锁;
    4. 随机出现大量线程访问接口缓慢;代码某个位置有阻塞性的操作,导致该功能调用整体比较耗时,但出现比较随机;平时消耗的CPU不多,占用的内存也不高;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值