2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > 《深入理解Java虚拟机》读后总结(一)JVM内存模型

《深入理解Java虚拟机》读后总结(一)JVM内存模型

时间:2024-03-07 18:44:39

相关推荐

《深入理解Java虚拟机》读后总结(一)JVM内存模型

基于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

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。