JVM全文
OOM案例
堆溢出
- visualVM分析dump文件
- 找到导致报错的位置
- 找到大量生成的对象
- mat分析
- 找到泄漏疑点
- 线程信息
- 存放大量对象所导致
Ergonomics
自适应调节所导致
- GCEasy查看GC效果
原因
- 可能存在大对象分配
- 可能存在内存泄漏,导致多次GC之后,无法找到一块大的内存容纳当前对象
解决方案
- 检查大对象的分配
- 分析dump文件
- 使用
-Xmx
加大对内存 - 是否有大量自定义的
Finalizable
对象,由框架内部提供
元空间移除
- 参数
-XX:+PrintGCDetails
-XX:MetaspaceSize=60m
-XX:MaxMetaspaceSize=60m
-Xss512K
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=heap/heapdump4.hprof
-XX:SurvivorRatio=8
-XX:+PrintGCDateStamps
-XX:+TraceClassLoading
-XX:+TraceClassUnloading
-Xms50M
-Xmx50M
-Xloggc:log/gc-oom4.log
复制代码
- visualVM
jstat -gc id
查看gc情况
YGC
次数YGCT
时间GCT
总时间
- 错误代码
- mat 直方图以包的形式查看产生的大量对象
原因
- 运行期间生成大量的代理类,无法卸载
- 应用长时间运行,没有重启
- 元空间设置较小
解决方式
- 检查元空间大小
- 检查代码中是否有大量发射操作
- dump文件检查是否存在大量由反射生成的代理类
GC overhead limit exceeded
- jdk6之后,GC过于频繁,98%的时间在GC,但是回收不到2%的堆内存,回收效果差
- 设置参数
-XX:-UseGCOverheadLimit
禁用这个检查,报堆溢出 - 参数
-XX:+PrintGCDetails
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=heap/heapdumpExceeded.hprof
-XX:SurvivorRatio=8
-Xms10M
-Xmx10M
-Xloggc:log/gc-comExceeded.log
复制代码
解决方法
- 是否有大量的死循环,或者大内存的代码
- dump检查是否存在内存泄漏,加大内存
线程溢出
- unable to create new native Thread
- 创建大量线程
- 创建线程数量的计算公式
(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads
- MaxProcessMemory 进程可寻址的最大空间 64位操作系统 2^64
- JVMMemory JVM内存
- ReservedOsMemory 保留的操作系统内存
- ThreadStackSize 线程栈大小
性能调优
Jmemter
- 下载地址
- 配置环境
zprofile
export JMETER_HOME=/Users/mzx/Desktop/java/jvm/apache-jmeter-5.4.3
export PATH=$JAVA_HOME/bin:$PATH:.:$JMETER_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JMETER_HOME/lib/ext/ApacheJMeter_core.jar:$JMETER_HOME/lib/jorphan.jar:$JMETER_HOME/lib/logkit-2.0.jar
复制代码
- 添加线程组,模拟访问
- 设置线程组参数
- 添加线程请求
- 添加监听器
吞吐量
- 提高堆内存,减少
fullgc
的频率,提高吞吐量
JIT优化
- 对于只执行一次的代码,用解释器效率更好
- 只被调用一次,类的构造器
- 没有循环的代码
- 只执行少量次数的代码
- 没有进行逃逸分析
-XX:-DoEscapeAnalysis
,产生大量对象
- 打开逃逸分析,实际上没有那么多对象
- 同步省略,字节码并不会直接省略,还是在编译期间进行优化
- 打开标量替换
-XX:+EliminateAllocations
,逃逸分析才有意义,jvm的栈上分配
,实际上就是标量替换
开启逃逸分析,但是没有开启标量替换
开启逃逸分析,开启标量替换
没有开启逃逸分析
- 小结
- 如果经过逃逸分析后,没有一个对象是不逃逸的,那么逃逸分析的过程就是浪费的
- 无法保证逃逸分析之后,性能一定会提升
合理配置堆内存
- Xmx和Xms设置为老年代存活对象的
3-4倍
,即FullGC之后老年代内存占用的3-4倍 - 方法区大小设置为老年代存活对象的
1.2-1.5倍
- 年轻代 Xmn设置为老年代存活对象的
1-1.5倍
- 强制触发FullGC
jmap -dump:live,forma=b,file=heap.bin <pid>
生成当前存活对象的dump文件jmap -histo:live <pid>
打印class的实例数目,内存占用,类全名等信息,加上live后,只会统计存活对象的数量- 测试工具
- 估算YGC频率
- 面对大流量,低延迟的系统,不建议启用
UseAdaptiveSizePolicy
CPU占用
- 服务器上进行
top -Hp 16195
进程下进行的线程占比情况
jstack 16195 > jstack.log
保存进程的线程信息- 需要根据pid的十六进制找对应的进程
jstack 16195 | grep -A20 3f44
直接找出线程后20行的内容
- 死锁解决
- 调整锁的顺序,保持一致
- 采用定时锁,一段时间后,还不能获取锁,就释放本身持有的锁
G1并发执行的线程数对性能的影响
ConcGCThreads
并发标记的线程数,最多也就是用户线程的1/4- 可以提高吞吐量
调整垃圾回收器
- G1提高吞吐量
日均百万订单系统
- 吞吐量
- 响应时间控制在100ms
- G1设置最大stw时间,设置Java堆占用率阈值
其他问题
- 12306减库存和订单,同时异步进行
分布式本地库存+单独服务器做库存均衡
- 50万PV资料类网站,服务器和性能增加,效率更低
- 原网站内存有限,频繁进行GC,STW比较长,响应时间比较慢
- 之所以更卡顿,内存空间越大,花费FGC时间更长,延迟时间更长
- 垃圾回收器,优先考虑G1,在延迟可控的情况下尽可能提高吞吐量;调整配置参数,
MaxGCPauseMillis
、ConcGCThreads
和堆空间分配;dump文件分析,优化内存空间比例
- CPU经常100%,调优过程
top -Hp 16195
进程下进行的线程占比情况jstack 16195 > jstack.log
保存进程的线程信息jstack 16195 | grep -A20 3f44
直接找出线程后20行的内容
- 系统内存飙高
- 具体工具,内存占比,日志情况
- dump文件
- 监控jvm
- 命令行工具
- 图形化界面