JVM调优

JVM调优

JVM调优目标:使用较小的内存占用来获得较高的吞吐量或者较低的延迟。

程序在上线前的测试或运行中有时会出现一些大大小小的JVM问题,比如cpu load过高、请求延迟、tps降低等,甚至出现内存泄漏(每次垃圾收集使用的时间越来越长,垃圾收集频率越来越高,每次垃圾收集清理掉的垃圾数据越来越少)、内存溢出导致系统崩溃,因此需要对JVM进行调优,使得程序在正常运行的前提下,获得更高的用户体验和运行效率。

这里有几个比较重要的指标:

内存占用:程序正常运行需要的内存大小。

延迟:由于垃圾收集而引起的程序停顿时间。

吞吐量:用户程序运行时间 占 用户程序和垃圾收集总时间的比值。

当然,和CAP原则一样,同时满足一个程序内存占用小、延迟低、高吞吐量是不可能的,程序的目标不同,调优时所考虑的方向也不同。在调优之前,必须要结合实际场景,有明确的的优化目标,找到性能瓶颈,对瓶颈有针对性的优化,最后进行测试,通过各种监控工具确认调优后的结果是否符合目标。

(1)调优可以依赖、参考的数据有系统运行日志、堆栈错误信息、gc日志、线程快照、堆转储快照等。

系统运行日志:系统运行日志就是在程序代码中打印出的日志,描述了代码级别的系统运行轨迹(执行的方法、入参、返回值等),一般系统出现问题,系统运行日志是首先要查看的日志。

堆栈错误信息:当系统出现异常后,可以根据堆栈信息初步定位问题所在。比如根据“java.lang.OutOfMemoryError: Java heap space”可以判断是堆内存溢出;根据“java.lang.StackOverflowError”可以判断是栈溢出;根据“java.lang.OutOfMemoryError: PermGen space”可以判断是方法区溢出等。

GC日志:程序启动时用 -XX:+PrintGCDetails 和 -Xloggc:/data/jvm/gc.log 可以在程序运行时把gc的详细过程记录下来,或者直接配置“-verbose:gc”参数把gc日志打印到控制台。通过记录的gc日志可以分析每块内存区域gc的频率、时间等,从而发现问题,进行有针对性的优化。

线程快照:顾名思义,根据线程快照可以看到线程在某一时刻的状态。当系统中可能存在请求超时、死循环、死锁等情况时,可以根据线程快照来进一步确定问题。通过执行虚拟机自带的“jstack pid”命令,可以dump出当前进程中线程的快照信息,更详细的使用和分析网上有很多例。

堆转储快照:程序启动时可以使用 “-XX:+HeapDumpOnOutOfMemory” 和 “-XX:HeapDumpPath=/data/jvm/dumpfile.hprof”,当程序发生内存溢出时,把当时的内存快照以文件形式进行转储(也可以直接用jmap命令转储程序运行时任意时刻的内存快照),事后对当时的内存使用情况进行分析。

JVM的优化我们可以从JIT优化,内存分区设置优化以及GC选择优化三个方面入手。

1、JIT编译器优化

在系统启动的时候,首先Java代码是解释执行的,当方法调用次数到达一定的阈值的时候(client:1500,server:10000),会采用JIT优化编译。而直接将JVM的启动设置为-Xcomp并不会有想象中那么好。没有足够的profile(侧写,可以大致理解为分析结果),优化出来的代码质量很差,甚至于执行效率还要低于解释器执行,并且机器码的大小很容易就超出字节码大小的10倍以上。

那么我们能做的,就是通过附加启动命令适当的调整这个阈值或者调整热度衰减行为,在恰当的时候触发对代码进行即时编译。

方法计数器阈值:-XX:CompileThreshold

回边计数器阈值:-XX:OnStackReplacePercentage(这并不是直接调整阈值,此计数器会根据是Client模式还是Server模式有不同的计算公式)

关闭热度衰减:-XX:UseCounterDecay

设置半衰周期:-XX:CounterHalfLifeTime

可以根据以下的优化技术名称搜索了解详情。

JIT编译器优化

JIT编译器优化

2、JVM内存分区优化

1.使用jps,jmap分析内存快照

① 用 jps(JVM process Status)可以查看虚拟机启动的所有进程、执行主类的全名、JVM启动参数
比如当执行了JPSTest类中的main方法后(main方法持续执行),执行 jps -l可看到下面的OOMTest类的pid为7480,加上-v还可以看到JVM启动参数。
内存快照
内存快照
② 用jstat(JVM Statistics Monitoring Tool)监视虚拟机信息

jstat -gc pid 500 10 :pid是线程ID,每500毫秒打印一次Java堆状况(各个区的容量、使用容量、gc时间等信息),打印10次。
jstat分析内存快照
jstat还可以以其他角度监视各区内存大小、监视类装载信息等。

③ 用jmap(Memory Map for Java)查看堆内存信息

④ 执行jmap -histo pid可以打印出当前堆中所有每个类的实例数量和内存占用
如下,class name是每个类的类名([B是byte类型,[C是char类型,[I是int类型),bytes是这个类的所有示例占用内存大小,instances是这个类的实例数量:
jstat分析内存快照

执行jmap -dump 可以转储堆内存快照到指定文件,比如执行

jmap -dump:format=b,file=/data/jvm/dumpfile_jmap.hprof PID ,可以把当前堆内存的快照转储到dumpfile_jmap.hprof文件中,然后可以对内存快照进行分析。dumpfile_jmap.hprof是一个二进制格式文件,不出意外的话看不懂里面的内容。

⑤ 分析堆转储快照

前面说到配置了 “-XX:+HeapDumpOnOutOfMemory” 参数可以在程序发生内存溢出时dump出当前的内存快照,也可以用jmap命令随时dump出当时内存状态的快照信息,dump的内存快照一般是以.hprof为后缀的二进制格式文件。

可以直接用 jhat(JVM Heap Analysis Tool) 命令来分析内存快照,它实际上内嵌了一个微型的服务器,可以通过浏览器来分析对应的内存快照,比如执行 jhat -port 9810 -J-Xmx4G /data/jvm/dumpfile_jmap.hprof 表示以9810端口启动 jhat 内嵌的服务器:
jhat分析堆转储快照
在控制台可以看到服务器启动了,访问 http://127.0.0.1:9810/ 可以看到对快照中的每个类进行分析的结果。

访问jhat的定义的网站这个东西还是不够直观。

2.jvisualvm分析内存快照

jvisualvm(在jdk的bin目录下)也可以分析内存快照,在jvisualvm菜单的“文件”-“装入”,选择堆内存快照,快照中的信息就以图形界面展示出来了,
或者直接使用监视 - 堆Dump :如下,主要可以查看每个类占用的空间、实例的数量和实例的详情等:
jvisualvm分析内存快照
jvisualvm分析内存快照

3.内存溢出问题定位

1. 设置堆大小,以及捕获jvm日志

-Xms50m

-Xmx50m

-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=d:\

设置堆大小,以及捕获jvm日志
2.使用Java Visual VM 分析日志

文件 -> 装入 -> 文件类型选择堆 -> 打开
使用Java Visual VM 分析日志
概要中可以看到内存溢出异常(在此之前呢,想办法造成堆内存溢出),Thread-61 ,点击进去
使用Java Visual VM 分析日志这里可以看到是哪一行,哪个对象造成的内存溢出

使用Java Visual VM 分析日志

4.常用JVM参数参考

参数 说明 实例

-Xms 初始堆大小,默认物理内存的1/64 -Xms512M

-Xmx 最大堆大小,默认物理内存的1/4 -Xms2G

-Xmn 新生代内存大小,官方推荐为整个堆的3/8 -Xmn512M

-Xss 线程堆栈大小,jdk1.5及之后默认1M,之前默认256k -Xss512k

-XX:NewRatio=n 设置新生代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4 -XX:NewRatio=3

-XX:SurvivorRatio=n 年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:8,表示Eden:Survivor=8:1:1,一个Survivor区占整个年轻代的1/8 -XX:SurvivorRatio=8

-XX:PermSize=n 永久代初始值,默认为物理内存的1/64 -XX:PermSize=128M

-XX:MaxPermSize=n 永久代最大值,默认为物理内存的1/4 -XX:MaxPermSize=256M

-verbose:class 在控制台打印类加载信息

-verbose:gc 在控制台打印垃圾回收日志

-XX:+PrintGC 打印GC日志,内容简单

-XX:+PrintGCDetails 打印GC日志,内容详细

-XX:+PrintGCDateStamps 在GC日志中添加时间戳

-Xloggc:filename 指定gc日志路径 -Xloggc:/data/jvm/gc.log

-XX:+UseSerialGC 年轻代设置串行收集器Serial

-XX:+UseParallelGC 年轻代设置并行收集器Parallel Scavenge

-XX:ParallelGCThreads=n设置Parallel Scavenge收集时使用的CPU数。并行收集线程数。 -XX:ParallelGCThreads=4

-XX:MaxGCPauseMillis=n 设置Parallel Scavenge回收的最大时间(毫秒) -XX:MaxGCPauseMillis=100

-XX:GCTimeRatio=n设置Parallel Scavenge垃圾回收时间占程序运行时间的百分比。公式为1/(1+n) -XX:GCTimeRatio=19

-XX:+UseParallelOldGC 设置老年代为并行收集器ParallelOld收集器

-XX:+UseConcMarkSweepGC 设置老年代并发收集器CMS

-XX:+CMSIncrementalMode 设置CMS收集器为增量模式,适用于单CPU情况。

jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.9 默认垃圾收集器G1

-XX:+PrintCommandLineFlags jvm参数可查看默认设置收集器类型

单CPU情况。

jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.9 默认垃圾收集器G1

-XX:+PrintCommandLineFlags jvm参数可查看默认设置收集器类型

-XX:+PrintGCDetails亦可通过打印的GC日志的新生代、老年代名称判断

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值