大部分的情况都是由于企业内部代码逻辑不合理导致。
JVM内部性能优化
- 栈上分配
- 方法内联
- JVM的自适应调整
JVM改错
- 大并发内存不足
- OOM 内存泄漏
- GC频繁
- CPU飙升
JVM的调优的原则是让你各项指标尽可能的利用到你硬件的性能瓶颈。
JVM的性能优化可以分为代码层面和非代码层面。
在代码层面,大家可以结合字节码指令进行优化,比如一个循环语句,可以将循环不相关的代码提 取到循环体之外,这样在字节码层面就不需要重复执行这些代码了。
在非代码层面,一般情况可以从内存、 gc以及cpu占用率等方面进行优化。
注意,JVM调优是一个漫长和复杂的过程,而在很多情况下,JVM是不需要优化的,因为JVM本身已经做了很多的内部优化操作。
一,JVM参数
1,标准参数
-version
-help
-server
-cp
2,非标准参数[ -X ]
-Xint 解释执行
-Xcomp 第一次使用就编译成本地代码
-Xmixed 混合模式,JVM自己来决定
3,非标准化参数 [ -XX ]
a.Boolean类型
格式:-XX:[+-]<name> +或-表示启用或者禁用name属性
比如:-XX:+UseConcMarkSweepGC 表示启用CMS类型的垃圾回收器 -XX:+UseG1GC 表示启用G1类型的垃圾回收器
b.非Boolean类型
格式:-XX<name>=<value>表示name属性的值是value
比如:-XX:MaxGCPauseMillis=500
4,其他参数
-Xms1000M等价于-XX:InitialHeapSize=1000M
-Xmx1000M等价于-XX:MaxHeapSize=1000M
-Xss100等价于-XX:ThreadStackSize=100
所以这块也相当于是-XX类型的参数
5,查看参数
java -XX:+PrintFlagsFinal -version > flags.txt
值得注意的是"="表示默认值,":="表示被用户或JVM修改后的值
要想查看某个进程具体参数的值,可以使用jinfo一般要设置参数,可以先查看一下当前参数是什么,然后进行修改
6,设置参数的常见方式
- 开发工具中设置比如IDEA,eclipse
- 运行jar包的时候:java -XX:+UseG1GC xxx.jar
- web容器比如tomcat,可以在脚本中的进行设置
- 通过jinfo实时调整某个java进程的参数(参数只有被标记为manageable的flags可以被实时修改)
7,实践和单位换算
(1)设置堆内存大小和参数打印 -Xmx100M -Xms100M -XX:+PrintFlagsFinal
(2)查询+PrintFlagsFinal的值 :=true
(3)查询堆内存大小MaxHeapSize := 104857600
(4)换算 104857600(Byte)/1024=102400(KB) 102400(KB)/1024=100(MB)
(5)结论 104857600是字节单位
1Byte(字节)=8bit(位)1KB=1024Byte(字节)1MB=1024KB1GB=1024MB1TB=1024GB
8,常用参数含义
-XX:CICompilerCount=3
最大并行编译数
如果设置大于1,虽然编译速度 会提高,但是同样影响系统稳定 性,会增加JVM崩溃的可能
-XX:InitialHeapSize=100M
初始化堆大小
简写-Xms100M
-XX:MaxHeapSize=100M
最大堆大小
简写-Xms100M
-XX:NewSize=20M
设置年轻代的大小
-XX:MaxNewSize=50M
年轻代最大大小
-XX:OldSize=50M
设置老年代大小
-XX:MetaspaceSize=50M
设置方法区大小
-XX:MaxMetaspaceSize=50M
方法区最大大小
-XX:+UseParallelGC
使用UseParallelGC
新生代,吞吐量优先
-XX:+UseParallelOldGC
使用UseParallelOldGC
老年代,吞吐量优先
-XX:+UseConcMarkSweepGC
使用CMS
老年代,停顿时间优先
-XX:+UseG1GC
使用G1GC
新生代,老年代,停顿时间优先
-XX:NewRatio
新老生代的比值
比如-XX:Ratio=4,则表示新生代: 老年代=1:4,也就是新生代占整 个堆内存的1/5
-XX:SurvivorRatio
两个S区和Eden区的比值
比如-XX:SurvivorRatio=8,也就 是(S0+S1):Eden=2:8,也就是一 个S占整个新生代的1/10
-XX:+HeapDumpOnOutOfMemoryError
启动堆内存溢出打印
当JVM堆内存发生溢出时,也就 是OOM,自动生成dump文件
-XX:HeapDumpPath=heap.hprof
指定堆内存溢出打印目录
表示在当前目录生成一个 heap.hprof文件
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-Xloggc:g1- gc.log
打印出GC日志
可以使用不同的垃圾收集器,对 比查看GC情况
-Xss128k
设置每个线程的堆栈大小
经验值是3000-5000最佳
-XX:MaxTenuringThreshold=6
提升年老代的最大临界值
默认值为 15
-XX:InitiatingHeapOccupancyPercent
启动并发GC周期时堆内存使用占比
G1之类的垃圾收集器用它来触发 并发GC周期,基于整个堆的使用 率,而不只是某一代内存的使用比. 值为 0 则表示”一直执行GC循环”. 默认值为 45.
-XX:G1HeapWastePercent
允许的浪费堆空间的占比
默认是10%,如果并发标记可回 收的空间小于10%,则不会触发 MixedGC。
-XX:MaxGCPauseMillis=200ms
G1最大停顿时间
暂停时间不能太小,太小的话就 会导致出现G1跟不上垃圾产生的 速度。最终退化成Full GC。所以 对这个参数的调优是一个持续的 过程,逐步调整到最佳状态。
-XX:ConcGCThreads=n
并发垃圾收集器使用的线程数量
默认值随JVM运行的平台不同而 不同
-XX:G1MixedGCLiveThresholdPercent=65
混合垃圾回收周期中要包括的旧区
域设置占用率阈值
默认占用率为 65%
-XX:G1MixedGCCountTarget=8
设置标记周期完成后,对存活数据 上限为 G1MixedGCLIveThresholdPercent 的旧区域执行混合垃圾回收的目标次数
默认8次混合垃圾回收,混合回 收的目标是要控制在此目标次数以内
-XX:G1OldCSetRegionThresholdPercent=1
描述Mixed GC时,Old Region被加 入到CSet中
默认情况下,G1只把10%的Old Region加入到CSet中
二,常用命令
1,jps
2,jinfo
(1)实时查看和调整JVM配置参数
jinfo -flag name PID 查看某个java进程的name属性的值
jinfo -flag MaxHeapSize PID
jinfo -flag UseG1GC PID
(3)修改
参数只有被标记为manageable的flags可以被实时修改
jinfo -flag [+|-] PID jinfo -flag <name>=<value> PID
(4)查看曾经赋过值的一些参数
jinfo -flags PID
3,jstat
(1)查看虚拟机性能统计信息
The jstack command prints Java stack traces of Java threads for a specified Java process, core file, or remote debug server.
(2) 查看类装载信息
jstat -class PID 1000 10 查看某个java进程的类装载信息,每1000毫秒输出一次,共输出10次
(3)查看垃圾收集信息
jstat -gc PID 1000 10
4,jstack
(1)查看线程堆栈信息
The jstack command prints Java stack traces of Java threads for a specified Java process, core file, or remote debug server.
(2)用法
jstack PID
(3)排查死锁案例
//运行主类
public class DeadLockDemo {
public static void main(String[] args) {
DeadLock d1 = new DeadLock(true);
DeadLock d2 = new DeadLock(false);
Thread t1 = new Thread(d1);
Thread t2 = new Thread(d2);
t1.start();
t2.start();
}
}
//定义锁对象
class MyLock {
public static Object obj1 = new Object();
public static Object obj2 = new Object();
}
//死锁代码
class DeadLock implements Runnable {
private boolean flag;
DeadLock(boolean flag) {
this.flag = flag;
}
public void run() {
if (flag) {
while (true) {
synchronized(MyLock.obj1) {
System.out.println(Thread.currentThread().getName() + "----if获
得obj1锁 ");
synchronized(MyLock.obj2) {
System.out.println(Thread.currentThread().getName() + "--- - if获得obj2锁 ");
}
}
}
} else {
while (true) {
synchronized(MyLock.obj2) {
System.out.println(Thread.currentThread().getName() + "----否则
获得obj2锁 ");
synchronized(MyLock.obj1) {
System.out.println(Thread.currentThread().getName() + "--- - 否则获得obj1锁 ");
}
}
}
}
}
jstack分析
把打印信息拉到最后可以发现
5,jmap
(1)生成堆转储快照
The jmap command prints shared object memory maps or heap memory details of a specified process, core file, or remote debug server.
(2)打印出堆内存相关信息
jmap -heap PID
jinfo -flag UsePSAdaptiveSurvivorSizePolicy 35352
-XX:SurvivorRatio=8
(3) dump出堆内存相关信息
jmap -dump:format=b,file=heap.hprof PID
(4)要是在发生堆内存溢出的时候,自动dump出该文件
一般在开发中, JVM参数可以加上下面两句,这样内存溢出时,会自动dump出该文件
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=heap.hprof
三,内存
正常情况下不需要设置,那如果是促销或者秒杀的场景呢?
每台机器配置2c4G,以每秒3000笔订单为例,整个过程持续60秒
1,内存溢出(OOM)
一般会有两个原因:
(1)大并发情况下
(2)内存泄露导致内存溢出
2,大并发[秒杀]
浏览器缓存、本地缓存、验证码
CDN静态资源服务器
集群+负载均衡
动静态资源分离、限流[基于令牌桶、漏桶算法]
应用级别缓存、接口防刷限流、队列、 Tomcat性能优化
异步消息中间件
Redis热点数据对象缓存
分布式锁、数据库锁
5分钟之内没有支付,取消订单、恢复库存等
3,内存泄露导致内存溢出
ThreadLocal引起的内存泄露,最终导致内存溢出
public class TLController {
@RequestMapping(value = "/tl")
public String tl(HttpServletRequest request) {
ThreadLocal < Byte[] > tl = new ThreadLocal < Byte[] > ();
// 1MB
tl.set(new Byte[1024 * 1024]);
return "ok";
}
}
(1) top命令查看
top
top -Hp PID
(2) jstack查看线程情况,发现没有死锁或者IO阻塞的情况
jstack PID
java -jar arthas.jar ---> thread
(3)查看堆内存的使用,发现堆内存的使用率已经高达88.95%
jmap -heap PID
java -jar arthas.jar ---> dashboard
(4)此时可以大体判断出来,发生了内存泄露从而导致的内存溢出,那怎么排查呢?
jmap -histo:live PID | more
获取到jvm.hprof文件,上传到指定的工具分析,比如heaphero.io
获取使用Memory Analyzer Tool 进行分析
四,垃圾收集器的选择
吞吐量和停顿时间
- 停顿时间
- 吞吐量
这两个指标也是评价垃圾回收器好处的标准。
- 优先调整堆的大小让服务器自己来选择
- 如果内存小于100M,使用串行收集器
- 如果是单核,并且没有停顿时间要求,使用串行或JVM自己选
- 如果允许停顿时间超过1秒,选择并行或JVM自己选
- 如果响应时间最重要,并且不能超过1秒,使用并发收集器
对于G1收集
(1)50%以上的堆被存活对象占用(2)对象分配和晋升的速度变化非常大(3)垃圾回收时间比较长
全称Remembered Set,记录维护Region中对象的引用关系
(1)串行
-XX:+UseSerialGC
-XX:+UseSerialOldGC
(2)并行(吞吐量优先):
-XX:+UseParallelGC
-XX:+UseParallelOldGC
(3)并发收集器(响应时间优先)
-XX:+UseConcMarkSweepGC
-XX:+UseG1GC
五,G1优化
是否选用G1
官网 https://docs.oracle.com/javase/8/docs/technotes/guides/vm/G1.html#use_cases
(1) 50%以上的堆被存活对象占用
(2) 对象分配和晋升的速度变化非常大
(3) 垃圾回收时间比较长
(1)使用G1GC垃圾收集器: -XX:+UseG1GC
修改配置参数,获取到gc日志,使用GCViewer分析吞吐量和响应时间
Throughput 99.16% | Min Pause 0.00016s | Max Pause 0.0137s | Avg Pause 0.00559s | GC count 12 |
(2)调整内存大小再获取gc日志分析
比如设置堆内存的大小,获取到gc日志,使用GCViewer分析吞吐量和响应时间
Throughput 98.89% | Min Pause 0.00021s | Max Pause 0.01531s | Avg Pause 0.00538s | GC count 12 |
(3)调整最大停顿时间
比如设置最大停顿时间,获取到gc日志,使用GCViewer分析吞吐量和响应时间
Throughput 98.96% | Min Pause 0.00015s | Max Pause 0.01737s | Avg Pause 0.00574s | GC count 12 |
(4)启动并发GC时堆内存占用百分比
-XX:InitiatingHeapOccupancyPercent=45
G1用它来触发并发GC周期 ,基于整个堆的使用率 ,而不只是某一代内存的使用比例。值为 0 则表示“一直执行 GC循环)'. 默认值为 45 (例如 , 全部的 45% 或者使用了45%).
比如设置该百分比参数,获取到gc日志,使用GCViewer分析吞吐量和响应时间
Throughput 98.11% | Min Pause 0.00406s | Max Pause 0.00532s | Avg Pause 0.00469s | GC count 12 |
G1调优最佳实战
官网
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html#recommendations
(1)不要手动设置新生代和老年代的大小,只要设置整个堆的大小
why: https://blogs.oracle.com/poonam/increased-heap-usage-with-g1-gc
G1收集器在运行过程中,会自己调整新生代和老年代的大小
其实是通过adapt代的大小来调整对象晋升的速度和年龄,从而达到为收集器设置的暂停时间目标 如果手动设置了大小就意味着放弃了G1的自动调优
(2)不断调优暂停时间目标
一般情况下这个值设置到100ms或者200ms都是可以的(不同情况下会不一样),但如果设置成50ms就不 太合理。暂停时间设置的太短,就会导致出现G1跟不上垃圾产生的速度。最终退化成Full GC。所以对 这个参数的调优是一个持续的过程,逐步调整到最佳状态。暂停时间只是一个目标,并不能总是得到满 足。
(3)使用-XX:ConcGCThreads=n来增加标记线程的数量
IHOP如果阀值设置过高,可能会遇到转移失败的风险,比如对象进行转移时空间不足。如果阀值设置过 低,就会使标记周期运行过于频繁,并且有可能混合收集期回收不到空间。
IHOP值如果设置合理,但是在并发周期时间过长时,可以尝试增加并发线程数,调高 ConcGCThreads。
(4) MixedGC调优
-XX:InitiatingHeapOccupancyPercent
-XX:G1MixedGCLiveThresholdPercent
-XX:G1MixedGCCountTarger
-XX:G1OldCSetRegionThresholdPercent
(5)适当增加堆内存大小
(6)不正常的Full GC
有时候会发现系统刚刚启动的时候,就会发生一次Full GC,但是老年代空间比较充足,一般是由Metaspace 区域引起的。可以通过MetaspaceSize适当增加其大家,比如256M。