记录一次JVM发生OOM问题的解决,包括jvisualvm工具的使用,以及对GC的理解等

OOM原因分析:

  1. 一次性申请对象数量过多
  2. 使用资源未释放
  3. 为JVM分配的空间不足

解决:

  1. 采用分页等,减少一次返回数据量的大小
  2. 检查代码中是否有未关闭的资源
  3. 修改JVM参数,合理设置堆大小

利用hprof文件分析的步骤,结合jvisualvm工具

  1. 程序启动时,提前加上参数如下参数,会在OOM发生时,导出堆文件,直接指定至目录即可,最终会生成后缀为hprof的文件
-XX:+HeapDumpOnOutOfMemoryError  
-XX:HeapDumpPath=/opt/aaa/bbb/logs

例如:
java -XX:+HeapDumpOnOutOfMemoryError  -XX:HeapDumpPath=/opt/aaa/bbb/logs aaa-web-0.0.1.jar
  1. 程序运行过程中,可到出当前堆信息,生成aaa.hprof文件
jmap -dump:live,format=b,file=aaa.hprof pid
  1. 使用JDK自带的jvisualvm工具,装入aaa.hprof进行分析

在这里插入图片描述

  1. 点击【文件】->【装入】,选择aaa.hprof,点击【类】,找到实例数较多的类信息,并且是业务代码所定义的,点击进入该类,找到一个实例,查找GCRoot

在这里插入图片描述

  1. GCRoot对象在左上角会有蓝色三角形,找到后,右键【在线程中显示】,定位至具体业务代码,参考下面3张图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

jvisualvm实时监控本地jvm

  1. java程序如果是本地运行的话,打开jvisualvm就会出现当前的进程信息,如下图

在这里插入图片描述

  1. 查看CPU、内存、类、线程等信息

在这里插入图片描述

  1. 查看线程信息。可以分析出是否创建线程过多,修改线程创建方式,采用线程池等
    在这里插入图片描述
  2. 查看GC信息,需要装Visual GC插件,在【工具】->【插件】中可下载。或者访问该链接https://visualvm.github.io/pluginscenters.html,找到对应JDK版本的插件,导入后重启即可使用。
    在这里插入图片描述

jvisualvm实时监控远程jvm

  1. 进入远程JDK路径:
cd /opt/aaa/jdk-11.0.8/bin
  1. 创建文件
touch jstatd-all.policy
  1. 编辑文件
vi jstatd-all.policy
  1. 文件内容:
grant codebase "file:/opt/aaa/jdk-11.0.8/lib/tools.jar" {
     permission java.security.AllPermission;
};
  1. 修改文件权限
chmod 777 jstatd-all.policy
  1. 执行命令
./jstatd -J-Djava.security.policy=jstatd-all.policy -J-Djava.rmi.server.hostname=172.18.98.111

执行后,可能会报下面的错误

Could not create remote object
java.security.AccessControlException: access denied ("java.util.PropertyPermission" "java.rmi.server.ignoreSubClasses" "write")
        at java.base/java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
        at java.base/java.security.AccessController.checkPermission(AccessController.java:897)
        at java.base/java.lang.SecurityManager.checkPermission(SecurityManager.java:322)
        at java.base/java.lang.System.setProperty(System.java:894)
        at jdk.jstatd/sun.tools.jstatd.Jstatd.main(Jstatd.java:140)

解决方法如下:

cd /opt/aaa/jdk-11.0.8/conf/security
vi java.policy

添加 permission java.security.AllPermission;

再回到JDK的bin目录,执行

./jstatd -J-Djava.security.policy=jstatd-all.policy -J-Djava.rmi.server.hostname=172.18.98.111
  1. 打开jvisualvm,右键左侧树结构中的【远程】->【添加远程主机】
    在这里插入图片描述
  2. 右键已添加的主机,点击【添加jstatd连接】,不用修改,直接【确认】后,展示远程服务器上所有运行的java进程。

在这里插入图片描述

对于JVM的GC理解

新创建的对象被放于Eden区,满了之后触发MinorGC,老年代满了之后Full GC。
考虑并发量较高的业务,一次请求中包含多少对象数据,大概能占用多大的内存空间,一般不要超过S0、S1区的一半,否则对象直接被存入老年代,频繁导致Full GC,影响业务流程。每次Full GC都会停掉业务线程,所以尽量减少Full GC的发生。

Minor GC

由于Eden去残存的为朝生夕死的对象,大部分都是要清除的,残存对象较少,一般采用通过复制算法。
在Eden区和S0区标记找出所有GCRoot,然后清除所有未标记对象,并把剩余对象移至S1区,等待下一次Eden区满了之后,再次通过标记清除法,将剩余对象移至S0区,S0和S1每次只会使用一个。
对象从Eden区到S0区,以及S0和S1区的交换,累计经过15次之后,将次对象放入老年代,该阈值可配置。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Full GC

老年代一般情况需要清理的的对象比较少,都是存活率较高的,所以一般采用标记整理算法或者标记清除算法。
标记清除会产生碎片化的内存,存在缺陷,而标记整理会将剩余的对象移至一端,效率没有标记清除高。
每次Full GC都会停掉业务线程,所以尽量减少Full GC的发生。

常用JVM参数配置

-Xms:2g   初始化堆大小,等价于-XX:InitialHeapSize=2g,默认初始化堆大小为物理内存的1/64
-Xmx:2g   最大堆大小,等价于-XX:MaxHeapSize=2g,最大堆内存为物理内存的1/4
-Xmn:1g   新生代大小,等价于-XX:NewSize=1g
-XX:NewRatio=2   老年代与新生代比例为2:1,默认老年代与新生代比例为2:1
-XX:SurvivorRatio=8   Eden:S0:S1为2:1:1,Eden:S0:S1为2:1:1
-XX:MaxTenuringThreshold=15   被放入老年代的年龄阈值,15次

查看JVM堆信息

查看堆信息,使用jmap -heap 7118,会出现如下提示信息,需要用jhsdb jmap

Error: -heap option used
Cannot connect to core dump or remote debug server. Use jhsdb jmap instead

jhsdb jmap --pid 7118 --heap

Attaching to process ID 7118, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0.8+10

using thread-local object allocation.
Garbage-First (G1) GC with 13 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 8392802304 (8004.0MB)
   NewSize                  = 1363144 (1.2999954223632812MB)
   MaxNewSize               = 5035261952 (4802.0MB)
   OldSize                  = 5452592 (5.1999969482421875MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 2097152 (2.0MB)

Heap Usage:
G1 Heap:
   regions  = 4002
   capacity = 8392802304 (8004.0MB)
   used     = 950722080 (906.6792297363281MB)
   free     = 7442080224 (7097.320770263672MB)
   11.327826458474863% used
G1 Young Generation:
Eden Space:
   regions  = 235
   capacity = 1874853888 (1788.0MB)
   used     = 492830720 (470.0MB)
   free     = 1382023168 (1318.0MB)
   26.286353467561522% used
Survivor Space:
   regions  = 7
   capacity = 14680064 (14.0MB)
   used     = 14680064 (14.0MB)
   free     = 0 (0.0MB)
   100.0% used
G1 Old Generation:
   regions  = 215
   capacity = 1109393408 (1058.0MB)
   used     = 445308448 (424.6792297363281MB)
   free     = 664084960 (633.3207702636719MB)
   40.1398137747002% used

一些问题的解决

执行jdk命令时,有可能会出现如下报错信息

Attaching to process ID 7118, please wait...
Error attaching to process: java.lang.RuntimeException: can't determine target's VM version : field "_reserve_for_allocation_prefetch" not found in type Abstract_VM_Version
sun.jvm.hotspot.debugger.DebuggerException: java.lang.RuntimeException: can't determine target's VM version : field "_reserve_for_allocation_prefetch" not found in type Abstract_VM_Version
        at sun.jvm.hotspot.HotSpotAgent.setupVM(HotSpotAgent.java:435)
        at sun.jvm.hotspot.HotSpotAgent.go(HotSpotAgent.java:305)
        at sun.jvm.hotspot.HotSpotAgent.attach(HotSpotAgent.java:140)
        at sun.jvm.hotspot.tools.Tool.start(Tool.java:185)
        at sun.jvm.hotspot.tools.Tool.execute(Tool.java:118)
        at sun.jvm.hotspot.tools.HeapSummary.main(HeapSummary.java:49)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at sun.tools.jmap.JMap.runTool(JMap.java:201)
        at sun.tools.jmap.JMap.main(JMap.java:130)
Caused by: java.lang.RuntimeException: can't determine target's VM version : field "_reserve_for_allocation_prefetch" not found in type Abstract_VM_Version
        at sun.jvm.hotspot.runtime.VM.<init>(VM.java:291)
        at sun.jvm.hotspot.runtime.VM.initialize(VM.java:370)
        at sun.jvm.hotspot.HotSpotAgent.setupVM(HotSpotAgent.java:431)
        ... 11 more

原因是操作系统默认的JDK版本和程序使用的JDK版本不一致,程序使用的JDK11
查看系统JDK版本:java -version

java version "1.8.0_101"
Java(TM) SE Runtime Environment (build 1.8.0_101-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, mixed mode)

可以在执行命令前,加上JDK11的路径,指定JDK为11

/opt/java/jdk-11.0.8/bin/jhsdb jmap --pid 7118 --heap

JVM调查命令总结

jstack

jstack pid

命令用于生成虚拟机当前时刻的线程快照。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因。

jinfo

jinfo -flags pid

查看进程的JVM配置信息

在这里插入图片描述

jmap

jmap -dump:live,format=b,file=heap.hprof pid

导出堆文件,并通过jvisualvm、MAT等工具进行分析

jhsdb jmap --pid pid --heap

打印heap的概要信息

在这里插入图片描述

jmap -histo pid

查看系统内存使用情况,可以找到跟业务代码相关的类信息

在这里插入图片描述

使用到的Linux命令总结

nohup

nohup /opt/cpms-server/bin/cpms-server.sh > /dev/null  > /opt/cpms-server/logs/cpms-stdout.log  2>&1 &

拆解分析:

  • nohup(no hang up
    不挂起),在系统后台不挂断的运行命令,退出终端不影响程序运行,如果不加&,虽然不会在控制台输出log,但是ctrl+C会中断程序运行;加了&后,退出终端命令依旧执行
  • /dev/null 指向一个不存在的文件,/dev/null 是一个黑洞,重定向到它的数据都会被扔掉
  • 默认 >、>> 为标准输出(stdout),即:1>、1>>
  • 标准输入0 stdin、标准输出1 stdout、标准错误2 stderr
  • 覆盖>;>>追加 2>&1,&1为取stdout的重定向输出流地址,2>将stderr输出到stdout定向文件的地址

查看防火墙命令

systemctl status firewalld

查看进程信息

ps -aux|grep java    或者 jps

查看内存使用情况

free -h

在这里插入图片描述

查看硬盘使用情况

df -hT

top

top -p pid

RES使用的物理内存大小,%MEM使用的物理内存占实际内存的百分比
在这里插入图片描述

该页面输入H后,可显示进程下的线程信息
在这里插入图片描述

查找文件

find / -name fileName

添加环境变量

export JAVA_HOME=/opt/founder/jdk-11.0.8
export PATH=$PATH:$JAVA_HOME/bin                  
使用java -version命令,提示未找到命令,原因:未设环境变量

切换用户

su username

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值