JVM系列四(JVM参数及调优(常用命令、调优工具、JVM运行情况预估、优化思路))

常见的Java内存溢出

  • java.lang.OutOfMemoryError: Java heap space (JVM堆溢出),JVM 在启动的时候会自动设置 JVM Heap 的值,其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)不可超过物理内存。可以利用 JVM提供的 -Xmn -Xms -Xmx 等选项可进行设置。
  • java.lang.OutOfMemoryError: PermGen space :PermGen space老年代/永久代空间溢出,PermGen space 的全称是 Permanent Generation space,是指内存的永久保存区域。为什么会内存溢出,这是由于这块内存主要是被 JVM 存放Class 和 Meta 信息的,Class 在被 Load 的时候被放入 PermGen space 区域,它和存放 Instance 的 Heap 区域不同,sun 的 GC 不会在主程序运行期对 PermGen space 进行清理,所以如果你的 APP 会载入很多 CLASS 的话,就很可能出现 PermGen space 溢出
  • java.lang.StackOverflowError : 栈溢出,递归调用层数太深超过虚拟机允许的深度,就会报StackOverflowError

1. 常见的JVM调优命令工具

1.1 jps

输入命令jps后可以看到我启动的项目java进程和进程号PID,接着可以用其他命令对进程号(代表进程)进行操作。

1.2 jinfo

jinfo -flags PID命令可以实时查看和调整所有JVM参数。jinfo -flag name PID查看某个java进程的name属性值。jinfo -flag [+-]name PID 开启或者关闭对应名称的参数(只有被标记为manageable的参数才可以被动态修改)

1.3 jmap命令

jmap -histo 进程id > ./log.ext可以查看内存信息,实例个数以及占用内存大小。此命令可以用来查看内存信息,实例个数以及占用内存大小,打开log.txt,文件内容如下:

  • num:序号
  • instances:实例数量
  • bytes:占用空间大小
  • class name:类名称,[C is a char[],[S is a short[],[I is a int[],[B is a byte[],[[I is a int[][]

堆信息

jmap -heap 进程id

堆内存dump(.hprof文件)

jmap -dump:format=b,file=XXX.hprof 进程id

也可以设置内存溢出自动导出dump文件(内存很大的时候,可能会导不出来)

  • -XX:+HeapDumpOnOutOfMemoryError
  • -XX:HeapDumpPath=./ (路径)

1.4 jstack命令

使用命令top -p <pid> ,显示你的java进程的内存情况,pid是你的java进程号。接下来用jstack加进程id查找死锁,按H获取每个线程的内存情况。

jstack 进程id

流程
1、使用命令top -p ,显示你的java进程的内存情况,pid是你的java进程号,比如19663
2、按H,获取每个线程的内存情况。
3、找到内存和cpu占用最高的线程tid,比如19664
4、转为十六进制得到 0x4cd0,此为线程id的十六进制表示
5、执行 jstack 19663|grep -A 10 4cd0,得到线程堆栈信息中 4cd0 这个线程所在行的后面10行,从堆栈中可以发现导致cpu飙高的调
用方法。
6、查看对应的堆栈信息找出可能存在问题的代码

1.5 jstat命令(最常用)

jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。用于监视虚拟机运行时状态信息的命令,可以查看虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。命令的格式如下:

jstat [-命令选项] [vmid] [间隔时间(毫秒)] [查询次数]

垃圾回收统计

jstat -gc {pid}最常用,可以评估程序内存使用及GC压力整体情况。

  • S0C:第一个幸存区的大小,单位KB
  • S1C:第二个幸存区的大小
  • S0U:第一个幸存区的使用大小S1U:第二个幸存区的使用大小
  • EC:伊甸园区的大小
  • EU:伊甸园区的使用大小
  • OC:老年代大小
  • OU:老年代使用大小
  • MC:方法区大小(元空间)
  • MU:方法区使用大小
  • CCSC:压缩类空间大小
  • CCSU:压缩类空间使用大小
  • YGC:年轻代垃圾回收次数
  • YGCT:年轻代垃圾回收消耗时间,单位s
  • FGC:老年代垃圾回收次数
  • FGCT:老年代垃圾回收消耗时间,单位s
  • GCT:垃圾回收消耗总时间,单位s

堆内存统计

jstat -gccapacity {pid}可以查看堆内存的参数

  • NGCMN:新生代最小容量
  • NGCMX:新生代最大容量
  • NGC:当前新生代容量
  • S0C:第一个幸存区大小
  • S1C:第二个幸存区的大小
  • EC:伊甸园区的大小
  • OGCMN:老年代最小容量
  • OGCMX:老年代最大容量
  • OGC:当前老年代大小
  • OC:当前老年代大小
  • MCMN:最小元数据容量
  • MCMX:最大元数据容量
  • MC:当前元数据空间大小
  • CCSMN:最小压缩类空间大小
  • CCSMX:最大压缩类空间大小
  • CCSC:当前压缩类空间大小
  • YGC:年轻代gc次数
  • FGC:老年代GC次数

新生代垃圾回收统计

jstat -gcnew {pid}

新生代内存统计

jstat -gvnewcapacity {pid}

老年代垃圾回收统计

jstat -gcold {pid}

老年代内存统计

jstat -gcoldcapacity {pid}

元数据空间统计

jstat -gcmetacapacity {pid}

2. JVM运行情况预估

jstat gc -pid命令可以计算出如下一些关键数据,有了这些数据就可以使用对应的优化策略。先给自己的系统设置一些初始性的JVM参数,比如堆内存大小,年轻代大小,Eden和Survivor的比例,老年代的大小,大对象的阈值,大龄对象进入老年代的阈值等。

2.1 年轻代对象增长的速率

执行命令jstat -gc pid 1000 10(每隔1秒执行一次命令,共执行10次),通过观察EU(Eden区的实验)来估算每秒Eden区新增多少对象,如果系统负载不高,可以把频率换成1秒1分钟,甚至10分钟来观察整体情况。注:一般系统可能有高峰期和日常期,所以需要在不同的时间分别估算不同情况下对象增长速率。

2.2 Young GC的触发频率和每次耗时

知道年轻代对象增长速率即能根据Eden区的大小推算出Young GC大概多久触发一次,Young GC的平均耗时可以通过 YGCT/YGC公式算出,可以知道系统大概多久会因为Young GC的执行而卡顿多久

2.3 每次Young GC后有多少对象存活进入老年代

已经知道Young GC 频率的情况下,假设是每5分钟一次,那么可以执行命令jstat -gc pid 300000 10,观察每次Eden、Survivor和老年代内存使用的变化情况,在每次GC后Eden区一般会大幅减少,Survivor和老年代都有可能增长,这些增长的对象就是 每次Young GC后存活的对象,同时可以看出每次Young GC后进去老年代大概多少对象,可以推算出老年代对象增长速率

2.4 Full GC的触发频率和每次耗时

知道了老年代对象的增长速率就可以推算出Full GC的触发频率了,Full GC的每次耗时可以用公式FGCT/FGC计算得出。

3. 优化思路、常见的GC场景

简单来说就是尽量让每次Young GC后的存活对象小于Survivor区域的50%,都留存在年轻代里。尽量别让对象进入老年代,尽量减少Full GC的频率,避免频繁Full GC对JVM性能的影响。

3.1 Full GC比Minor GC还多

  • 元空间不够导致的饿多余Full GC
  • 显示调用System.gc()造成多余的Full GC,一般线上尽量通过-XX:+DisableExplicitGC参数禁用,如果加上了这个JCM启动参数,那么代码中调用System.gc()没有任何效果。
  • 老年代空间分配担保机制

最快速度分析完这些我们推测的原因以及优化后,我们发现young gc和full gc依然很频繁了,而且看到有大量的对象频繁的被挪动到老年代,这种情况我们可以借助jmap命令大概看下是什么对象jmap -histo PID,查到了有大量User对象产生,这个可能是问题所在,但不确定,还必须找到对应的代码确认,如何去找对应的代码了?
1、代码里全文搜索生成User对象的地方(适合只有少数几处地方的情况)
2、如果生成User对象的地方太多,无法定位具体代码,我们可以同时分析下占用cpu较高的线程,一般有大量对象不断产生,对应的方法代码肯定会被频繁调用,占用的cpu必然较高。

同时,java的代码也是需要优化的,一次查询出500M的对象出来,明显不合适,要根据之前说的各种原则尽量优化到合适的值,尽量消除这种朝生夕死的对象导致的full gc

3.2 内存泄漏

一般电商架构可能会使用多级缓存架构,就是redis加上JVM级缓存,大多数同学可能为了图方便对于JVM级缓存就简单使用一个hashmap,于是不断往里面放缓存数据,但是很少考虑这个map的容量问题,结果这个缓存map越来越大,一直占用着老年代的很多空间,时间长了就会导致full gc非常频繁,这就是一种内存泄漏,对于一些老旧数据没有及时清理导致一直占用着宝贵的内存资源,时间长了除了导致full gc,还有可能导致OOM。

这种情况完全可以考虑采用一些成熟的JVM级缓存框架来解决,比如ehcache等自带一些LRU数据淘汰算法的框架来作为JVM级的缓存。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值