个人随笔
目录
JVM(十六)、各种调优工具详解
2021-03-26 21:48:59

一、jmap

此命令可以用来查看内存信息。
先用jps命令获得进程ID

  1. jps -lv
  1. 23041 blog.jar
  2. 2375 sun.tools.jps.Jps -...

1、查看实例个数以及占用内存大小

  1. jmap -histo 23041 > ./log.txt

打开log.txt

  1. num #instances #bytes class name
  2. ----------------------------------------------
  3. 1: 37816 136896568 [B
  4. 2: 482904 97163816 [C
  5. 3: 236991 9479640 java.util.TreeMap$Entry
  6. 4: 345843 8300232 java.lang.String
  7. 5: 18467 3700312 [I
  8. 6: 23865 2100120 java.lang.reflect.Method
  9. 7: 31700 2028800 java.net.URL
  10. 8: 82756 1986144 java.lang.Long
  11. 9: 60580 1938560 org.springframework.boot.loader.jar.StringSequence
  12. 10: 60044 1921408 java.util.concurrent.ConcurrentHashMap$Node
  13. 11: 28726 1505032 [Ljava.lang.Object;
  14. 12: 12081 1346744 java.lang.Class
  15. 13: 14913 991640 [Ljava.util.HashMap$N
  1. num:序号
  2. instances:实例数量
  3. bytes:占用空间大小
  4. class name:类名称,[C is a char[],[S is a short[],[I is aint[],[B is a byte[],[[I is a int[][]

2、查看堆信息

  1. jmap -heap 23041
  1. Attaching to process ID 23041, please wait...
  2. Debugger attached successfully.
  3. Server compiler detected.
  4. JVM version is 25.181-b13
  5. using thread-local object allocation.
  6. Mark Sweep Compact GC
  7. Heap Configuration:
  8. MinHeapFreeRatio = 40
  9. MaxHeapFreeRatio = 70
  10. MaxHeapSize = 482344960 (460.0MB)
  11. NewSize = 10485760 (10.0MB)
  12. MaxNewSize = 160759808 (153.3125MB)
  13. OldSize = 20971520 (20.0MB)
  14. NewRatio = 2
  15. SurvivorRatio = 8
  16. MetaspaceSize = 21807104 (20.796875MB)
  17. CompressedClassSpaceSize = 1073741824 (1024.0MB)
  18. MaxMetaspaceSize = 17592186044415 MB
  19. G1HeapRegionSize = 0 (0.0MB)
  20. Heap Usage:
  21. New Generation (Eden + 1 Survivor Space):
  22. capacity = 112656384 (107.4375MB)
  23. used = 41996176 (40.05067443847656MB)
  24. free = 70660208 (67.38682556152344MB)
  25. 37.278114660594824% used
  26. Eden Space:
  27. capacity = 100204544 (95.5625MB)
  28. used = 41341648 (39.42646789550781MB)
  29. free = 58862896 (56.13603210449219MB)
  30. 41.257258752656966% used
  31. From Space:
  32. capacity = 12451840 (11.875MB)
  33. used = 654528 (0.62420654296875MB)
  34. free = 11797312 (11.25079345703125MB)
  35. 5.256476151315789% used
  36. To Space:
  37. capacity = 12451840 (11.875MB)
  38. used = 0 (0.0MB)
  39. free = 12451840 (11.875MB)
  40. 0.0% used
  41. tenured generation:
  42. capacity = 250109952 (238.5234375MB)
  43. used = 186887384 (178.22969818115234MB)
  44. free = 63222568 (60.293739318847656MB)
  45. 74.72209022694148% used
  46. 32094 interned Strings occupying 3814360 bytes.

3、堆内存dump

  1. jmap -dump:format=b,file=blog.hprof 23041

然后用jvisualvm工具装入打开来查看。

有时候我们当内存很大的时候可能导不出来,此时我们可以设置内存溢出自动导出dump文件。

  1. -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./路径

二、jstack

1、用jstack查找死锁

  1. jstack 线程ID

如图可以看到:

  1. Thread-1
  2. waiting to lock <0x000000076b6ef868>
  3. locked <0x000000076b6ef878>

线程1持有锁<0x000000076b6ef878>,等待锁<0x000000076b6ef868>

  1. Thread-1
  2. waiting to lock <0x000000076b6ef878>
  3. locked <0x000000076b6ef868>

线程2持有锁<0x000000076b6ef868>,等待锁<0x000000076b6ef878>
刚好互相持有互相等待所以造成了死锁。

还可以用jvisualvm自动检测死锁

2、jstack找出占用cpu最高的堆栈信息

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

  1. Unable to open socket file: target process not responding or HotSpot VM not loaded

网上说的解决办法:https://blog.csdn.net/u011250186/article/details/99676292

三、远程连接jvisualvm

1、查看实例个数以及占用内存大小

  1. java -Dcom.sun.management.jmxremote.port=8899 -Dcom.sun.management.jmxremote.ssl=false -
  2. Dcom.sun.management.jmxremote.authenticate=false -jar foo.jar

2、tomcat的JMX配置

  1. JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8899 -Dcom.sun.management.jmxremote.ssl=false -
  2. Dcom.sun.management.jmxremote.authenticate=false

jvisualvm远程连接服务需要在远程服务器上配置host(连接ip 主机名),并且要关闭防火墙

正常不会这样子操作,非常占用资源。

四、jinfo

查看正在运行的Java应用程序的扩展参数

1、查看jvm的参数

  1. jinfo -flags 23041
  1. Attaching to process ID 23041, please wait...
  2. Debugger attached successfully.
  3. Server compiler detected.
  4. JVM version is 25.181-b13
  5. Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=31457280 -XX:MaxHeapSize=482344960 -XX:MaxNewSize=160759808 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=10485760 -XX:OldSize=20971520 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops
  6. Command line:

2、查看java系统参数

  1. jinfo -sysprops 23041
  1. Attaching to process ID 23041, please wait...
  2. Debugger attached successfully.
  3. Server compiler detected.
  4. JVM version is 25.181-b13
  5. java.runtime.name = Java(TM) SE Runtime Environment
  6. java.vm.version = 25.181-b13
  7. sun.boot.library.path = /usr/local/jdk1.8.0_181/jre/lib/amd64
  8. java.protocol.handler.pkgs = org.springframework.boot.loader
  9. java.vendor.url = http://java.oracle.com/
  10. java.vm.vendor = Oracle Corporation
  11. path.separator = :
  12. file.encoding.pkg = sun.io
  13. java.vm.name = Java HotSpot(TM) 64-Bit Server VM
  14. sun.os.patch.level = unknown
  15. sun.java.launcher = SUN_STANDARD
  16. user.country = US
  17. user.dir = /usr/local/project/blog
  18. java.vm.specification.name = Java Virtual Machine Specification
  19. PID = 23041
  20. java.runtime.version = 1.8.0_181-b13
  21. java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment
  22. os.arch = amd64
  23. java.endorsed.dirs = /usr/local/jdk1.8.0_181/jre/lib/endorsed
  24. line.separator =
  25. java.io.tmpdir = /tmp
  26. java.vm.specification.vendor = Oracle Corporation
  27. os.name = Linux
  28. sun.jnu.encoding = UTF-8
  29. java.library.path = /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
  30. spring.beaninfo.ignore = true
  31. java.specification.name = Java Platform API Specification
  32. java.class.version = 52.0
  33. sun.management.compiler = HotSpot 64-Bit Tiered Compilers
  34. os.version = 3.10.0-957.5.1.el7.x86_64
  35. user.home = /root
  36. user.timezone = Asia/Shanghai
  37. catalina.useNaming = false
  38. java.awt.printerjob = sun.print.PSPrinterJob
  39. file.encoding = UTF-8
  40. java.specification.version = 1.8
  41. catalina.home = /tmp/tomcat.5457169550512388857.8081
  42. user.name = root
  43. java.class.path = blog.jar
  44. java.vm.specification.version = 1.8
  45. sun.arch.data.model = 64
  46. sun.java.command = blog.jar --server.port=8081 --spring.config.location=application-blog.yml
  47. java.home = /usr/local/jdk1.8.0_181/jre
  48. user.language = en
  49. java.specification.vendor = Oracle Corporation
  50. awt.toolkit = sun.awt.X11.XToolkit
  51. java.vm.info = mixed mode
  52. java.version = 1.8.0_181
  53. java.ext.dirs = /usr/local/jdk1.8.0_181/jre/lib/ext:/usr/java/packages/lib/ext
  54. sun.boot.class.path = /usr/local/jdk1.8.0_181/jre/lib/resources.jar:/usr/local/jdk1.8.0_181/jre/lib/rt.jar:/usr/local/jdk1.8.0_181/jre/lib/sunrsasign.jar:/usr/local/jdk1.8.0_181/jre/lib/jsse.jar:/usr/local/jdk1.8.0_181/jre/lib/jce.jar:/usr/local/jdk1.8.0_181/jre/lib/charsets.jar:/usr/local/jdk1.8.0_181/jre/lib/jfr.jar:/usr/local/jdk1.8.0_181/jre/classes
  55. java.awt.headless = true
  56. java.vendor = Oracle Corporation
  57. catalina.base = /tmp/tomcat.5457169550512388857.8081
  58. file.separator = /
  59. java.vendor.url.bug = http://bugreport.sun.com/bugreport/
  60. sun.io.unicode.encoding = UnicodeLittle
  61. sun.font.fontmanager = sun.awt.X11FontManager
  62. sun.cpu.endian = little
  63. sun.cpu.isalist =

五、jstat

jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。命令的格式如下:
jstat [-命令选项] [vmid] [间隔时间(毫秒)] [查询次数]
注意:使用的jdk版本是jdk8.

1、垃圾回收统计

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

  1. jstat -gc 23041

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

2、堆内存统计

  1. jstat -gccapacity 23041

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

3、新生代垃圾回收统计

  1. jstat -gcnew 23041

S0C:第一个幸存区的大小
S1C:第二个幸存区的大小
S0U:第一个幸存区的使用大小
S1U:第二个幸存区的使用大小
TT:对象在新生代存活的次数
MTT:对象在新生代存活的最大次数
DSS:期望的幸存区大小
EC:伊甸园区的大小
EU:伊甸园区的使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间

4、新生代内存统计

  1. jstat -gcnewcapacity 23041

NGCMN:新生代最小容量
NGCMX:新生代最大容量
NGC:当前新生代容量
S0CMX:最大幸存1区大小
S0C:当前幸存1区大小
S1CMX:最大幸存2区大小
S1C:当前幸存2区大小
ECMX:最大伊甸园区大小
EC:当前伊甸园区大小
YGC:年轻代垃圾回收次数
FGC:老年代回收次数

5、老年代垃圾回收统计

  1. jstat -gcold 23041

MC:方法区大小
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
OC:老年代大小
OU:老年代使用大小
YGC:年轻代垃圾回收次数
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间

6、老年代内存统计

  1. jstat -gcoldcapacity 23041

OGCMN:老年代最小容量
OGCMX:老年代最大容量
OGC:当前老年代大小
OC:老年代大小
YGC:年轻代垃圾回收次数
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间

7、元数据空间统计

  1. jstat -gcmetacapacity 23041

MCMN:最小元数据容量
MCMX:最大元数据容量
MC:当前元数据空间大小
CCSMN:最小压缩类空间大小
CCSMX:最大压缩类空间大小
CCSC:当前压缩类空间大小
YGC:年轻代垃圾回收次数
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间

8、各代内存使用比例

  1. jstat -gcutil 23041

S0:幸存1区当前使用比例
S1:幸存2区当前使用比例
E:伊甸园区使用比例
O:老年代使用比例
M:元数据区使用比例
CCS:压缩使用比例
YGC:年轻代垃圾回收次数
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间

六、JVM运行情况预估

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

1、年轻代对象增长的速率

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

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

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

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

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

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

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

5、优化思路

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

七、内存泄露到底是怎么回事

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

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

 256

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2