JVM 内存占用过高分析

应用检测常用命令

jps(java process status):查看正在运行的java程序的状态

jps [ options ]  pid 
jps -m  显示主函数输入的参数
jps -l  显示应用程序 主类完整 / 全限定包类名 或 jar完整名称
jps -v  列出程序启动时的 jvm 参数
jps -V   输出通过.hotsportrc 或 -XX:Flags= 指定的 jvm 参数

jmap:用于生产堆转存快照

jmap [ option ] pid
jmap [ option ] executable core
jmap [ option ] [server-id@]remote-hostname-or-IP

参数选项:

-dump:[live,]format=b,file= 使用 hprof 二进制形式,输出 jvm 的 heap 内容到文件. live子选项是可选的,假如指定 live 选项,那么只输出活的对象到文件(即dump前执行一次 FullGC)。

-finalizerinfo 打印正等候回收的对象的信息.

-heap 打印 heap 的概要信息,GC使用的算法,heap的配置等

-histo[:live] 打印每个class的实例数目,内存占用,类全名信息. VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量. 

-permstat 打印classload和jvm heap长久层的信息. 包含每个classloader的名字,活泼性,地址,父classloader和加载的class数量. 另外,内部String的数量和占用内存数也会打印出来. 

-F 强迫.在pid没有响应 -dump 或者 -histo 参数的时候使用. 在这个模式下,live子参数无效. 

-h | -help 打印辅助信息 

-J 传递参数给jmap启动的jvm. 

jmap -dump:format=b,file=heap.hprof < PID > :将 PID 进程的堆快照按二进制的方式存储到 heap.hprof 中去

jmap -heap < PID > :heap 的概要信息;
重点参数解释:

Heap Configuration:         # Heap堆配置信息
   MinHeapFreeRatio         = 0 # JVM堆最小空闲比率(default 40)
   MaxHeapFreeRatio         = 100  # JVM堆最大空闲比率(default 70)
   MaxHeapSize              = 536870912 (512.0MB) # 堆最大值,可以通过 -XX:MaxHeapSize 或 -Xmx设置
   NewSize                  = 44564480 (42.5MB) # 新生代大小
   MaxNewSize               = 178782208 (170.5MB) # 新生代的最大大小
   OldSize                  = 89653248 (85.5MB) # 老年代大小
   NewRatio                 = 2 # 新生代与老生代的大小比率 1:2
   SurvivorRatio            = 8 # 年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:8,表示Eden:Survivor=8:2
   MetaspaceSize            = 21807104 (20.796875MB) # jdk1.7有永久代,jdk1.8更换成了Metaspace
   CompressedClassSpaceSize = 1073741824 (1024.0MB)  # class信息存放的空间大小
   MaxMetaspaceSize         = 17592186044415 MB # Metaspace是使用的直接内存,理论上可以无限大(内存+硬盘缓冲区)
   G1HeapRegionSize         = 0 (0.0MB) # 设置的 G1 区域的大小。值是 2 的幂,范围是 1 MB 到 32 MB 之间。目标是根据最小的 Java 堆大小划分出约 1024/2048 个区域。

jcmd

jcmd  GC.heap_dump -all=true,进行一次不做 Full GC 的 heap dump

只记录了几个比较重要的命令,其他更多的命令及参数可参考:Java应用监测命令行工具


JVM 启动参数设置

  我们有必要在一个应用启动前,结合实际的业务场景,为它设置一些 JVM 参数,来实现更合理的分配计算机资源、记录一些信息去排查突发问题等目的。

java启动参数共分为三类:

  1. 标准参数(-),所有的 JVM 实现都必须实现这些参数的功能,而且向后兼容;
  2. 非标准参数(-X),默认 jvm 实现这些参数的功能,但是并不保证所有 jvm 实现都满足,且不保证向后兼容;
  3. 非Stable参数(-XX),此类参数各个 jvm 实现会有所不同,将来可能会随时取消,需要慎重使用;

常用参数解释

内存参数:

-XX:+HeapDumpOnOutOfMemoryError:当 OutOfMemoryError 发生时自动生成 Heap Dump 文件
(谨慎使用 HeapDumpBeforeFullGC, HeapDumpAfterFullGC,这两个在线上运行时,每次 FullGC 都会生成出dump文件,最终导致磁盘占满,影响其他服务)
-XX:HeapDumpPath=...:自动生成的 Heap Dump 文件位置

如下两种方式在 jdk8 及之前,一般设置为相同,后续的设置需要结合使用的垃圾回收算法来考虑
-Xms10m / -Xms10g:初始 Heap 的大小,heap=young generation(=eden + from + to) + old generation
-Xmx10m / -Xms10g:最大 Heap 的大小

-XX:NewSize=10m:Yong Generation 的初始大小
-XX:MaxNewSize=10g:Yong Generation 的最大值内存值

其他参数:

-XX:+PrintCommandLineFlags:记录下设置的JVM 参数,如 GC 算法、堆内存大小
-XX:+PrintGCDetails:记录详细的 GC 日志


-XX:+PrintGCDateStamps:打印具体的时间(建议使用这种,方便和请求日志对应)
-XX:+PrintGCTimeStamps:打印从 JVM 启动到现在打印日志的相对时间值,单位转化到了秒
-Xloggc:...:GC 日志记录位置

-server:server模式,特点是启动速度比较慢,但内存管理效率很高,适用于生产环境。在具有64位能力的jdk环境下将默认启用该模式,而忽略-client参数。
-client:client模式,特点是启动速度比较快,但内存管理效率不高,通常用于客户端应用程序或者 PC 应用开发和调试。

应用启动 JVM 参数使用用例

$Java_opts=-Dfile.encoding-utf-8 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:....
$java_mem_opts=-XX:NewSize=100m -XX:MaxNewSize=300M -Xms500m -Xmx500m
$java_jmx_opts=-Dcom.sun.management.jmxremote.port=5000 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false 
nohup -server  $Java_opts $java_mem_opts clazz.Main 1>/dev/null 2>&1 &

JVM 内存、CPU 高占用问题排查方向

首先应该去查看是否有错误日志记录。当从错误日志里找不到可用信息时,排查时的几个方向如下:

一、JVM 堆内存启动参数与代码目前运行场景是否相符

二、 dump出内存使用情况,分析各个类占用情况,定位是否有业务代码的问题

  在对线上高内存占用应用指向 dump 命令后(dump中,没有加入live参数),得到的文件大小和应用实际内存使用大小相差巨大,为什么会出现这种情况?????

三、查看内存回收情况,判断内存是否可以被回收

  如果内存可以被回收掉,那么是在某个操作下一些比较大的对象被创建了出来,重点从日志中定位排查这些对象;如果内存一直是一个高占用状态,那么可能是内存泄漏,重点排查 dump 文件中,导致内存泄漏的大对象。

Mat工具使用

拿到dump文件后,利用MAT解析,生成报告,记得把MAT的启动参数里面的最大内存调到足够大,如果有条件可以调到dump文件大小的2/3应该够用了。

JVM heap dump使用Mat分析

用例

应用场景介绍:
  应用基于 SpringBoot + JDK 1.8 构建,提供一个 http 接口,用于向静态成员变量中添加对象,静态成员变量中的对象不会被 GC 掉,所以当该接口请求到一定次数后,会出现内存溢出。应用启动参数如下:

-Xms500M -Xmx500M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\Software\MemoryAnalyzer-1.10.0.20200225-win32.win32.x86_64\autoHeapDump.hprof
 	public  static Map<Object,Object> OOM_MAP=new ConcurrentHashMap<>();
    static int startId=0;
	@RequestMapping("/addobj")
    @ResponseBody
    public String addobj(){
        int objNum=10000+startId;
        for (; startId < objNum; startId++) {
            Users users = new Users();
            users.setMessageId(startId);
            users.setUsername("name "+startId);
            OOM_MAP.put(startId,users);
        }
        return "oom_map success addd object "+ objNum;
    }

结论

  先放从下面列举的图片中,得到的结论。通过 mat 分析各个对象的占用的 Retained Heap ,当 Retained Heap 值已经接近 堆中老年代 分配的内存时,这个对象就应当重点排查。 Bigest Objects 查看与之有关联的类数量和类 Retained Heap 值,也可以参考 Leak Suspects 的分析。

应用启动时状态

JVM 内存占用过高分析_第1张图片

JVM 内存占用过高分析_第2张图片
JVM 内存占用过高分析_第3张图片
JVM 内存占用过高分析_第4张图片

接口被多次请求后

JVM 内存占用过高分析_第5张图片
JVM 内存占用过高分析_第6张图片
JVM 内存占用过高分析_第7张图片
JVM 内存占用过高分析_第8张图片

JVM 内存占用过高分析_第9张图片

老年代占满后的内存使用变化
结合下图和请求多次的第一张图,可知,当 Old 被占满后,会使用 eden 区域,eden 区域被占满后,GC 会释放掉一部分内存,腾出新的空间(释放的是那一部分对象,有待学习?)
JVM 内存占用过高分析_第10张图片


线上 JVM 问题记录

项目场景及问题描述:

  项目 A 在内网,提供给公司内部其他项目使用的 http 接口,A 已经运行很长一段时间了。某天,项目 B 上线,B是有前端交互功能的,在 B 的使用过程中,前端页面抛出了 SQL 执行异常。遂排查错误,发现是由于 B 在调用 A 的接口时,A 内存占用高,A 和 B 的数据库是同一台,所以 B 的页面上抛出了 SQL 执行异常。

老年代内存占用高,Full GC 频繁

项目内存分配信息:
JVM 内存占用过高分析_第11张图片
统计gc时,heap情况如下:
JVM 内存占用过高分析_第12张图片

  出现这种老年代占用过高时,可以先把堆内存设置大一些,一是可以让线上环境暂时跑起来,二是可以观察一下内存是否可以被回收掉,获取到更多的信息。

  堆内存放大4倍后,发现虽然老年代内存会迅速膨胀,但在执行一次 full gc 后,内存恢复到正常水平,所以定位这种问题,是在某一个接口被请求后,查出了大量的数据,放到了内存中,方法执行结束执行 full gc 后,内存恢复到正常水平,查找日志定位方法,处理 sql 问题,遂解决问题。

  

你可能感兴趣的:(#,JVM,#,JDK,8,java,jvm)