JVM详解.docx

上传人:b****6 文档编号:7305599 上传时间:2023-01-22 格式:DOCX 页数:35 大小:1.06MB
下载 相关 举报
JVM详解.docx_第1页
第1页 / 共35页
JVM详解.docx_第2页
第2页 / 共35页
JVM详解.docx_第3页
第3页 / 共35页
JVM详解.docx_第4页
第4页 / 共35页
JVM详解.docx_第5页
第5页 / 共35页
点击查看更多>>
下载资源
资源描述

JVM详解.docx

《JVM详解.docx》由会员分享,可在线阅读,更多相关《JVM详解.docx(35页珍藏版)》请在冰豆网上搜索。

JVM详解.docx

JVM详解

JVM详解

本文详细讲解了JVM(JavaVirtualMachine)的方方面面,首先由java的特性来描绘JVM的大致应用,再细细阐述了JVM的原理及内存管理机制和调优.最后讲述了与JVM密切相关的JavaGC机制.

本文内容大多来自网络,但内容十分丰富,是学习JVM的好资料.

后面会再针对JVM的两大职责classloader和executionengine进行讲解

目录

Java相关1

1.1Java定义1

1.2Java的开发流程1

1.3Java运行的原理2

1.4半编译半解释3

1.5平台无关性4

JVM内存模型4

2.1JVM规范5

2.2SunJVM8

2.3SUNJVM内存管理(优化)10

2.4SUNJVM调优13

2.5.JVM简单理解16

2.5.1 Java栈16

2.5.2 堆16

2.5.3 堆栈分离的好处20

2.5.4堆(heap)和栈(stack)20

JAVA垃圾收集器21

3.1 垃圾收集简史21

3.2 常见的垃圾收集策略21

3.2.1 Reference Counting(引用计数)22

3.2.2 跟踪收集器22

3.3 JVM的垃圾收集策略27

3.3.1 Serial Collector28

3.3.2 Parallel Collector29

3.3.3 Concurrent Collector30

Java虚拟机(JVM)参数配置说明30

Java相关

1.1Java定义

1.2Java的开发流程

 

1.3Java运行的原理

 

1.4半编译半解释

1.5平台无关性

JVM内存模型

2.1JVM规范

JVMspecification对JVM内存的描述

首先我们来了解JVMspecification中的JVM整体架构。

如下图:

 

    主要包括两个子系统和两个组件:

Classloader(类装载器)子系统,Executionengine(执行引擎)子系统;Runtimedataarea(运行时数据区域)组件,Nativeinterface(本地接口)组件。

    Classloader子系统的作用:

根据给定的全限定名类名(如java.lang.Object)来装载class文件的内容到Runtimedataarea中的methodarea(方法区域)。

Javsa程序员可以extendsjava.lang.ClassLoader类来写自己的Classloader。

     Executionengine子系统的作用:

执行classes中的指令。

任何JVMspecification实现(JDK)的核心是Executionengine,换句话说:

Sun的JDK和IBM的JDK好坏主要取决于他们各自实现的Execution engine的好坏。

每个运行中的线程都有一个Executionengine的实例。

    Nativeinterface组件:

与nativelibraries交互,是其它编程语言交互的接口。

 

    Runtimedataarea组件:

这个组件就是JVM中的内存。

下面对这个部分进行详细介绍。

Runtimedataarea的整体架构图

Runtimedataarea主要包括五个部分:

Heap(堆),MethodArea(方法区域),JavaStack(java的栈),ProgramCounter(程序计数器),Nativemethodstack(本地方法栈)。

Heap和MethodArea是被所有线程的共享使用的;而Javastack,Programcounter和Nativemethodstack是以线程为粒度的,每个线程独自拥有。

Heap

Java程序在运行时创建的所有类实例或数组都放在同一个堆中。

而一个Java虚拟实例中只存在一个堆空间,因此所有线程都将共享这个堆。

每一个java程序独占一个JVM实例,因而每个java程序都有它自己的堆空间,它们不会彼此干扰。

但是同一java程序的多个线程都共享着同一个堆空间,就得考虑多线程访问对象(堆数据)的同步问题。

(这里可能出现的异常java.lang.OutOfMemoryError:

Javaheapspace)

Methodarea

在Java虚拟机中,被装载的class的信息存储在Methodarea的内存中。

当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件内容并把它传输到虚拟机中。

紧接着虚拟机提取其中的类型信息,并将这些信息存储到方法区。

该类型中的类(静态)变量同样也存储在方法区中。

与Heap一样,methodarea是多线程共享的,因此要考虑多线程访问的同步问题。

比如,假设同时两个线程都企图访问一个名为Lava的类,而这个类还没有内装载入虚拟机,那么,这时应该只有一个线程去装载它,而另一个线程则只能等待。

(这里可能出现的异常java.lang.OutOfMemoryError:

PermGenfull)

Javastack

      Javastack以帧为单位保存线程的运行状态。

虚拟机只会直接对Javastack执行两种操作:

以帧为单位的压栈或出栈。

每当线程调用一个方法的时候,就对当前状态作为一个帧保存到javastack中(压栈);当一个方法调用返回时,从javastack弹出一个帧(出栈)。

栈的大小是有一定的限制,这个可能出现StackOverFlow问题。

下面的程序可以说明这个问题。

publicclassTestStackOverFlow{

publicstaticvoidmain(String[]args){

Recursiver=newRecursive();

r.doit(10000);

//Exceptioninthread"main"java.lang.StackOverflowError

}

}

classRecursive{

publicintdoit(intt){

if(t<=1){

return1;

}

returnt+doit(t-1);

}

}

 

Programcounter

每个运行中的Java程序,每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。

PC寄存器的内容总是指向下一条将被执行指令的饿“地址”,这里的“地址”可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。

Nativemethodstack

对于一个运行中的Java程序而言,它还能会用到一些跟本地方法相关的数据区。

当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。

本地方法可以通过本地方法接口来访问虚拟机的运行时数据区,不止与此,它还可以做任何它想做的事情。

比如,可以调用寄存器,或在操作系统中分配内存等。

总之,本地方法具有和JVM相同的能力和权限。

(这里出现JVM无法控制的内存溢出问题nativeheapOutOfMemory)

2.2SunJVM

SunJVM中对JVMSpecification的实现(内存部分)

JVMSpecification只是抽象的说明了JVM实例按照子系统、内存区、数据类型以及指令这几个术语来描述的,但是规范并非是要强制规定Java虚拟机实现内部的体系结构,更多的是为了严格地定义这些实现的外部特征。

SunJVM实现中:

Runtimedataarea(JVM内存)五个部分中的JavaStack,ProgramCounter,Nativemethodstack三部分和规范中的描述基本一致;但对Heap和MethodArea进行了自己独特的实现。

这个实现和SunJVM的Garbagecollector(垃圾回收)机制有关,下面的章节进行详细描述。

垃圾分代回收算法(GenerationalCollecting)

基于对对象生命周期分析后得出的垃圾回收算法。

把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。

现在的垃圾回收器(从J2SE1.2开始)都是使用此算法的。

如上图所示,为Java堆中的各代分布。

1.Young(年轻代)JVMspecification中的Heap的一部份

年轻代分三个区。

一个Eden区,两个Survivor区。

大部分对象在Eden区中生成。

当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。

需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。

而且,Survivor区总有一个是空的。

2.Tenured(年老代)JVMspecification中的Heap的一部份

年老代存放从年轻代存活的对象。

一般来说年老代存放的都是生命期较长的对象。

3.Perm(持久代)JVMspecification中的Methodarea

用于存放静态文件,如今Java类、方法等。

持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。

持久代大小通过-XX:

MaxPermSize=进行设置。

2.3SUNJVM内存管理(优化)

在我做J2EE系统开发的工作生涯中,经常遇到技术人员或客户发出诸如此类的感慨:

我的J2EE应用系统处理的数据量不大,系统体积也不大,技术架构也没有问题,我的应用服务器的内存有4G或8G;系统运行起来很慢,还经常出现内存溢出错误。

真是无奈!

每次遇到这样的情况,我心中都会忍不住窃笑之。

其实他们所遇到这种情况,不是技术架构上的问题,不是系统本身的问题,也不是应用服务器的问题,也可能不是服务器的内存资源真的不足的问题。

他们花了很多时间在J2EE应用系统本身上找问题(当然一般情况下,这种做法是对的;当出现问题时,在自身上多找找有什么不足),结果还是解决不了问题。

他们却忽略了很重要的一点:

J2EE应用系统是运行在J2EE应用服务器上的,而J2EE应用服务器又是运行在JVM(JavaVirtualMachine)上的。

其实在生产环境中JVM参数的优化和设置对J2EE应用系统性能有着决定性的作用。

本篇我们就来分析JAVA的创建者SUN公司的JVM的内存管理机制(在现实中绝大多数的应用服务器是运行在SUN公司的JVM上的,当然除了SUN公司的JVM,还有IBM的JVM,Bea的JVM等);下篇咱们具体讲解怎样优化JVM的参数以达到优化J2EE应用的目的。

咱们先来看JVM的内存管理制吧,JVM的早期版本并没有进行分区管理;这样的后果是JVM进行垃圾回收时,不得不扫描JVM所管理的整片内存,所以搜集垃圾是很耗费资源的事情,也是早期JAVA程序的性能低下的主要原因。

随着JVM的发展,JVM引进了分区管理的机制。

采用分区管理机制的JVM将JVM所管理的所有内存资源分为2个大的部分。

永久存储区(PermanentSpace)和堆空间(TheHeapSpace)。

其中堆空间又分为新生区(Young(New)generationspace)和养老区(Tenure(Old)generationspace),新生区又分为伊甸园(Edenspace),幸存者0区(Survivor0space)和幸存者1区(Survivor1space)。

具体分区如下图:

那JVM他的这些分区各有什么用途,请看下面的解说。

永久存储区(PermanentSpace):

永久存储区是JVM的驻留内存,用于存放JDK自身所携带的Class,Interface的元数据,应用服务器允许必须的Class,Interface的元数据和Java程序运行时需要的Class和Interface的元数据。

被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM时,释放此区域所控制的内存。

堆空间(TheHeapSpace):

是JAVA对象生死存亡的地区,JAVA对象的出生,成长,死亡都在这个区域完成。

堆空间又分别按JAVA对象的创建和年龄特征分为养老区和新生区。

新生区(Young(New)generationspace):

新生区的作用包括JAVA对象的创建和从JAVA对象中筛选出能进入养老区的JAVA对象。

伊甸园(Edenspace):

JAVA对空间中的所有对象在此出生,该区的名字因此而得名。

也即是说当你的JAVA程序运行时,需要创建新的对象,JVM将在该区为你创建一个指定的对象供程序使用。

创建对象的依据即是永久存储区中的元数据。

幸存者0区(Survivor0space)和幸存者1区(Survivor1space):

当伊甸园的控件用完时,程序又需要创建对象;此时JVM的垃圾回收器将对伊甸园区进行垃圾回收,将伊甸园区中的不再被其他对象所引用的对象进行销毁工作。

同时将伊甸园中的还有其他对象引用的对象移动到幸存者0区。

幸存者0区就是用于存放伊甸园垃圾回收时所幸存下来的JAVA对象。

当将伊甸园中的还有其他对象引用的对象移动到幸存者0区时,如果幸存者0区也没有空间来存放这些对象时,JVM的垃圾回收器将对幸存者0区进行垃圾回收处理,将幸存者0区中不在有其他对象引用的JAVA对象进行销毁,将幸存者0区中还有其他对象引用的对象移动到幸存者1区。

幸存者1区的作用就是用于存放幸存者0区垃圾回收处理所幸存下来的JAVA对象。

养老区(Tenure(Old)generationspace):

用于保存从新生区筛选出来的JAVA对象。

上面我们看了JVM的内存分区管理,现在我们来看JVM的垃圾回收工作是怎样运作的。

首先当启动J2EE应用服务器时,JVM随之启动,并将JDK的类和接口,应用服务器运行时需要的类和接口以及J2EE应用的类和接口定义文件也及编译后的Class文件或JAR包中的Class文件装载到JVM的永久存储区。

在伊甸园中创建JVM,应用服务器运行时必须的JAVA对象,创建J2EE应用启动时必须创建的JAVA对象;J2EE应用启动完毕,可对外提供服务。

JVM在伊甸园区根据用户的每次请求创建相应的JAVA对象,当伊甸园的空间不足以用来创建新JAVA对象的时候,JVM的垃圾回收器执行对伊甸园区的垃圾回收工作,销毁那些不再被其他对象引用的JAVA对象(如果该对象仅仅被一个没有其他对象引用的对象引用的话,此对象也被归为没有存在的必要,依此类推),并将那些被其他对象所引用的JAVA对象移动到幸存者0区。

如果幸存者0区有足够控件存放则直接放到幸存者0区;如果幸存者0区没有足够空间存放,则JVM的垃圾回收器执行对幸存者0区的垃圾回收工作,销毁那些不再被其他对象引用的JAVA对象(如果该对象仅仅被一个没有其他对象引用的对象引用的话,此对象也被归为没有存在的必要,依此类推),并将那些被其他对象所引用的JAVA对象移动到幸存者1区。

如果幸存者1区有足够控件存放则直接放到幸存者1区;如果幸存者0区没有足够空间存放,则JVM的垃圾回收器执行对幸存者0区的垃圾回收工作,销毁那些不再被其他对象引用的JAVA对象(如果该对象仅仅被一个没有其他对象引用的对象引用的话,此对象也被归为没有存在的必要,依此类推),并将那些被其他对象所引用的JAVA对象移动到养老区。

如果养老区有足够控件存放则直接放到养老区;如果养老区没有足够空间存放,则JVM的垃圾回收器执行对养老区区的垃圾回收工作,销毁那些不再被其他对象引用的JAVA对象(如果该对象仅仅被一个没有其他对象引用的对象引用的话,此对象也被归为没有存在的必要,依此类推),并保留那些被其他对象所引用的JAVA对象。

如果到最后养老区,幸存者1区,幸存者0区和伊甸园区都没有空间的话,则JVM会报告“JVM堆空间溢出(java.lang.OutOfMemoryError:

Javaheapspace)”,也即是在堆空间没有空间来创建对象。

这就是JVM的内存分区管理,相比不分区来说;一般情况下,垃圾回收的速度要快很多;因为在没有必要的时候不用扫描整片内存而节省了大量时间。

通常大家还会遇到另外一种内存溢出错误“永久存储区溢出(java.lang.OutOfMemoryError:

JavaPermanentSpace)”。

好,本篇对SUN的JVM内存管理机制讲解就到此为止,下一篇我们将详细讲解怎样优化SUN的JVM让我们的J2EE系统运行更快,不出现内存溢出等问题。

2.4SUNJVM调优

JVM相关参数:

参数名参数说明

-server启用能够执行优化的编译器,显著提高服务器的性能,但使用能够执行优化的编译器时,服务器的预备时间将会较长。

生产环境的服务器强烈推荐设置此参数。

-Xss单个线程堆栈大小值;JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。

在相同物理内存下,减小这个值能生成更多的线程。

但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

-XX:

+UseParNewGC可用来设置年轻代为并发收集【多CPU】,如果你的服务器有多个CPU,你可以开启此参数;开启此参数,多个CPU可并发进行垃圾回收,可提高垃圾回收的速度。

此参数和+UseParallelGC,-XX:

ParallelGCThreads搭配使用。

+UseParallelGC选择垃圾收集器为并行收集器。

此配置仅对年轻代有效。

即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。

可提高系统的吞吐量。

-XX:

ParallelGCThreads年轻代并行垃圾收集的前提下(对并发也有效果)的线程数,增加并行度,即:

同时多少个线程一起进行垃圾回收。

此值最好配置与处理器数目相等。

永久存储区相关参数:

参数名参数说明

-Xnoclassgc每次永久存储区满了后一般GC算法在做扩展分配内存前都会触发一次FULLGC,除非设置了-Xnoclassgc.

-XX:

PermSize应用服务器启动时,永久存储区的初始内存大

-XX:

MaxPermSize应用运行中,永久存储区的极限值。

为了不消耗扩大JVM永久存储区分配的开销,将此参数和-XX:

PermSize这个两个值设为相等。

堆空间相关参数

参数名参数说明

-Xms启动应用时,JVM堆空间的初始大小值。

-Xmx应用运行中,JVM堆空间的极限值。

为了不消耗扩大JVM堆控件分配的开销,将此参数和-Xms这个两个值设为相等,考虑到需要开线程,讲此值设置为总内存的80%.

-Xmn此参数硬性规定堆空间的新生代空间大小,推荐设为堆空间大小的1/4。

上面所列的JVM参数关系到系统的性能,而其中-XX:

PermSize,-XX:

MaxPermSize,-Xms,-Xmx和-Xmn这5个参数更是直接关系到系统的性能,系统是否会出现内存溢出。

-XX:

PermSize和-XX:

MaxPermSize分别设置应用服务器启动时,永久存储区的初始大小和极限大小;在生成环境中强烈推荐将这个两个值设置为相同的值,以避免分配永久存储区的开销,具体的值可取系统“疲劳测试”获取到的永久存储区的极限值;如果不进行设置-XX:

MaxPermSize默认值为64M,一般来说系统的类定义文件大小都会超过这个默认值。

-Xms和-Xmx分别是服务器启动时,堆空间的初始大小和极限值。

-Xms的默认值是物理内存的1/64但小于1G,-Xmx的默认值是物理内存的1/4但小于1G.在生产环境中这些默认值是肯定不能满足我们的需要的。

也就是你的服务器有8g的内存,不对JVM参数进行设置优化,应用服务器启动时还是按默认值来分配和约束JVM对内存资源的使用,不会充分的利用所有的内存资源。

到此我们就不难理解上文提到的“我的服务器有8g内存,系统也就100M左右,居然出现内存溢出”这个“怪现象”了。

在上文我曾提到“永久存储区溢出(java.lang.OutOfMemoryError:

JavaPermanentSpace)”和“JVM堆空间溢出(java.lang.OutOfMemoryError:

Javaheapspace)”这两种溢出错误。

现在大家都知道答案了:

“永久存储区溢出(java.lang.OutOfMemoryError:

JavaPermanentSpace)”乃是永久存储区设置太小,不能满足系统需要的大小,此时只需要调整-XX:

PermSize和-XX:

MaxPermSize这两个参数即可。

“JVM堆空间溢出(java.lang.OutOfMemoryError:

Javaheapspace)”错误是JVM堆空间不足,此时只需要调整-Xms和-Xmx这两个参数即可。

到此我们知道了,当系统出现内存溢出时,是哪些参数设置不合理需要调整。

但我们怎么知道服务器启动时,到底JVM内存相关参数的值是多少呢。

在实践中,经常遇到对JVM参数进行设置了,并且自己心里觉得应该不会出现内存溢出了;但不幸的是内存溢出还是发生了。

很多人百思不得其解,那我可以肯定地告诉你,你设置的JVM参数并没有起作用(本文咱不探讨没有起作用的原因)。

不信我们就去看看,下面介绍如何使用SUN公司的内存使用监控工具jvmstat.

本文只介绍如何使用jvmstat查看内存使用,不介绍其安装配置。

有兴趣的读者,可到SUN公司的官方网站下载一个,他本身已经带有非常详细的安装配置文档了。

这里假设你已经在你的应用服务器上配置好了jvmstat了。

那我们就开始使用他来看看我们的服务器到底是有没有按照我们设置的参数启动。

首先启动服务器,等服务器启动完。

开启DOS窗口(此例子是在windows下完成,linux下同样),在dos窗口中输入jps这个命令。

如下图

窗口中会显示所有JAVA应用进程列表,列表的第一列为应用的进程ID,第二列为应用的名字。

在列表中找到你的应用服务器的进程ID,比如我这里的应用服务器进程ID为1856.在命令行输入visualgc1856回车。

进入jvmstat的主界面,如下图:

上图分别标注了伊甸园,幸存者0区,幸存者1区,养老区和永久存储区。

图上直观的反应出各存储区的大小,已经使用的大小,剩下的空间大小,并用数字标出了各区的大小;如果你这上面的数字和你设置的JVM参数相同的话,那么恭喜你,你设置的参数已经起作用,如果和你设置的不一致的话,那么你设置的参数没有起作用

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 小学教育 > 语文

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1