jvm 优化和诊断
内存溢出场景
- Java heap space,堆内存不足
- metaspace 或 Permgen space,方法区不足
- GC overhead limit exceeded,超过 98% 时间在做 GC,但是回收内存不到 2%
- 默认开启 UseGCOverheadLimit,当超过 98% 时间在做 GC,但是回收内存不到 2%时报
- 可以关闭这个检查
-XX: -UseGCOverheadLimit
,就会报 OOM java heap space 堆空间不足 - 可能的问题:内存泄露,检查流是否关闭,是否有大对象,是否有死循环等
- Unableto createnewnativethread:内存不足不能创建线程了。把 -Xss 设置小点,意思每个线程内存使用少点,理论上就能多创建线程了,但实际不是这样的,具体能创建多少线程数量的公式入如下:
- (MaxProcessMemory - JVMMemory - ReservedOsMemory) / ThreadStackSize
- ThreadStackSize 就对应 -Xss 的值,这只是分母,不同操作系统和位数都不一样,记住结论就好了
- 结论: 32 位操作系统,还是比较严格遵循这个公式的,64 位的操作系统不遵循,因为操作系统有自己的保护机制(64位的 MaxProcessMemory 是一个超大的数,理论上可以常见无限的线程,这回触发才做系统的保护机制
JIT 优化
后端编译器的优化,默认开启,两个优化项:栈上分配、锁消除。栈上分配涉及内存逃逸和标量替换知识
1 栈上分配:对于没有发生内存逃逸的对象,该对象直接分配在栈上,不会分配在堆上,用完就出栈,不涉及 GC
-
内存逃逸:方法里的变量,既不是入参传进来的,也不会 return 出去,这个对象就没有发生内存逃逸
public User getUser(){ return new User(1, "张三"); // 返回了对象,其他地方可能会用到这个这个对象,不是当前方法独自使用,存在内存逃逸 } public void initUser(User user){ user.setAge(18); // 虽然没返回,但是 user 是入参传进来的,这里设置后,别的地方也可能使用,也不是独自使用,存在内存逃逸 } public void test(){ // 没有返回,也不是参数传进来,对象只在当前方法使用,不存在内存逃逸 User user = new User(); user... }
-
标量替换:可以使用局部变量代替对象
// User 假设有这三个属性,如果没有发生内存逃逸,会用局部变量的形式替换对象 public void test(){ int id = xxx; int age = xxx; String name = xxx; }
-
jvm 参数配置
-XX:+DoEscapeAnalysis # 开启栈上分配(当对象内存不逃逸时有用) -XX:+EliminateAllocations # 开启标量替换(两者必须同时开启,因为 hotpot 虚拟机的栈上分配是标量替换来体现的)
2 锁消除:synchronized 没必要存在的时候,会自动消除
public class TestClass{
public void test(){
User user = new User();
synchronized(user) { // test 的同步代码块的锁对象是方法内部创建的,所以每个线程调用这个方法都会创建一个对象
....
}
}
}
优化1:内存调整
增大堆内存会减少 FULL GC、提升吞吐量。但是也有弊端,当真 FULL GC 的时候要回收的垃圾也会比较多,STW 时间会增加,不能两全其美
java 官方有一些建议如下,这些都是基于 FULL GC 后,以老年代已使用空间作为参照(可以多次 FULL GC 求平均值):
- 整个堆:老年代已使用空间的 3-4 倍
- 方法区:老年代已使用空间的 1.2 - 1.5 倍
- 年轻代:老年代已使用空间 1 - 1.5 倍
- 老年代:老年代已使用空间的 2-3 倍
如何触发 FULL GC?1,生成 dump 文件;2,VisualVM 或 JConsole 工具点击【回收垃圾】功能
优化2:堆大小自适应调整策略
-XX:+UseAdaptiveSizePolicy 会自动调整堆大小,具体是伊甸园和幸存区的大小比例
默认伊甸园和幸存区的比例是 8:1:1,如果 UseAdaptiveSizePolicy 生效,伊甸园和幸存区的比例就是 6:1:1,保留了 20% 的空间来动态自适应
- 对于 Parallel GC 无论设置开启还是关闭,UseAdaptiveSizePolicy 都会生效,如果要使 UseAdaptiveSizePolicy 失效,需要设置 SurvivorRatio 参数(SurvivorRatio 是设置伊甸园和幸存区的比例的参数)
- 对于 CMS GC,默认开启 UseAdaptiveSizePolicy,可以设置
-XX:-UseAdaptiveSizePolicy
来关闭
优化3:CPU 占用高
-
先确认哪个进程占用高,多种方式
top
显示所有进程,带有 cpu、内存 等使用情况,然后按P
,根据 cpu 使用率从高到底排序ps aux --sort=-%cpu | head -n 10
这条命令会列出 CPU 使用率最高的前 10 个进程top -p $(pgrep -d',' -f 'java')
这个命令只显示 java 进程的 cpu 使用情况htop
、atop
也可以,但是要安装,有的 linux 默认没有这俩命令
-
有了进程后,就要分析该进程的线程了
top -Hp 12323
看12323 这个进程下所有线程使用资源情况,可以看出哪个线程使用 cpu 过高 -
把 线程id 转成 16 进制,因为 jstack 命令要跟 16 进制的线程id
-
jstack 进程id | grep -A20 16进制线程id
看某个进程下某个线程信息(-A20 后 20 行)
注意有可能是 GC 线程占用高,这个一半 不用做处理,要处理的话,如果是 CMS 和 G1 可以试试降低 STW 时间
优化4:内存占用高
这个没啥好说的,用命令或分析 dump,看哪个线程使用内存多了,根据 VisualVM 或 JConsole 分析 dump 文件也能看到哪个线程、哪种类型的对象使用内存高,从而定位到代码
内存泄露(是垃圾但是没有被回收):因为 java 使用的是可达性分析,这种情况的原因多半是可达性能达到,但是某个环节不适用了还引用着,比如流没关闭等情况。可以使用 jstat 命令,隔一段时间观察一下老年代使用情况,如果一直递增,说明有可能内存泄露了
优化5:诊断命令
-
jps:查看正在运行的 java 进程信息
命令 作用 jps 显示进程 ID 和主类名 jps-q 显示进程ID jps -l 显示进程 ID、主类的全类名(包括全路径) jps -v 显示进程 ID、主类名以及 JVM 启动参数 jps -m 显示进程 ID、主类名以及传递给主类的参数 jps -lvm 显示进程 ID、主类名、JVM 参数及主类参数 jps -v | grep 进程id 指定指定进程的 进程 ID、主类名以及 JVM 启动参数 - q 独立,lvm 可以组合
- v 和 m 一个虚拟机的启动参数,一个是主方法的入参
- 如果 jvm 启动参数有
-XX:-UsePerData
就不能使用 jps 命令了
-
jstat:显示正在运行的 java 进程的虚拟机信息
命令 作用 描述 jstat -class 进程id 查看进程的类加载信息 当前进程对应的 jvm 加载类的个数、时间和卸载类的个数和时间 jstat -gc 进程id 查看进程 jvm 内存信息,单位是字节 S0C、S1C:幸存区容量
S0U、S1U:幸存区使用容量
EC、EU:伊甸园区容量和已使用容量
OC、OU:老年代容量和已使用容量
MC、MU:方法区容量和已使用容量
YGC、YGCT:轻 GC 次数、轻 GC 时间
FGC、FGCT:重 GC 次数、种 GC 时间
GCT:总 GC 时间jstat -gc 进程id 1000 10 每隔1秒打印一次,总共打印10次 jstat -gcutil 进程id 查看进程 jvm 内存使用信息 gc 打印容量和使用容量,gcutil 打印使用容量 jstat -gcutil 进程id 1000 50 每个1秒答应一次,总共打印50次 jstat -gc 进程id -间隔时间 -次数
-
jinfo:查看 jvm 参数(虽然也能修改,但是修改的项很有限,主要还是查看 jvm 参数)
命令 作用 jinfo -sysprops 进程id 可以查看由 System.getProperties() 方法取得的参数 jinfo -flags 进程id 查看 jvm 参数。比如堆空间大小,使用的哪个 GC 收集器等 -
jmap:导出堆内存此时的 dum 文件或查看此时堆内存使用情况。还有别的用法,别的用的不多
命令 作用 jmap -dump:format=b,file=/root/a.hprof 进程id 导出此时的 dump 文件 jmap -dump:live,format=b,file=/root/a.hprof 进程id 功能同上,只关注存活对象 jmap -heap 进程id 显示内存情况,容量、已使用、已使用百分比 jmap -heap 进程id >/root/a.txt 功能同上,输出到一个文件中 jmap -histo 进程id 此时占用内存从大到小排列的对象,[B byte 类型数组,[C char 类型数组 jmap -histo 进程id >/root/b.txt 功能同上,输出到文件中 -
jstack:查看线程信息
命令 作用 jstack 进程id 打印当前进程所有线程信息 jstack 进程id | grep -A30 16进制线程id 打印进程指定的线程信息,注意线程id是16进制的 需要关注的状态
状态 含义 Deadlock 死锁 Waiting on condition 等待资源 Waiting on monitor entry 等待获取监视器 Blocked 阻塞
优化6:诊断工具
有很多 jconsole、visual VM、eclipse MAT、jprofileer(收费),jconsole 是官方提供的,jdk5 开始有的,visual VM 是 jdk6 之后官方提供的,相比于 jconsle,visual VM 更强大
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)