用到的工具 jmap jstack jvisualvm现在叫visualVM MAT JConsole

生成的dump文件可以用jhat分析

jmap命令将程序的内存数据保存下来,jmap -dump:live,format=b,file=m.hprof PID

然后我指着监控信息,让运维看:“大哥你看这监控历史,堆内存是达到过 6G 的,只是后面 GC 了,没问题啊!”

“JVM 的垃圾回收,只是一个逻辑上的回收,回收的只是 JVM 申请的那一块逻辑堆区域,将数据标记为空闲之类的操作,不是调用 free 将内存归还给操作系统”

JVM 的自动内存管理,其实只是先向操作系统申请了一大块内存,然后自己在这块已申请的内存区域中进行“自动内存管理”。JAVA 中的对象在创建前,会先从这块申请的一大块内存中划分出一部分来给这个对象使用,在 GC 时也只是这个对象所处的内存区域数据清空,标记为空闲而已

运维:“原来是这样,那按你的意思,JVM 就不会将 GC 回收后的空闲内存还给操作系统了吗?”

JVM 还是会归还内存给操作系统的,只是因为这个代价比较大,所以不会轻易进行。而且不同垃圾回收器 的内存分配算法不同,归还内存的代价也不同。

java加jvm参数正确格式应该是nohup java -Xms800m -Xmx800m -XX:PermSize=256m -XX:MaxPermSize=512m -XX:MaxNewSize=512m -jar 你的jar包 >>/dev/null &

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/usr/local/java/jdk1.8.0_131/bin/java -jar -server -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/spb_zcmweb/8103/dump/heap/
-Djava.io.tmpdir=/data/spb_zcmweb/8103/tmp/
-Dserver.port=8103
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=5103
-Dcom.sun.management.jmxremote.rmi.port=6103
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.access.file=/usr/local/java/jdk1.8.0_131/jre/lib/management/jmxremote.access
-Xmx2G -Xms2G -XX:+DisableExplicitGC -verbose:gc -Xloggc:/data/spb_zcmweb/8103/log/gc.%t.log -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCTaskTimeStamps -XX:+PrintGCDetails -XX:+PrintGCDateStamps
-Dserver.connection-timeout=60000
-Dserver.tomcat.accept-count=1000
-Dserver.tomcat.max-threads=300
-Dserver.tomcat.min-spare-threads=65
-Dserver.tomcat.accesslog.enabled=false
-Dserver.tomcat.accesslog.directory=/data/spb_zcmweb/8103/log/
-Dserver.tomcat.accesslog.prefix=access_log
-Dserver.tomcat.accesslog.pattern=combine
-Dserver.tomcat.accesslog.suffix=.log
-Dserver.tomcat.accesslog.rotate=true
-Dserver.tomcat.accesslog.rename-on-rotate=true
-Dserver.tomcat.accesslog.request-attributes-enabled=true
-Dserver.tomcat.accesslog.buffered=true
-XX:NewRatio=4 -XX:SurvivorRatio=30 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=8 -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 -XX:ParallelGCThreads=24 -XX:ConcGCThreads=24 -XX:-UseGCOverheadLimit -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=1 -XX:+CMSParallelRemarkEnabled -XX:+CMSScavengeBeforeRemark -XX:+ParallelRefProcEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSMaxAbortablePrecleanTime=6000 -XX:CompileThreshold=10 -XX:MaxInlineSize=1024 -Dsun.net.client.defaultConnectTimeout=60000
-Dsun.net.client.defaultReadTimeout=60000
-Dnetworkaddress.cache.ttl=300 -Dsun.net.inetaddr.ttl=300
-Djsse.enableCBCProtection=false
-Djava.security.egd=file:/dev/./urandom
-Dfile.encoding=UTF-8
-Dlog.path=/data/spb_zcmweb/8103/log/
-Dspring.profiles.active=online
/data/spb_zcmweb/8103/deploy/zcmweb.jar zcmweb

最后xxx.jar 后面加–spring.config.location

jmap指定
-heap:打印jvm heap的情况
-histo:打印jvm heap的直方图。其输出信息包括类名,对象数量,对象占用大小。
-histo:live :同上,但是只答应存活对象的情况
-permstat:打印permanent generation heap情况

jmap -histo 18473 | head -30
jmap -histo 18473 | tail -30

/home/wxj/Documents/Hexo-source-files/source/_posts/通过一次线上问题排查内存过大来学习jvm基础/2021-07-30_11-41.png

从打印结果可看出,类名中存在[C、[B等内容,只知道它占用了那么大的内存,但不知道由什么对象创建的。下一步需要将其他dump出来,使用内存分析工具进一步明确它是由谁引用的、由什么对象。

Heap Dump 是Java进程所使用的内存情况在某一时间的一次快照。 以文件的形式持久化到磁盘中。

分析dump文件的几种方式
1、visualVM
2、MAT
3、jhat 15397.hprof (去localhost:7000查看)

插入内容:JDK命令行(jps、jstat、jinfo、jmap、jhat、jstack、jstatd、hprof)与JConsole
https://www.huaweicloud.com/articles/9871161f404d4e817e18db0f40e815a1.html

看本身大小时,占大头的都是char[] ,byte[]之类的,没什么意思(用jmap -histo:live pid 看的也是本身大小)。所以需要关心的是保留大小比较大的对象,看谁在引用这些char[], byte[]。

(MAT能看的信息更多,但VisualVM胜在JVM自带,用法如下:命令行输入jvisualvm,文件->装入->堆Dump->检查 -> 查找20保留大小最大的对象,就会触发保留大小的计算,然后就可以类视图里浏览,按保留大小排序了)

可以从这个图看出这个类java.lang.ref.Finalizer占用500多M,表示这其中很多不能够被回对象的对象,此时点开hisgogram视图,并通过Retained Heap进行排序,如下截图:

从图中可以看出,被线线框圈起来的三个对象占用量非常大,那说明这几个大的对象并没有被释放,那现在就可以有针对性的从代码中去找这几个对象为什么没有被释放了。
再切换到dominator_tree视图:

这里可以看到velocity渲染也存在着问题,以及数据库的请求也比较多。

https://blog.51cto.com/supercharles888/1347144 讲了shallow size Retained size区别
Shallow Size是对象本身占据的内存的大小,不包含其引用的对象。对于常规对象(非数组)的Shallow Size由其成员变量的数量和类型来定,而数组的ShallowSize由数组类型和数组长度来决定,它为数组元素大小的总和。

Retained Size=当前对象大小+当前对象可直接或间接引用到的对象的大小总和。(间接引用的含义:A->B->C,C就是间接引用) ,并且排除被GC Roots直接或者间接引用的对象

Incoming references被什么实例引用了 和 Outgoing references引用了什么实例

一般来说,如果浅堆比较小,但是深堆比较大,那么这种对象比较可疑,首先找到深堆大的对象,然后通过incoming查看哪些对象引用这个对象,通过outgoing查看这个对象内部都有哪些对象

不确定这行写的对:dominator_tree中这两个值与Histogram中的区别是:dominator_tree基于实例的角度,而historgram基于类的角度

X86是一种架构,但是他有32位的和64位的!
32位的叫x86 ,后来出现基于它的64位版,就叫X86-64,后来有时简称X64。

Histogram是类粒度的,可以找到哪个类占用的堆内存比较多;dominator tree是对象粒度的,可以用来查看哪个对象引起占用堆内存比较大。选择dominator tree以后可以 group by class 就能按class看了!!!但是可能是因为unreachable 的原因,数目对不上。

升级版 JConsole 即 jvisualvm 。