基于Sun HotSpot JVM
直接上图:
从图中看到,JVM内存分为两个主要区域,一个是所有线程共享的数据区,一个是线程隔离数据区(线程私有)
线程隔离数据区
程序计数器(Program Counter Register):
一小块内存空间,单前线程所执行的字节码行号指示器。字节码解释器工作时,通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
JVM虚拟机栈(Java Virtual Machine Stacks):
Java方法执行内存模型,用于存储局部变量,操作数栈,动态链接,方法出口等信息。是线程私有的。
本地方法栈(Native Method Stacks):
为JVM用到的Native方法服务,Sun HotSpot 虚拟机把本地方法栈和JVM虚拟机栈合二为一。是线程私有的。
线程共享的数据区
方法区(Method Area):
用于存储JVM加载的类信息、常量、静态变量、即使编译器编译后的代码等数据。
运行时常量池(Runtime Constant Pool):
是方法区的一部分,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法取得运行时常量池中。具备动态性,用的比较多的就是String类的intern()方法。
JVM堆( Java Virtual Machine Heap):
存放所有对象实例的地方。
新生代,由Eden Space 和大小相同的两块Survivor组成
旧生待,存放经过多次垃圾回收仍然存活的对象
如图:
直接内存(Direct Memory):
它并不是虚拟机运行时数据区的一部分,也不是JAVA虚拟机规范中定义的内存区域。在JDK1.4中加入了NIO类,引入了一种基于通道(Channel)于缓冲区(Buffer)的I/O方式,他可以使用Native函数库直接分配堆外内存,然后通过一个存储在JAVA堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在JAVA堆中和Native堆中来回复制数据。
基于Sun HotSpot JVM
请先了解JVM内存模型在来看此篇文章
使用对JVM不同内存区域灌入数据,导致相关区域内存溢出,来验证JVM内存分配
先看一个经典问题:
Java代码
Strings1="小金子(aub)";Strings2="小金子(aub)";Strings3="小金子"+"(aub)";Strings4=newString("小金子(aub)");Strings5="小金子"+newString("(aub)");Strings6=s4.intern();System.out.println("s1==s2:"+(s1==s2));//true;System.out.println("s1==s3:"+(s1==s3));//true;System.out.println("s2==s3:"+(s2==s3));//true;System.out.println("s1==s4:"+(s1==s4));//false;System.out.println("s1==s5:"+(s1==s5));//false;System.out.println("s4==s5:"+(s4==s5));//false;System.out.println("s1==s6:"+(s1==s6));//true;
原因就在与String对象特殊的内存分配方式:(Strings pool是JVM内存中运行时常量池的一部分)
1.String s1 = new String("小金子(aub)");
2.String s2 = "小金子(aub)";
3.String s3 = "小金子" + "(aub)";
虽然两个语句都是返回一个String对象的引用,但是jvm对两者的处理方式是不一样的。
对于第一种,jvm会马上在heap中创建一个String对象,然后将该对象的引用返回给用户。
对于第二种,jvm首先会在内部维护的strings pool中通过String的 equels 方法查找是对象池中是否存放有该String对象,如果有,则返回已有的String对象给用户,而不会在heap中重新创建一个新的String对象;如果对象池中没有该String对象,jvm则在heap中创建新的String对象,将其引用返回给用户,同时将该引用添加至strings pool中。
注意:使用第一种方法创建对象时,jvm是不会主动把该对象放到strings pool里面的,除非程序调用 String的intern方法
对于第三种,jvm会进行“+”运算符号的优化,两遍都是字符串常量会做类似于第二种的处理,如果“+”任意一边是一个变量,就会做类似第一种的处理。
JVM栈和Native Method栈内存分配:
JAVA中八个基本类型数据,在运行时都是分配在栈中的。在栈上分配的内存,随着数据的进栈出栈,方法运行完毕,或则线程结束时,自动被回收掉了。
测试代码如下:
Java代码
publicclassJvmStackOOM{privateintstackLength=1;publicvoidexecute(){try{stackLeak();}catch(Throwablee){System.out.println("stackLength:"+stackLength);e.printStackTrace();}}privatevoidstackLeak(){stackLength++;stackLeak();}}
用一个递归不断地对实例变量stackLength进行自增操作,当JVM在扩展栈时无法申请到足够的空间,将产生StackOverflowError
可以使用Jvm 参数-Xss配置栈大小,例如:-Xss2M,栈内存越大,可的栈深度越大,在内存不变的情况下,jvm可创建的线程就越少,需要合理设置。
方法区内存分配:
类信息和运行时常量将会分配到此区域。
测试代码如下:
Java代码
publicclassJvmRuntimeConstantPoolOOM{privateintruntimeConstantCount=1;publicvoidexecute(){try{runtimeConstantLeak();}catch(Throwablee){System.out.println("runtimeConstantCount:"+runtimeConstantCount);e.printStackTrace();}}privatevoidruntimeConstantLeak(){List<String>list=newArrayList<String>();while(true){list.add(String.valueOf(runtimeConstantCount++).intern());}}}
使用String的intern()方法向方法区中灌入数据,当方法区内存不足时,抛出OutOfMemoryError: PermGen space,
也可以加载过多的类的方式,测试是否有OutOfMemoryError: PermGen space异常,如果有说明类信息也是存放在方法区中的可以
使用Jvm 参数-XX:PermSize和-XX:MaxPermSize配置栈大小,例如:-XX:PermSize=10M -XX:MaxPermSize=10M
堆内存分配:
所有对象实例及数组都会在堆上分配。
堆分为新生代和老年代。新生代分为3个区域:一个eden区,和两个survivor区(互为From、To,相对的),
新建对象时首先想eden区申请分配空间,如果空间够,就直接进行分配,否则进行一次Minor GC(新生代垃圾回收)。
Minor GC后再次尝试将对象放到eden区,如果空间仍然不够,直接在老年代创建对象。
测试代码如下:
Java代码
publicclassJvmHeapOOM{privateintbojectCount=1;publicvoidexecute(){try{heapLeak();}catch(Throwablee){System.out.println("bojectCount:"+bojectCount);e.printStackTrace();}}privatevoidheapLeak(){List<OOMObject>list=newArrayList<OOMObject>();while(true){list.add(newOOMObject());bojectCount++;}}privateclassOOMObject{}}
创建多个OOMObject对象放到List中,当堆内存不足时,产生OutOfMemoryError:Java Heap space
使用Jvm 参数-Xm -Xmx -Xmn -XX:SurvivorRatio配置堆,例如:-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
本地直接内存分配:
堆外内存,NIO相关操作将在此分配内存
使用Jvm 参数-XX:MaxDirectMemorySize配置,例如:-XX:MaxDirectMemorySize=10M
所有用到的JVM启动参数:
-Xss2M 设置JVM栈内存大小
-Xms20M 设置堆内存初始值
-Xmx20M 设置堆内存最大值
-Xmn10M 设置堆内存中新生代大小
-XX:SurvivorRatio=8设置堆内存中新生代Eden 和 Survivor 比例
-XX:PermSize=10M设置方法区内存初始值
-XX:MaxPermSize=10M设置方法区内存最大值
-XX:MaxDirectMemorySize=10M设置堆内存中新生代大小
基于Sun HotSpot JVM
这里将介绍几款sun hotspot jvm 自带的监控工具:
请确保java_home/bin配置到path环境变量下,因为这些工具都在jdk的bin目录下
jps(JVM Process Status Tool):JVM机进程状况工具
用来查看基于HotSpotJVM里面所有进程的具体状态,包括进程ID,进程启动的路径等等。与unix上的ps类似,用来显示本地有权限的java进程,可以查看本地运行着几个java程序,并显示他们的进程号。使用jps时,不需要传递进程号做为参数。
Jps也可以显示远程系统上的JAVA进程,这需要远程服务上开启了jstat服务,以及RMI注及服务,不过常用都是对本对的JAVA进程的查看。
命令格式:jps[options][hostid]
常用参数说明:
-m输出传递给main方法的参数,如果是内嵌的JVM则输出为null。
-l输出应用程序主类的完整包名,或者是应用程序JAR文件的完整路径。
-v输出传给JVM的参数。
例如:
Cmd命令行代码
C:\Users\Administrator>jps-lmv1796-Dosgi.requiredJavaVersion=1.5-Xms40m-Xmx512m-XX:MaxPermSize=256m7340sun.tools.jps.Jps-lmv-Denv.class.path=.;D:\DevTools\VM\jdk1.6.0_31\\lib\dt.jar;D:\DevTools\VM\jdk1.6.0_31\\lib\tools.jar;-Dapplication.home=D:\DevTools\VM\jdk1.6.0_31-Xms8m
其中pid为1796的是我的eclipse进程,pid为7340的是jps命令本身的进程
jinfo(Configuration Info for Java):JVM配置信息工具
可以输出并修改运行时的java 进程的opts。用处比较简单,用于输出JAVA系统参数及命令行参数
命令格式:jinfo [ options ] [ pid ]
常用参数说明:
-flag 输出,修改,JVM命令行参数
例如:
Cmd命令行代码
C:\Users\Administrator>jinfo1796
将会打印出很多jvm运行时参数信息,由于比较长这里不再打印出来,可以自己试试,内容一目了然
Jstack(Stack Trace for Java):JVM堆栈跟踪工具
jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项"-J-d64“
命令格式:jstack [ option ] pid
常用参数说明:
-F 当’jstack [-l] pid’没有相应的时候强制打印栈信息
-l 长列表. 打印关于锁的附加信息,例如属于java.util.concurrent的ownable synchronizers列表.
-m 打印java和native c/c++框架的所有栈信息.
-h | -help打印帮助信息
例如:
Cmd命令行代码
C:\Users\Administrator>jstack1796-05-2211:42:38FullthreaddumpJavaHotSpot(TM)ClientVM(20.6-b01mixedmode):"Worker-30"prio=6tid=0x06514c00nid=0x1018inObject.wait()[0x056af000]java.lang.Thread.State:TIMED_WAITING(onobjectmonitor)atjava.lang.Object.wait(NativeMethod)atorg.eclipse.core.internal.jobs.WorkerPool.sleep(WorkerPool.java:188)-locked<0x1ad84a90>(aorg.eclipse.core.internal.jobs.WorkerPool)atorg.eclipse.core.internal.jobs.WorkerPool.startJob(WorkerPool.java:220)atorg.eclipse.core.internal.jobs.Worker.run(Worker.java:50)........................
jstat(JVM statistics Monitoriing Tool):JVM统计信息监视工具
对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heapsize和垃圾回收状况的监控
命令格式:jstat[option pid [interval [ s | ms ] [count] ] ]
常用参数说明:
-gcutil 输出已使用空间占总空间的百分比
-gccapacity 输出堆中各个区域使用到的最大和最小空间
例如:每隔1秒监控jvm内存一次,共监控5次
Cmd命令行代码
C:\Users\Administrator>jstat-gccapacity17961s5NGCMNNGCMXNGCS0CS1CECOGCMNOGCMXOGCOCPGCMNPGCMXPGCPCYGCFGC13632.0174720.040896.04032.04032.032832.027328.0349568.081684.081684.012288.0262144.080640.080640.0429613632.0174720.040896.04032.04032.032832.027328.0349568.081684.081684.012288.0262144.080640.080640.0429613632.0174720.040896.04032.04032.032832.027328.0349568.081684.081684.012288.0262144.080640.080640.0429613632.0174720.040896.04032.04032.032832.027328.0349568.081684.081684.012288.0262144.080640.080640.0429613632.0174720.040896.04032.04032.032832.027328.0349568.081684.081684.012288.0262144.080640.080640.04297
Cmd命令行代码
C:\Users\Administrator>jstat-gcutil17961s5S0S1EOPYGCYGCTFGCFGCTGCT0.000.000.5253.3599.77420.5139938.11938.6320.000.000.5253.3599.77420.5139938.11938.6320.000.000.5253.3599.77420.5139938.11938.6320.000.000.5253.3599.77420.5139938.11938.6320.000.000.5253.3599.77420.5139938.11938.632
一些术语的中文解释:
S0C:年轻代中第一个survivor(幸存区)的容量 (KB)
S1C:年轻代中第二个survivor(幸存区)的容量 (KB)
S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (KB)
S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (KB)
EC:年轻代中Eden(伊甸园)的容量 (KB)
EU:年轻代中Eden(伊甸园)目前已使用空间 (KB)
OC:Old代的容量 (KB)
OU:Old代目前已使用空间 (KB)
PC:Perm(持久代)的容量 (KB)
PU:Perm(持久代)目前已使用空间 (KB)
YGC:从应用程序启动到采样时年轻代中gc次数
YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)
FGC:从应用程序启动到采样时old代(全gc)gc次数
FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT:从应用程序启动到采样时gc用的总时间(s)
NGCMN:年轻代(young)中初始化(最小)的大小 (KB)
NGCMX:年轻代(young)的最大容量 (KB)
NGC:年轻代(young)中当前的容量 (KB)
OGCMN:old代中初始化(最小)的大小 (KB)
OGCMX:old代的最大容量 (KB)
OGC:old代当前新生成的容量 (KB)
PGCMN:perm代中初始化(最小)的大小 (KB)
PGCMX:perm代的最大容量 (KB)
PGC:perm代当前新生成的容量 (KB)
S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比
S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
E:年轻代中Eden(伊甸园)已使用的占当前容量百分比
O:old代已使用的占当前容量百分比
P:perm代已使用的占当前容量百分比
S0CMX:年轻代中第一个survivor(幸存区)的最大容量 (KB)
S1CMX :年轻代中第二个survivor(幸存区)的最大容量 (KB)
ECMX:年轻代中Eden(伊甸园)的最大容量 (KB)
DSS:当前需要survivor(幸存区)的容量 (KB)(Eden区已满)
TT: 持有次数限制
MTT : 最大持有次数限制
jmap( Memory Map for Java):JVM内存映像工具
打印出某个java进程(使用pid)内存内的所有‘对象’的情况(如:产生那些对象,及其数量)
命令格式:jmap [ option ] pid
常用参数说明:
-dump:[live,]format=b,file=<filename>使用二进制形式输出jvm的heap内容到文件中,live子选项是可选的,假如指定live选项,那么只输出活的对象到文件.
-histo[:live]打印每个class的实例数目,内存占用,类全名信息.VM的内部类名字开头会加上前缀”*”.如果live子参数加上后,只统计活的对象数量.
-F强迫.在pid没有相应的时候使用-dump或者-histo参数.在这个模式下,live子参数无效.
例如:以二进制形式输入当前堆内存映像到文件data.hprof中
Cmd命令行代码
jmap-dump:live,format=b,file=data.hprof1796
生成的文件可以使用jhat工具进行分析,在OOM(内存溢出)时,分析大对象,非常有用
通过使用如下参数启动JVM,也可以获取到dump文件:
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./java_pid<pid>.hprof
在jvm发生内存溢出时生成内存映像文件
jhat(JVM Heap Analysis Tool):JVM堆转储快照分析工具
用于对JAVAheap进行离线分析的工具,他可以对不同虚拟机中导出的heap信息文件进行分析,如LINUX上导出的文件可以拿到WINDOWS上进行分析,可以查找诸如内存方面的问题。
命令格式:jhat dumpfile(jmap生成的文件)
例如:分析jmap导出的内存映像
Cmd命令行代码
jhatdata.hprof
执行成功后,访问http://localhost:7000即可查看内存信息,
MAT(Memory Analyzer Tool):一个基于Eclipse的内存分析工具
官网:/mat/
update:/mat/1.2/update-site/
这是eclipse的一个插件,安装后可以打开xxx.hprof文件,进行分析,比jhat更方便使用,有些时候由于线上xxx.hprof文件过大,直接使用jhat进行初步分析了,可以的话拷贝到本地分析效果更佳。
图形化监控工具:
在JDK安装目录bin下面有两个可视化监控工具
1. JConsole(Java Monitoring and Management Console) 基于JMX的可视化管理工具。
2. VisualVM(All-in-one Java Troubleshooting Tool)随JDK发布的最强大的运行监视和故障处理程序。
推荐使用VisualVM,他有很多插件,可以更方便的监控运行时JVM