本文共分为四部分

JVM内存分配机制

jstat性能分析

jmap显示堆中对象的统计信息

jstack查看Java线程

 

 

一、JVM内存分配机制和GC机制

 

JVM虚拟机中的共划分为三个代:

  • 年轻代(Young Generation)、年轻代分三个区。一个Eden区,两个Survivor区(一般而

言)

  • 老年代(Old Generation TENURED)
  • 持久代(Permanent Generation)。

其中持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。年轻代和年老代的划分是对垃圾收集影响比较大的。

 

年轻代:

所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。

 

年老代:

在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

 

持久代:

用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过­XX:MaxPermSize=进行设

置。

 

内存申请流程图:

 

java老年代缓慢增长的原因 jvm 老年代_Java

流程解释:

A. JVM会试图为相关Java对象在Eden中初始化一块内存区域

B. 当Eden空间足够时,内存申请结束。否则到下一步

C. JVM试图释放在Eden中所有不活跃的对象(这属于1或更高级的垃圾回收);释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区/OLD区

D. Survivor区被用来作为Eden及OLD的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区

E. 当OLD区空间不够时,JVM会在OLD区进行完全的垃圾收集(0级)

F. 完全垃圾收集后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无

法在Eden区为新对象创建内存区域,则出现”out of memory错误”

Exception: java.lang.OutOfMemoryError: PermGen space

Exception: java.lang.OutOfMemoryError: Java heap space

 

jvm内存参数

-­vmargs -­Xms128M ­-Xmx512M ­-XX:PermSize=64M -­XX:MaxPermSize=128M

 

-vmargs 说明后面是VM的参数,所以后面的其实都是JVM的参数了

-Xms128m JVM初始分配的堆内存

-Xmx512m JVM最大允许分配的堆内存,按需分配

-XX:PermSize=64M JVM初始分配的非堆内存

-­XX:MaxPermSize=128M JVM最大允许分配的非堆内存,按需分配

 

堆(Heap)和非堆(Non­heap)内存按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non­heap memory)”。

可以看出JVM主要管理两种类型的内存:堆和非堆。简单来说堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给自己用的。

 

二、jstat(性能分析)

 

jstat(JVM Statistics Monitoring Tool)是用于监控虚拟机各种运行状态信息的命令行工具

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 -gcutil pid:统计gc信息统计。
jstat -printcompilation pid:当前VM执行的信息。

 

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

 

使用示例:

2.1、先找到tomcat对应的pid

ps -ef | grep tomcat

java老年代缓慢增长的原因 jvm 老年代_java老年代缓慢增长的原因_02

2.2、执行

jstat -gcutil 28932 1000 20

S0 — Heap上的 Survivor space 0 区已使用空间的百分比 
S1 — Heap上的 Survivor space 1 区已使用空间的百分比 
E — Heap上的 Eden space 区已使用空间的百分比 
O — Heap上的 Old space 区已使用空间的百分比 
P — Perm space 区已使用空间的百分比 
YGC — 从应用程序启动到采样时发生 Young GC 的次数 
YGCT– 从应用程序启动到采样时 Young GC 所用的时间(单位秒) 
FGC — 从应用程序启动到采样时发生 Full GC 的次数 
FGCT– 从应用程序启动到采样时 Full GC 所用的时间(单位秒) 
GCT — 从应用程序启动到采样时用于垃圾回收的总时间(单位秒)

java老年代缓慢增长的原因 jvm 老年代_JVM_03

 

 

三、 jmap(显示堆中对象的统计信息)

jmap -histo:live 28932  >> tmp.txt

java老年代缓慢增长的原因 jvm 老年代_java老年代缓慢增长的原因_04

 

通常情况下,如果Map或List或者某个自定义的对象排在第一位,则说明程序存在问题。上图中,就存在ConcurrentHashMap占用内存偏高的问题。

 

四、jstack(查看线程)

4.1、查出最高cpu占用进程id  PID

top -c

java老年代缓慢增长的原因 jvm 老年代_Java_05

 

4.2、获取到这个进程下面所有线程,通过查看%CPU找到最耗费CPU的是线程PID(shift+p 按cpu排序,shift+m 按内存排序)

top -Hp 84253

java老年代缓慢增长的原因 jvm 老年代_java老年代缓慢增长的原因_06

4.3、线程号转换成对应的16进制PID

printf '%x\n' 84303

java老年代缓慢增长的原因 jvm 老年代_JVM_07

 

4.4、使用jstack 获取对应的线程信息

jstack 84253 | grep -A 10 1494f

java老年代缓慢增长的原因 jvm 老年代_java老年代缓慢增长的原因_08

 

线程状态:

NEW:未启动的。不会出现在Dump中。

RUNNABLE:在虚拟机内执行的。运行中状态,可能里面还能看到locked字样,表明它获得了某把锁。

BLOCKED:受阻塞并等待监视器锁。被某个锁(synchronizers)給block住了。

WATING:无限期等待另一个线程执行特定操作。等待某个condition或monitor发生,一般停留在park(), wait(), sleep(),join() 等语句里。

TIMED_WATING:有时限的等待另一个线程的特定操作。和WAITING的区别是wait() 等语句加上了时间限制 wait(timeout)。 TERMINATED,已退出的。