Sun JVM的内存管理机制

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

Sun JVM实现中:Runtime data area (JVM 内存) 五个部分中的Java Stack, Program Counter, Native method stack三部分和规范中的描述基本一致;但对Heap 和 Method Area进行了本身独特的实现。这个实现和Sun JVM 的Garbage collector(垃圾回收)机制有关。 算法

JVM的早期版本并无进行分区管理;这样的后果是JVM进行垃圾回收时,不得不扫描JVM所管理的整片内存,因此搜集垃圾是很耗费资源的事情,也是早期JAVA程序的性能低下的主要缘由。随着JVM的发展,JVM引进了分区管理的机制。 缓存

采用分区管理机制的JVMJVM所管理的全部内存资源分为2个大的部分。永久存储区Permanent Space)和堆空间The Heap Space)。其中堆空间又分为新生区(Young (New) generation space)和年老区(Tenure (Old) generation space),新生区又分为伊甸园(Eden space),幸存者0区(Survivor 0 space)和幸存者1区(Survivor 1 space)。 服务器

2.2.1 内存分区管理 并发

垃圾分代回收算法(Generational Collecting)是基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不一样生命周期的对象使用不一样的算法进行回收。如今的垃圾回收器(从J2SE1.2开始)都是使用此算法的。  app

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

1. 永久存储区(Permanent Space 性能

也称为持久代,图中的Perm区,JVM specification中的 Method area 测试

JVM的驻留内存,用于存放JDK自身所携带的Class, Interface的元数据,应用服务器运行必须的Class, Interface的元数据和Java程序运行时须要的ClassInterface的元数据。被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM时,释放此区域所控制的内存。 优化

这部分的空间通常不会溢出,除非一次性加载了不少的类。

在涉及到热部署的应用服务器的时候,有时候会遇到java.lang.OutOfMemoryError : PermGen space 的错误,形成这个错误的很大缘由就有多是每次都从新部署,可是从新部署后,类的class没有被卸载掉,这样就形成了大量的class对象保存在了perm中,这种状况下,通常从新启动应用服务器能够解决问题。 还有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候须要设置一个比较大的持久代空间来存放这些运行过程当中新增的类,能够经过-XX:MaxPermSize= 进行设置。

2. 堆空间(The Heap Space

包括年轻代(young)和年老代(Tenured/old),JVM specification中的Heap

JAVA对象生死存亡的地区,JAVA对象的出生,成长,死亡都在这个区域完成。堆空间又分别按JAVA对象的建立和年龄特征分为养老区和新生区。

1. Young(年轻代)

Young区的做用包括JAVA对象的建立和从JAVA对象中筛选出能进入养老区的JAVA对象。

Young区被划分为三部分,Eden区和两个大小严格相同的Survivor区。

伊甸园(Eden space):当你的JAVA程序运行时,须要建立新的对象,JVM将在该区为你建立一个指定的对象供程序使用。建立对象的依据便是永久存储区中的元数据

幸存者0区(Survivor 0 space)和幸存者1区(Survivor1 space):

Survivor区间中,某一时刻只有其中一个是被使用的,另一个留作垃圾收集时复制对象用,在Eden区间变满的时候,minor GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在通过几回垃圾收集后,任然存活于Survivor的对象将被移动到Tenured区间。具体过程以下:

Eden区的资源用完时,程序又须要建立对象;此时JVM的垃圾回收器将对Eden区进行垃圾回收,将Eden区中的再也不被其余对象所引用的对象进行销毁工做。同时将Eden区中的还有其余对象引用的对象移动到Survivor 0区。Survivor 0区就是用于存放Eden区垃圾回收时所幸存下来的JAVA对象。当将Eden区中的还有其余对象引用的对象移动到Survivor 0区时,若是Survivor 0区也没有空间来存放这些对象时,JVM的垃圾回收器将对Survivor 0区进行垃圾回收处理,将Survivor 0区中不在有其余对象引用的JAVA对象进行销毁,将Survivor 0区中还有其余对象引用的对象移动到Survivor 1区。此时Survivor 1区的做用就是用于存放Survivor 0区垃圾回收处理所幸存下来的JAVA对象。

须要注意,Survivor的两个区是对称的,没前后关系,因此同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象。并且,Survivor区总有一个是空的。

2. Tenured(年老代)

Tenured区主要保存生命周期长的对象,通常是一些老的对象,当一些对象在Young复制转移必定的次数之后,对象就会被转移到Tenured区,通常若是系统中用了application级别的缓存,缓存中的对象每每会被转移到这一区间。

2.2.2 垃圾回收的过程

上面咱们看了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区;若是幸存者1区没有足够空间存放,则JVM的垃圾回收器执行对幸存者1区的垃圾回收工做,销毁那些再也不被其余对象引用的JAVA对象,并将那些被其余对象所引用的JAVA对象移动到养老区。

若是养老区有足够控件存放则直接放到养老区;若是养老区没有足够空间存放,则JVM的垃圾回收器执行对养老区的垃圾回收工做,销毁那些再也不被其余对象引用的JAVA对象,并保留那些被其余对象所引用的JAVA对象。若是到最后养老区,幸存者1区,幸存者0区和伊甸园区都没有空间的话,则JVM会报告“JVM堆空间溢出(java.lang.OutOfMemoryError: Java heap space,也便是在堆空间没有空间来建立对象。

这就是JVM的内存分区管理,相比不分区来讲;通常状况下,垃圾回收的速度要快不少;由于在没有必要的时候不用扫描整片内存而节省了大量时间。

2.2.3 内存参数设置

正如上面描述,JVM中内存被分为了3个大的区间,JVM也提供了相应的参数来对内存大小进行配置。

下面是经常使用的JVM相关参数,一般会用这些参数进行JVM调优:

-server 启用可以执行优化的编译器, 显著提升服务器的性能,但使用可以执行优化的编译器时,服务器的预备时间将会较长。生产环境的服务器强烈推荐设置此参数。

-Xss 单个线程堆栈大小值JDK5.0之后每一个线程堆栈大小为1M,之前每一个线程堆栈大小为256K。在相同物理内存下,减少这个值能生成更多的线程。可是操做系统对一个进程内的线程数仍是有限制的,不能无限生成,经验值在3000~5000左右

-XX: +UseParNewGC 可用来设置年轻代为并发收集【多CPU】,若是你的服务器有多个CPU,你能够开启此参数;开启此参数,多个CPU可并发进行垃圾回收,可提升垃圾回收的速度。此参数和+UseParallelGC-XX:ParallelGCThreads搭配使用。

+UseParallelGC 选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集 。可提升系统的吞吐量。

-XX:ParallelGCThreads 年轻代并行垃圾收集的前提下(对并发也有效果)的线程数,增长并行度,即:同时多少个线程一块儿进行垃圾回收。此值最好配置与处理器数目相等。

永久存储区相关参数(Perm Generation):

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

-XX: PermSize 应用服务器启动时,永久存储区的初始内存大小。如: -XX: PermSize=16M 

-XX: MaxPermSize 应用运行中,永久存储区的极限值。为了避免消耗扩大JVM永久存储区分配的开销,将此参数和-XX:PermSize这个两个值设为相等。

如:-XX: MaxPermSize=64M

 Thread Stack

-XX:Xss=128K 

堆空间相关参数

Total heap

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

-Xmx 应用运行中,JVM堆空间的最大值,在JVM启动之后,会分配-Xmx参数指定大小的内存给JVM,可是不必定所有使用,JVM会根据-Xms参数来调节真正用于JVM的内存。

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

-Xmx -Xms之差就是三个Virtual空间的大小

Young Generation

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

-XX: NewRatio=8意味着tenured  young的比值81,这样eden+2*survivor=1/9堆内存

-XX: SurvivorRatio=32意味着eden和一个survivor的比值是321,这样一个Survivor就占Young区的1/34.

上面所列的JVM参数关系到系统的性能,而其中-XX:PermSize-XX:MaxPermSize-Xms-Xmx-Xmn5个参数更是直接关系到系统的性能,系统是否会出现内存溢出。
-XX: PermSize
-XX: MaxPermSize分别设置应用服务器启动时,永久存储区的初始大小和极限大小;在生成环境中强烈推荐将这个两个值设置为相同的值,以免分配永久存储区的开销,具体的值可取系统疲劳测试获取到的永久存储区的极限值;若是不进行设置-XX: MaxPermSize默认值为64M, 通常来讲系统的类定义文件大小都会超过这个默认值。

-Xms-Xmx分别是服务器启动时,堆空间的初始大小和极限值。-Xms的默认值是物理内存的1/64但小于1G,-Xmx的默认值是物理内存的1/4但小于1G。在生产环境中这些默认值是确定不能知足咱们的须要的。也就是你的服务器有8g的内存,不对JVM参数进行设置优化,应用服务器启动时仍是按默认值来分配和约束JVM对内存资源的使用,不会充分的利用全部的内存资源。

内存监控工具