jmap
jmap 即 JVM Memory Map。
jmap 用于生成 heap dump 文件。
如果不使用这个命令,还可以使用 -XX:+HeapDumpOnOutOfMemoryError 参数来让虚拟机出现 OOM 的时候,自动生成 dump 文件。
jmap 不仅能生成 dump 文件,还可以查询 finalize 执行队列、Java 堆和永久代的详细信息,如当前使用率、当前使用的是哪种收集器等。
命令格式:
jmap [option] LVMID
option 参数:
- dump - 生成堆转储快照
- finalizerinfo - 显示在 F-Queue 队列等待 Finalizer 线程执行 finalizer 方法的对象
- heap - 显示 Java 堆详细信息
- histo - 显示堆中对象的统计信息
- permstat - to print permanent generation statistics
- F - 当-dump 没有响应时,强制生成 dump 快照
示例:jmap -dump PID 生成堆快照
dump 堆到文件,format 指定输出格式,live 指明是活着的对象,file 指定文件名
$ jmap -dump:live,format=b,file=dump.hprof 28920Dumping heap to /home/xxx/dump.hprof ...Heap dump file created
dump.hprof 这个后缀是为了后续可以直接用 MAT(Memory Anlysis Tool)打开。
示例:jmap -heap 查看指定进程的堆信息
注意:使用 CMS GC 情况下,jmap -heap 的执行有可能会导致 java 进程挂起。
jmap -heap PID[root@chances bin]# ./jmap -heap 12379Attaching to process ID 12379, please wait...Debugger attached successfully.Server compiler detected.JVM version is 17.0-b16using thread-local object allocation.Parallel GC with 6 thread(s)Heap Configuration: MinHeapFreeRatio = 40 MaxHeapFreeRatio = 70 MaxHeapSize = 83886080 (80.0MB) NewSize = 1310720 (1.25MB) MaxNewSize = 17592186044415 MB OldSize = 5439488 (5.1875MB) NewRatio = 2 SurvivorRatio = 8 PermSize = 20971520 (20.0MB) MaxPermSize = 88080384 (84.0MB)Heap Usage:PS Young GenerationEden Space: capacity = 9306112 (8.875MB) used = 5375360 (5.1263427734375MB) free = 3930752 (3.7486572265625MB) 57.761608714788736% usedFrom Space: capacity = 9306112 (8.875MB) used = 3425240 (3.2665634155273438MB) free = 5880872 (5.608436584472656MB) 36.80634834397007% usedTo Space: capacity = 9306112 (8.875MB) used = 0 (0.0MB) free = 9306112 (8.875MB) 0.0% usedPS Old Generation capacity = 55967744 (53.375MB) used = 48354640 (46.11457824707031MB) free = 7613104 (7.2604217529296875MB) 86.39733629427693% usedPS Perm Generation capacity = 62062592 (59.1875MB) used = 60243112 (57.452308654785156MB) free = 1819480 (1.7351913452148438MB) 97.06831451706046% used
jstack
jstack 用于生成 java 虚拟机当前时刻的线程快照。
线程快照是当前 java 虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。
线程出现停顿的时候通过 jstack 来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。 如果 java 程序崩溃生成 core 文件,jstack 工具可以用来获得 core 文件的 java stack 和 native stack 的信息,从而可以轻松地知道 java 程序是如何崩溃和在程序何处发生问题。另外,jstack 工具还可以附属到正在运行的 java 程序中,看到当时运行的 java 程序的 java stack 和 native stack 的信息, 如果现在运行的 java 程序呈现 hung 的状态,jstack 是非常有用的。
命令格式:
jstack [option] LVMID
option 参数:
- -F - 当正常输出请求不被响应时,强制输出线程堆栈
- -l - 除堆栈外,显示关于锁的附加信息
- -m - 如果调用到本地方法的话,可以显示 C/C++的堆栈
jps
jps(JVM Process Status Tool),显示指定系统内所有的 HotSpot 虚拟机进程。
命令格式:
jps [options] [hostid]
option 参数:
- -l - 输出主类全名或 jar 路径
- -q - 只输出 LVMID
- -m - 输出 JVM 启动时传递给 main()的参数
- -v - 输出 JVM 启动时显示指定的 JVM 参数
其中[option]、[hostid]参数也可以不写。
$ jps -l -m28920 org.apache.catalina.startup.Bootstrap start11589 org.apache.catalina.startup.Bootstrap start25816 sun.tools.jps.Jps -l -m
jstat
jstat(JVM statistics Monitoring),是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据。
命令格式:
jstat [option] LVMID [interval] [count]
参数:
- [option] - 操作参数
- LVMID - 本地虚拟机进程 ID
- [interval] - 连续输出的时间间隔
- [count] - 连续输出的次数
jhat
jhat(JVM Heap Analysis Tool),是与 jmap 搭配使用,用来分析 jmap 生成的 dump,jhat 内置了一个微型的 HTTP/HTML 服务器,生成 dump 的分析结果后,可以在浏览器中查看。
注意:一般不会直接在服务器上进行分析,因为 jhat 是一个耗时并且耗费硬件资源的过程,一般把服务器生成的 dump 文件复制到本地或其他机器上进行分析。
命令格式:
jhat [dumpfile]
jinfo
jinfo(JVM Configuration info),用于实时查看和调整虚拟机运行参数。
之前的 jps -v 口令只能查看到显示指定的参数,如果想要查看未被显示指定的参数的值就要使用 jinfo 口令
命令格式:
jinfo [option] [args] LVMID
option 参数:
-flag : 输出指定 args 参数的值-flags : 不需要 args 参数,输出所有 JVM 参数的值-sysprops : 输出系统属性,等同于 System.getProperties()
HotSpot VM 参数
详细参数说明请参考官方文档:Java HotSpot VM Options,这里仅列举常用参数。
JVM 内存配置
配置描述-Xms堆空间初始值。-Xmx堆空间最大值。-XX:NewSize新生代空间初始值。-XX:MaxNewSize新生代空间最大值。-Xmn新生代空间大小。-XX:PermSize永久代空间的初始值。-XX:MaxPermSize永久代空间的最大值。
GC 类型配置
配置描述-XX:+UseSerialGC串行垃圾回收器-XX:+UseParallelGC并行垃圾回收器-XX:+UseParNewGC使用 ParNew + Serial Old 垃圾回收器组合-XX:+UseConcMarkSweepGC并发标记扫描垃圾回收器-XX:ParallelCMSThreads=并发标记扫描垃圾回收器 = 为使用的线程数量-XX:+UseG1GCG1 垃圾回收器
辅助配置
配置描述-XX:+PrintGCDetails打印 GC 日志-Xloggc:指定 GC 日志文件名-XX:+HeapDumpOnOutOfMemoryError内存溢出时输出堆快照文件
典型配置
堆大小设置
年轻代的设置很关键。
JVM 中最大堆大小有三方面限制:
- 相关操作系统的数据模型(32-bt 还是 64-bit)限制;
- 系统的可用虚拟内存限制;
- 系统的可用物理内存限制。
整个堆大小 = 年轻代大小 + 年老代大小 + 持久代大小
- 持久代一般固定大小为 64m。使用 -XX:PermSize 设置。
- 官方推荐年轻代占整个堆的 3/8。使用 -Xmn 设置。
回收器选择
JVM 给了三种选择:串行收集器、并行收集器、并发收集器。
JVM 实战
分析 GC 日志
获取 GC 日志
获取 GC 日志有两种方式:
- 使用命令动态查看
- 在容器中设置相关参数打印 GC 日志
jstat -gc 统计垃圾回收堆的行为:
jstat -gc 1262 S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT26112.0 24064.0 6562.5 0.0 564224.0 76274.5 434176.0 388518.3 524288.0 42724.7 320 6.417 1 0.398 6.815
也可以设置间隔固定时间来打印:
$ jstat -gc 1262 2000 20
这个命令意思就是每隔 2000ms 输出 1262 的 gc 情况,一共输出 20 次
Tomcat 设置示例:
JAVA_OPTS="-server -Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m -XX:SurvivorRatio=4-verbose:gc -Xloggc:$CATALINA_HOME/logs/gc.log-Djava.awt.headless=true-XX:+PrintGCTimeStamps -XX:+PrintGCDetails-Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000-XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15"
- -Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m Xms,即为 jvm 启动时得 JVM 初始堆大小,Xmx 为 jvm 的最大堆大小,xmn 为新生代的大小,permsize 为永久代的初始大小,MaxPermSize 为永久代的最大空间。
- -XX:SurvivorRatio=4 SurvivorRatio 为新生代空间中的 Eden 区和救助空间 Survivor 区的大小比值,默认是 8,则两个 Survivor 区与一个 Eden 区的比值为 2:8,一个 Survivor 区占整个年轻代的 1/10。调小这个参数将增大 survivor 区,让对象尽量在 survitor 区呆长一点,减少进入年老代的对象。去掉救助空间的想法是让大部分不能马上回收的数据尽快进入年老代,加快年老代的回收频率,减少年老代暴涨的可能性,这个是通过将-XX:SurvivorRatio 设置成比较大的值(比如 65536)来做到。
- -verbose:gc -Xloggc:$CATALINA_HOME/logs/gc.log 将虚拟机每次垃圾回收的信息写到日志文件中,文件名由 file 指定,文件格式是平文件,内容和-verbose:gc 输出内容相同。
- -Djava.awt.headless=true Headless 模式是系统的一种配置模式。在该模式下,系统缺少了显示设备、键盘或鼠标。
- -XX:+PrintGCTimeStamps -XX:+PrintGCDetails 设置 gc 日志的格式
- -Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000 指定 rmi 调用时 gc 的时间间隔
- -XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15 采用并发 gc 方式,经过 15 次 minor gc 后进入年老代
如何分析 GC 日志
Young GC 回收日志:
2016-07-05T10:43:18.093+0800: 25.395: [GC [PSYoungGen: 274931K->10738K(274944K)] 371093K->147186K(450048K), 0.0668480 secs] [Times: user=0.17 sys=0.08, real=0.07 secs]
Full GC 回收日志:
2016-07-05T10:43:18.160+0800: 25.462: [Full GC [PSYoungGen: 10738K->0K(274944K)] [ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) [PSPermGen: 85411K->85376K(171008K)], 0.6763541 secs] [Times: user=1.75 sys=0.02, real=0.68 secs]
通过上面日志分析得出,PSYoungGen、ParOldGen、PSPermGen 属于 Parallel 收集器。其中 PSYoungGen 表示 gc 回收前后年轻代的内存变化;ParOldGen 表示 gc 回收前后老年代的内存变化;PSPermGen 表示 gc 回收前后永久区的内存变化。young gc 主要是针对年轻代进行内存回收比较频繁,耗时短;full gc 会对整个堆内存进行回城,耗时长,因此一般尽量减少 full gc 的次数
通过两张图非常明显看出 gc 日志构成:
![JVM性能调优<二>常用命令](https://i-blog.csdnimg.cn/blog_migrate/ea7cee1f73daee52bcbf06284e30e3e7.jpeg)
![JVM性能调优<二>常用命令](https://i-blog.csdnimg.cn/blog_migrate/43b10783abe14836b38632488659ed81.jpeg)
OutOfMemory(OOM)分析
OutOfMemory ,即内存溢出,是一个常见的 JVM 问题。那么分析 OOM 的思路是什么呢?
首先,要知道有三种 OutOfMemoryError:
- OutOfMemoryError:Java heap space - 堆空间溢出
- OutOfMemoryError:PermGen space - 方法区和运行时常量池溢出
- OutOfMemoryError:unable to create new native thread - 线程过多
OutOfMemoryError:PermGen space
OutOfMemoryError:PermGen space 表示方法区和运行时常量池溢出。
原因:
Perm 区主要用于存放 Class 和 Meta 信息的,Class 在被 Loader 时就会被放到 PermGen space,这个区域称为年老代。GC 在主程序运行期间不会对年老区进行清理,默认是 64M 大小。
当程序程序中使用了大量的 jar 或 class,使 java 虚拟机装载类的空间不够,超过 64M 就会报这部分内存溢出了,需要加大内存分配,一般 128m 足够。
解决方案:
(1)扩大永久代空间
- JDK7 以前使用 -XX:PermSize 和 -XX:MaxPermSize 来控制永久代大小。
- JDK8 以后把原本放在永久代的字符串常量池移出, 放在 Java 堆中(元空间 Metaspace)中,元数据并不在虚拟机中,使用的是本地的内存。使用 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 控制元空间大小。
注意:-XX:PermSize 一般设为 64M
(2)清理应用程序中 WEB-INF/lib 下的 jar,用不上的 jar 删除掉,多个应用公共的 jar 移动到 Tomcat 的 lib 目录,减少重复加载。
OutOfMemoryError:Java heap space
OutOfMemoryError:Java heap space 表示堆空间溢出。
原因:JVM 分配给堆内存的空间已经用满了。
问题定位
(1)使用 jmap 或 -XX:+HeapDumpOnOutOfMemoryError 获取堆快照。 (2)使用内存分析工具(visualvm、mat、jProfile 等)对堆快照文件进行分析。 (3)根据分析图,重点是确认内存中的对象是否是必要的,分清究竟是是内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。
内存泄露
内存泄漏是指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。
内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏随着被执行的次数越多-最终会导致内存溢出。
而因程序死循环导致的不断创建对象-只要被执行到就会产生内存溢出。
内存泄漏常见几个情况:
- 静态集合类声明为静态(static)的 HashMap、Vector 等集合通俗来讲 A 中有 B,当前只把 B 设置为空,A 没有设置为空,回收时 B 无法回收-因被 A 引用。
- 监听器监听器被注册后释放对象时没有删除监听器
- 物理连接DataSource.getConnection()建立链接,必须通过 close()关闭链接
- 内部类和外部模块等的引用发现它的方式同内存溢出,可再加个实时观察jstat -gcutil 7362 2500 70
重点关注:
- FGC — 从应用程序启动到采样时发生 Full GC 的次数。
- FGCT — 从应用程序启动到采样时 Full GC 所用的时间(单位秒)。
- FGC 次数越多,FGCT 所需时间越多-可非常有可能存在内存泄漏。
解决方案
(1)检查程序,看是否有死循环或不必要地重复创建大量对象。有则改之。
下面是一个重复创建内存的示例:
public class OOM { public static void main(String[] args) { Integer sum1=300000; Integer sum2=400000; OOM oom = new OOM(); System.out.println("往ArrayList中加入30w内容"); oom.javaHeapSpace(sum1); oom.memoryTotal(); System.out.println("往ArrayList中加入40w内容"); oom.javaHeapSpace(sum2); oom.memoryTotal(); } public void javaHeapSpace(Integer sum){ Random random = new Random(); ArrayList openList = new ArrayList(); for(int i=0;i
执行结果:
往ArrayList中加入30w内容最大内存 = 20447232已分配内存 = 20447232已分配内存中的剩余空间 = 4032576最大可用内存 = 4032576往ArrayList中加入40w内容Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:2245) at java.util.Arrays.copyOf(Arrays.java:2219) at java.util.ArrayList.grow(ArrayList.java:242) at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208) at java.util.ArrayList.add(ArrayList.java:440) at pers.qingqian.study.seven.OOM.javaHeapSpace(OOM.java:36) at pers.qingqian.study.seven.OOM.main(OOM.java:26)
(2)扩大堆内存空间
使用 -Xms 和 -Xmx 来控制堆内存空间大小。
OutOfMemoryError: GC overhead limit exceeded
原因:JDK6 新增错误类型,当 GC 为释放很小空间占用大量时间时抛出;一般是因为堆太小,导致异常的原因,没有足够的内存。
解决方案:
查看系统是否有使用大内存的代码或死循环; 通过添加 JVM 配置,来限制使用内存:
-XX:-UseGCOverheadLimit
OutOfMemoryError:unable to create new native thread
原因:线程过多
那么能创建多少线程呢?这里有一个公式:
(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads MaxProcessMemory 指的是一个进程的最大内存 JVMMemory JVM内存 ReservedOsMemory 保留的操作系统内存 ThreadStackSize 线程栈的大小
当发起一个线程的创建时,虚拟机会在 JVM 内存创建一个 Thread 对象同时创建一个操作系统线程,而这个系统线程的内存用的不是 JVMMemory,而是系统中剩下的内存: (MaxProcessMemory - JVMMemory - ReservedOsMemory) 结论:你给 JVM 内存越多,那么你能用来创建的系统线程的内存就会越少,越容易发生 java.lang.OutOfMemoryError: unable to create new native thread。
CPU 过高
定位步骤:
(1)执行 top -c 命令,找到 cpu 最高的进程的 id
(2)jstack PID 导出 Java 应用程序的线程堆栈信息。
示例:
jstack 6795"Low Memory Detector" daemon prio=10 tid=0x081465f8 nid=0x7 runnable [0x00000000..0x00000000] "CompilerThread0" daemon prio=10 tid=0x08143c58 nid=0x6 waiting on condition [0x00000000..0xfb5fd798] "Signal Dispatcher" daemon prio=10 tid=0x08142f08 nid=0x5 waiting on condition [0x00000000..0x00000000] "Finalizer" daemon prio=10 tid=0x08137ca0 nid=0x4 in Object.wait() [0xfbeed000..0xfbeeddb8] at java.lang.Object.wait(Native Method) - waiting on <0xef600848> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:116) - locked <0xef600848> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:132) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159) "Reference Handler" daemon prio=10 tid=0x081370f0 nid=0x3 in Object.wait() [0xfbf4a000..0xfbf4aa38] at java.lang.Object.wait(Native Method) - waiting on <0xef600758> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:474) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116) - locked <0xef600758> (a java.lang.ref.Reference$Lock) "VM Thread" prio=10 tid=0x08134878 nid=0x2 runnable "VM Periodic Task Thread" prio=10 tid=0x08147768 nid=0x8 waiting on condition
在打印的堆栈日志文件中,tid 和 nid 的含义:
nid : 对应的 Linux 操作系统下的 tid 线程号,也就是前面转化的 16 进制数字tid: 这个应该是 jvm 的 jmm 内存规范中的唯一地址定位
在 CPU 过高的情况下,查找响应的线程,一般定位都是用 nid 来定位的。而如果发生死锁之类的问题,一般用 tid 来定位。
(3)定位 CPU 高的线程打印其 nid
查看线程下具体进程信息的命令如下:
top -H -p 6735