java程序内存泄漏排查

一个java应用越跑越慢,如何排查?

首先通过jps找到java进程ID。然后top -p [pid]发现内存占用是否达到了最大值(-Xmx)。开始怀疑是由于频繁Full GC导致的,于是通过:
jstat -gcutil [pid] 60000

查看GC的情况,其中60000表示每隔60秒钟输出一次。果然是Full GC次数太多,JVM大部分时间都进行Full GC,而此时JVM会暂停其他一切工作,所以程序会运行得非常慢。

那到底的程序的哪一部分导致消耗了这么多的内存呢?
jmap -histo:live [pid]

查看进程中各种类型的对象创建了多少个,以及每种类型的对象占多少内存。当看到有个对象被创建了1千多万个实例时就能定位到是哪里的问题了。

另外说一下,通过jmap还可以生成JVM的内存dump文件,命令为:
jmap -dump:format=b,file=文件名 [pid],

然后通过jhat命令在浏览器中查看,或者通过jvisualvm、eclipse memory analyzer等工具进行查看。使用jhat命令查看的方式为:
jhat -J-Xmx4096M [file]

这个内存用量可以跟据实际情况调整,等控制台输出:Started HTTP server on port 7000. Server is ready,后在浏览器中输入ip:7000就可以查看各上类中各种实例被创建了多少个。

jinfo:可以输出并修改运行时的java 进程的opts。

jps:与unix上的ps类似,用来显示本地的java进程,可以查看本地运行着几个java程序,并显示他们的进程号。

jstat:一个极强的监视VM内存工具。可以用来监视VM内存内的各种堆和非堆的大小及其内存使用量。

jmap:打印出某个java进程(使用pid)内存内的所有'对象'的情况(如:产生那些对象,及其数量)。

jconsole:一个java GUI监视工具,可以以图表化的形式显示各种数据,并可通过远程连接监视远程的服务器VM。

详细用法:在使用这些工具前,先用jps命令获取当前的每个JVM进程号,然后选择要查看的JVM。

查看java进程内存使用情况的常用命令:jstat、jmap。


java程序占用CPU过高排查

查看进程占多少CPU

#top -p 28296

 查看进程里各个线程占多少CPU
 #top -p 28296 -H

   PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                  
  9731 web       20   0 18.9g 2.6g  13m S  0.3  2.8   0:40.64 /usr/local/jdk1.8.0_11/jre/bin/java -Djava.util.logging.config.file=/usr/local/tomcat7/208
 17228 web       20   0 18.9g 2.6g  13m S  0.3  2.8   0:10.90 /usr/local/jdk1.8.0_11/jre/bin/java -Djava.util.logging.config.file=/usr/local/tomcat7/208
 ...
 19628 web       20   0 18.9g 2.6g  13m S  0.3  2.8   0:03.98 /usr/local/jdk1.8.0_11/jre/bin/java -Djava.util.logging.config.file=/usr/local/tomcat7/208

 查看线程的执行栈信息
 先把线程号转换为16进制。
 #printf '%0x\n' 17228
 434c

 再通过jstack命令看看这个线程在做什么:
 $jstack 28296 | grep -A 10 434c



持续执行上述jstack命令多次,不出意料你会发现代码总是停留在某一行或某一个循环块内,如果是停留在某一行说明该行代码执行起来非常耗CPU,如果是停留在某一个循环块内说明这很可能是个死循环。

盘点一些java性能监控及工具
 

Sun JDK自带监控和故障处理工具

jps

显示Hotspot虚拟机继承

jstat

收集Hotspot虚拟机的运行数据

jinfo

显示虚拟机配置信息

jmap

生成虚拟机的内存转储快照(文件)

jhat

用于分析heapdump文件,建立HTTP server用于浏览器访问。

jstack

显示虚拟机的线程快照

在JDK1.5中需要手动来设置“-Dcom.sun.management.jmxremote”开启JMX管理功能,部分工具是基于JMX的,在JDK1.6后该功能默认是开启的。

jps [options] [hostid]

jps和Linux的ps命令类似,查询的是虚拟机的进程。可以显示执行主类、LVMID(本地虚拟机唯一ID)等,LVMID和系统的PID是一致的。

如 >jps -l
 6632 sun.tools.jps.Jps
 6640 org.jetbrains.idea.maven.server.RemoteMavenServer

 jps工具主要选项

 -q:只输出LVMID,省略主类名称

 -m:输出虚拟机进程启动时传递给main()函数的参数,如果是内嵌的JVM则使出为null

 -l:输出应用程序主类的完整包名,或者是应用程序JAR文件的完整路径

 -v:输出虚拟机进程启动时JVM参数


jstat [ option vmid [interval [s|ms] [count] ] ]

 jstat用于监视虚拟机各种运行状态信息。可以显示类装载、内存、垃圾收集、JIT编译等运行数据。对于本地虚拟机,VMID和LVMID是一样的,如果是远程的则VMID变成远程的格式。jstat是JDK自带的一个轻量级小工具。全称"Java VirtualMachine statistics monitoring tool",它位于Java的bin目录下,主要利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控。

 interval和count表示查询间隔和次数。

 如> jstat -gc 632 250 20

 每250毫秒查询一次垃圾收集,共20次

 jstat工具主要选项

 -class
 监视类装载、卸载数量、总空间及类装载所耗费的时间

 -gc
 监视Java堆状况,Eden区、2个survivor区、老年代、永久代等

 -gccapacity
 和-gc类似,主要关注各区域最大最小空间

 -gcutil
 和-gc类似,主要关注已使用空间的百分比

 -gccause
 和-gcutil一样,会输出上一次gc的原因

 -gcnew
 监视新生代GC状况

 -gcnewcapacity
 和-gcnew类似,主要关注最大最小空间

 -gcold
 监视老年代GC状况

 -gcoldcapacity
 同上

 -gcpermcapacity
 永久代的最大最小空间

 -compiler
 输出JIT编译器编译过的方法,耗时等信息

 -printcompilation
 输出已被JIT编译的方法

 jstat工具特别强大,有众多的可选项,详细查看堆内各个部分的使用量,以及加载类的数量。使用时需加上查看进程的进程id和所选参数。以下详细介绍各个参数的意义。
 jstat -class pid:显示加载class的数量,及所占空间等信息。
 jstat -compiler pid:显示VM实时编译的数量等信息。
 jstat -gc pid:可以显示gc的信息,查看gc的次数,及时间。其中最后五项,分别是young gc的次数,young gc的时间,full gc的次数,full gc的时间,gc的总时间。
 jstat -gccapacity:可以显示,VM内存中三代(young,old,perm)对象的使用和占用大小,如:PGCMN显示的是最小perm的内存使用量,PGCMX显示的是perm的内存最大使用量,PGC是当前新生成的perm内存占用量,PC是但前perm内存占用量。其他的可以根据这个类推, OC是old内纯的占用量。
 jstat -gcnew pid:new对象的信息。
 jstat -gcnewcapacity pid:new对象的信息及其占用量。
 jstat -gcold pid:old对象的信息。
 jstat -gcoldcapacity pid:old对象的信息及其占用量。
 jstat -gcpermcapacity pid: perm对象的信息及其占用量。
 jstat -util pid:统计gc信息统计。
 jstat -printcompilation pid:当前VM执行的信息。

 除了以上一个参数外,还可以同时加上 两个数字,如:jstat -printcompilation 3024 250 6 是每250毫秒打印一次,一共打印6次,还可以加上-h3每三行显示一下标题。


jinfo [ option ] pid

 jinfo能实时查看和调整虚拟机的各项参数。用法比较简单,就是能输出并修改运行时的java进程的运行参数。用法是jinfo -opt pid 如:查看2758的MaxPerm大小可以用:jinfo -flag MaxPermSize 2758。

 如> jinfo -flag CMSInitiatingOccupancyFraction 6632


jmap [ option ] vmid

 jmap用于生成转储快照(heapdump)、查询堆和永久代详细信息等。jmap是一个可以输出所有内存中对象的工具,可以将VM 中的heap,以二进制输出成文本。使用方法 jmap -histo pid。可采用jmap -histo pid>a.log日志将其保存到文件中,间隔一段时间后,使用文本对比工具,可以对比出GC回收了哪些对象。jmap-dump:format=b,file=outfile <pid>可以将进程号为<pid>进程的内存heap输出到outfile文件中,然后再配合MAT(内存分析工具)进行分析。

 命令:jmap -dump:format=b,file=heap.bin <pid>
 file:保存路径及文件名
 pid:进程编号
 jmap -histo:live pid| less :堆中活动的对象以及大小
 jmap -heap pid : 查看堆的使用状况信息

 命令格式:
 jmap [ option ] pid

 jmap工具主要选项

 -dump
 生成heap文件。-dump:[live,]format-b,file=<filename> 其中live说明只dump出存活的对象

 -finalizerinfo
 显示F-Queue中等待Finalizer线程执行finalize方法的对象(Linux/Solaris有效)

 -heap
 显示堆详细信息(Linux/Solaris有效)

 -histo
 显示堆中对象统计信息,包括类、实例数量和合计容量

 -permstat
 显示永久代内存状态(Linux/Solaris有效)

 -F
 -dump没有响应时,可使用这个选项强制生成dump文件(Linux/Solaris有效)。
 如> jmap -dump:format=b,file=dumpfile.log 6632

 基本参数:
 1、-dump:[live,]format=b,file=<filename> 使用hprof二进制形式,输出jvm的heap内容到文件=. live子选项是可选的,假如指定live选项,那么只输出活的对象到文件.
 $jmap–dump:live,format=b,file=aaa.bin 3772

 2、-finalizerinfo 打印正等候回收的对象的信息
 $jmap -finalizerinfo 3772

 Attaching to process ID 3772, please wait...
 Debugger attached successfully.
 Server compiler detected.
 JVM version is 20.0-b11
 Number of objects pending for finalization: 0 (等候回收的对象为0个)

 3、-heap 打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情况.
 $jmap –heap 3772
 using parallel threads in the new generation.  ##新生代采用的是并行线程处理方式
 using thread-local object allocation.   
 Concurrent Mark-Sweep GC   ##同步并行垃圾回收

 Heap Configuration:  ##堆配置情况
    MinHeapFreeRatio = 40 ##最小堆使用比例
    MaxHeapFreeRatio = 70 ##最大堆可用比例
    MaxHeapSize      = 2147483648 (2048.0MB) ##最大堆空间大小
    NewSize          = 268435456 (256.0MB) ##新生代分配大小
    MaxNewSize       = 268435456 (256.0MB) ##最大可新生代分配大小
    OldSize          = 5439488 (5.1875MB) ##老生代大小
    NewRatio         = 2  ##新生代比例
    SurvivorRatio    = 8 ##新生代与suvivor的比例
    PermSize         = 134217728 (128.0MB) ##perm区大小
    MaxPermSize      = 134217728 (128.0MB) ##最大可分配perm区大小

 Heap Usage: ##堆使用情况
 New Generation (Eden + 1 Survivor Space):  ##新生代(伊甸区 + survior空间)
    capacity = 241631232 (230.4375MB)  ##伊甸区容量
    used     = 77776272 (74.17323303222656MB) ##已经使用大小
    free     = 163854960 (156.26426696777344MB) ##剩余容量
    32.188004570534986% used ##使用比例
 Eden Space:  ##伊甸区
    capacity = 214827008 (204.875MB) ##伊甸区容量
    used     = 74442288 (70.99369812011719MB) ##伊甸区使用
    free     = 140384720 (133.8813018798828MB) ##伊甸区当前剩余容量
    34.65220164496263% used ##伊甸区使用情况
 From Space: ##survior1区
    capacity = 26804224 (25.5625MB) ##survior1区容量
    used     = 3333984 (3.179534912109375MB) ##surviror1区已使用情况
    free     = 23470240 (22.382965087890625MB) ##surviror1区剩余容量
    12.43827838477995% used ##survior1区使用比例
 To Space: ##survior2 区
    capacity = 26804224 (25.5625MB) ##survior2区容量
    used     = 0 (0.0MB) ##survior2区已使用情况
    free     = 26804224 (25.5625MB) ##survior2区剩余容量
    0.0% used ## survior2区使用比例
 concurrent mark-sweep generation: ##老生代使用情况
    capacity = 1879048192 (1792.0MB) ##老生代容量
    used     = 30847928 (29.41887664794922MB) ##老生代已使用容量
    free     = 1848200264 (1762.5811233520508MB) ##老生代剩余容量
    1.6416783843721663% used ##老生代使用比例
 Perm Generation: ##perm区使用情况
    capacity = 134217728 (128.0MB) ##perm区容量
    used     = 47303016 (45.111671447753906MB) ##perm区已使用容量
    free     = 86914712 (82.8883285522461MB) ##perm区剩余容量
    35.24349331855774% used ##perm区使用比例

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

 num     #instances         #bytes  class name
 ----------------------------------------------
    1:         65220        9755240  <constMethodKlass>
    2:         65220        8880384  <methodKlass>
    3:         11721        8252112  [B
    4:          6300        6784040  <constantPoolKlass>
    5:         75224        6218208  [C
    6:         93969        5163280  <symbolKlass>
    7:          6300        4854440  <instanceKlassKlass>
    8:          5482        4203152  <constantPoolCacheKlass>
    9:         72097        2307104  java.lang.String
   10:         15102        2289912  [I
   11:          4089        2227728  <methodDataKlass>
   12:         28887        1386576  org.apache.velocity.runtime.parser.Token

 classname是对象类型,说明如下:
 B  byte
 C  char
 D  double
 F  float
 I  int
 J  long
 Z  boolean
 [  数组,如[I表示int[]
 [L+类名其他对象

 5、-permstat 打印classload和jvm heap长久层的信息. 包含每个classloader的名字,活泼性,地址,父classloader和加载的class数量. 另外,内部String的数量和占用内存数也会打印出来.
 $jmap -permstat 3772
 class_loader    classes bytes   parent_loader   alive?  type

 <bootstrap>     2172    13144040          null          live    <internal>
 0x00000007882d7ab8      0       0       0x0000000788106c00      dead    java/util/ResourceBundle$RBClassLoader@0x00000007f83b0388
 0x0000000788c15ca8      1       3136    0x00000007880213d8      dead    sun/reflect/DelegatingClassLoader@0x00000007f80686e0
 0x0000000788fb1718      1       1968    0x00000007880213d8      dead    sun/reflect/DelegatingClassLoader@0x00000007f80686e0
 0x00000007882d0f08      1       2008    0x00000007880213d8      dead    sun/reflect/DelegatingClassLoader@0x00000007f80686e0
 0x0000000788176c60      1       3112    0x00000007880213d8      dead    sun/reflect/DelegatingClassLoader@0x00000007f80686e0
 0x0000000788a7e018      1       3144    0x00000007880213d8      dead    sun/reflect/DelegatingClassLoader@0x00000007f80686e0
 0x0000000788f515d0      1       1984    0x00000007880213d8      dead    sun/reflect/DelegatingClassLoader@0x00000007f80686e0
 0x000000078829a2c8      1       3112    0x00000007880213d8      dead    sun/reflect/DelegatingClassLoader@0x00000007f80686e0
 0x0000000788fab478      1       3128      null          dead    sun/reflect/DelegatingClassLoader@0x00000007f80686e0
 0x0000000788030fd8      1       3112    0x00000007880213d8      dead    sun/reflect/DelegatingClassLoader@0x00000007f80686e0
 0x0000000788d46048      1       3144    0x00000007880213d8      dead    sun/reflect/DelegatingClassLoader@0x00000007f80686e0
 0x000000078816f6f0      1       3144      null          dead    sun/reflect/DelegatingClassLoader@0x00000007f80686e0
 0x0000000788c18850      1       3112    0x00000007880213d8      dead    sun/reflect/DelegatingClassLoader@0x00000007f80686e0

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

 7、-h | -help 打印辅助信息

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

 9、内存分析简单使用

 1.查看堆内存总体概况
 jmap -heap 进程ID

 2.查看堆中对象的数量以及大小
 jmap -histo 进程ID

 加上live参数则是过滤出活动的对象信息
 jmap -histo:live 进程ID

 3.统计gc信息
 jmap -gcutil 进程ID 时间间隔(ms)

 4.使用界面工具分析内存泄漏情况

 1)以二进制形式输出堆内存信息;
 jmap -dump:format=b,file=heap.bin 进程ID

 2)下载生成的文件heap.bin至本地电脑,利用MemoryAnalyzer等图形化工具可以直观的分析出内存泄漏情况,内存对象查看工具MemoryAnalyzer,可以查看dump时对象数量,内存占用,线程情况等。


jhat <heapdumpfile>

 jhat和jmap配合使用。来分析jmap生成的转储快照文件,内置了一个微型的HTTP服务器,供用户在浏览器来访问分析接口。除了这个,还有VisualVM、Eclipse Memory Analyzer、IBM HeapAnalyzer等都能更专业的分析heap文件。


jstack [ option ] vmid

 Java堆栈跟踪工具,用于生成虚拟机当前时刻的线程快照(threaddump文件)。打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项"-J-d64",可查看jvm线程运行状态,是否有死锁现象等等信息 : jstack pid : thread dump
 示例:jstat -gcutil  pid 1000 100 :1000ms统计一次gc情况统计100次;

 jstack工具主要选项

 -F:正常输出请求不响应时,强制输出线程堆栈

 -l:除堆栈外,显示关于锁的附加信息

 -m:调用本地方法的话,可以显示C/C++堆栈

 如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题。另外,jstack工具还可以附属到正在运行的java程序中,看到当时运行的java程序的java stack和native stack的信息,如果现在运行的java程序呈现hung的状态,jstack是非常有用的。

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

 1)、options:
 executable Java executable from which the core dump was produced.
 (可能是产生core dump的java可执行程序)
 core 将被打印信息的core dump文件
 remote-hostname-or-IP 远程debug服务的主机名或ip
 server-id 唯一id,假如一台主机上多个远程debug服务

 2)、基本参数:
 -F当’jstack [-l] pid’没有相应的时候强制打印栈信息
 -l长列表. 打印关于锁的附加信息,例如属于java.util.concurrent的ownable synchronizers列表.
 -m打印java和native c/c++框架的所有栈信息.
 -h | -help打印帮助信息
 pid 需要被打印配置信息的java进程id,可以用jps查询.


可视化工具

 JDK默认提供了两个可视化工具:Jconsole和VisualVM。Jconsole是JDK1.5提供的虚拟机监控工具,1.6后又推出了更强大的VisualVM。其中VisualVM除了能提供基本的监控功能外,最强大的是它的插件机制,基于它能扩展各种各样的插件用于监控调试。


 jconsole是一个用java写的GUI程序,用来监控VM,并可监控远程的VM,非常易用,而且功能非常强。使用方法:命令行里打 jconsole,选则进程就可以了。JConsole中关于内存分区的说明:
 Eden Space (heap): 内存最初从这个线程池分配给大部分对象。

 Survivor Space (heap):用于保存在eden space内存池中经过垃圾回收后没有被回收的对象。

 Tenured Generation (heap):用于保持已经在 survivor space内存池中存在了一段时间的对象。

 Permanent Generation (non-heap): 保存虚拟机自己的静态(refective)数据,例如类(class)和方法(method)对象。Java虚拟机共享这些类数据。这个区域被分割为只读的和只写的。
 Code Cache (non-heap):HotSpot Java虚拟机包括一个用于编译和保存本地代码(native code)的内存,叫做“代码缓存区”(code cache)


 

附JVM常用参数

参数

说明

备注

-Xms

最小堆容量,新生代旧生代的初始化容量

默认物理内存1/64(新生代+旧生代)-Xms512m

-Xmx

最大堆容量,包括新生代和旧生代

默认物理内存1/4(新生代+旧生代)-Xmx1024m

-Xmn

新生代容量,包括Eden,S1,S0

新生代增大对导致旧生代减小,对系统会有较大影响。Sun推荐比例是新生代为整个堆的3/8

-XX:NewSize

新生代容量

For 1.3/1.4

-XX:MaxNewSize

新生代最大容量

For 1.3/1.4

-XX:PermSize

持久代的初始值

默认物理内存1/64(-XX:PermSize=256m)

-XX:MaxPermSize

持久代最大值

默认物理内存1/4()

-Xss

每个线程的堆栈大小

JDK5.0以后默认为1M

-XX:SurvivorRatio

堆中新生代对比例(Eden对Survivor的倍数,如值为5,即Eden占5/7,S1和S0各占1/7)


-XX:+DisableExplicitGC

关闭System.gc()


-XX:MaxTenuringThreshold

垃圾最大年龄

在新生代中,Eden、S0、S1之间存活的周期(复制的次数),默认15

-XX:PretenureSizeThreshold

对象大于这个值则直接分配到老年代(对Serial和ParNew有效)


-XX:+UseParallelGC



-XX:+UseParNewGC

新生代使用ParNew进行收集(并行)

可与CMS收集同时使用

JDK5.0以上会根据系统配置自行设置

-XX:ParallelGCThreads

并行收集的线程数

最好与处理器数相等

-XX:+UseParallelOldGC

旧生代使用Parallel进行收集(并行收集)


-XX:GCTimeRatio

垃圾回收时间占程序运行时间的百分比

1(1+n)

-XX:+UseConcMarkSweepGC

使用CMS收集


-XX:+PrintGC

每次GC时打印日志


-XX:+PrintGCDetails

打印详细日志


-XX:+PrintGCTimeStamps

打印时间戳


-XX:+PrintGC:PrintGCTimeStamps



-XX:+PrintGCApplicationStoppedTime

打印出垃圾回收期间程序暂停的时间


-Xloggc:filename

相关日志输出到文件