文章出处(引用):http://topic.csdn.net/u/20090602/10/bd9d4ba2-dbeb-49b3-8651-67c5de2b228a.html
如图所示,JVM主要包括两个子系统和两个组件。两个子系统分别是Class loader子系统和Execution engine(执行引擎) 子系统;两个组件分别是Runtime data area (运行时数据区域)组件和Native interface(本地接口)组件。
Class loader子系统的作用:
根据给定的全限定名类名(如 java.lang.Object)来装载class文件的内容到 Runtime data area中的method area(方法区域)。Java程序员可以extends java.lang.ClassLoader类来写自己的Class loader。
Execution engine子系统的作用:
执行classes中的指令。任何JVM specification实现(JDK)的核心都是Execution engine,不同的JDK例如Sun 的JDK 和IBM的JDK好坏主要就取决于他们各自实现的Execution engine的好坏。
Native interface组件:
与native libraries交互,是其它编程语言交互的接口。当调用native方法的时候,就进入了一个全新的并且不再受虚拟机限制的世界,所以也很容易出现JVM无法控制的native heap OutOfMemory。
Runtime Data Area组件:
这就是我们常说的JVM的内存了。它主要分为五个部分——
1、Heap (堆):一个Java虚拟实例中只存在一个堆空间
2、Method Area(方法区域):被装载的class的信息存储在Method area的内存中。当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件内容并把它传输到虚拟机中。
3、Java Stack(java的栈):虚拟机只会直接对Java stack执行两种操作:以帧为单位的压栈或出栈
4、Program Counter(程序计数器):每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。PC寄存器的内容总是指向下一条将被执行指令的饿地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。
5、Native method stack(本地方法栈):保存native方法进入区域的地址
以上五部分只有Heap 和Method Area是被所有线程的共享使用的;而Java stack, Program counter 和Native method stack是以线程为粒度的,每个线程独自拥有自己的部分。
Sun的JVM Generational Collecting(垃圾回收)原理是这样的:把对象分为年青代(Young)、年老代(Tenured)、持久代(Perm),对不同生命周期的对象使用不同的算法。(基于对对象生命周期分析)
如上图所示,为Java堆中的各代分布。
1. Young(年轻代)
年轻代分三个区。一个Eden区,两个Survivor区。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制年老区(Tenured。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。
2. Tenured(年老代)
年老代存放从年轻代存活的对象。一般来说年老代存放的都是生命期较长的对象。
3. Perm(持久代)
用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。
举个例子:当在程序中生成对象时,正常对象会在年轻代中分配空间,如果是过大的对象也可能会直接在年老代生成(据观测在运行某程序时候每次会生成一个十兆的空间用收发消息,这部分内存就会直接在年老代分配)。年轻代在空间被分配完的时候就会发起内存回收,大部分内存会被回收,一部分幸存的内存会被拷贝至Survivor的from区,经过多次回收以后如果from区内存也分配完毕,就会也发生内存回收然后将剩余的对象拷贝至to区。等到to区也满的时候,就会再次发生内存回收然后把幸存的对象拷贝至年老区。
通常我们说的JVM内存回收总是在指堆内存回收,确实只有堆中的内容是动态申请分配的,所以以上对象的年轻代和年老代都是指的JVM的Heap空间,而持久代则是之前提到的Method Area,不属于Heap。
个参数意义:
引用:http://longdick.iteye.com/blog/468368
在JVM启动时,就已经保留了固定的内存空间给Heap内存,这部分内存并不一定都会被JVM使用,但是可以确定的是这部分保留的内存不会被其他进程使用。这部分内存大小由 -Xmx
参数指定。
而另一部分内存在JVM启动时就分配给JVM,作为JVM的初始Heap内存使用。影响这个的参数是 -Xms ,如果 -Xms 指定的值比-Xmx 的小,那么两者的差值就是Virtual内存值。随着程序的运行,Eden区、 Tenured区和Perm区会逐渐使用保留的Virtual空间。
如果没有具体指定,初始和最大堆内存将根据机器的内存计算得出。参数DefaultInitialRAMFraction
和DefaultMaxRAMFraction
会影响最终的结果,如下表所示:
Formula Default
initial heap size | memory / DefaultInitialRAMFraction |
memory / 64 |
maximum heap size | MIN(memory / DefaultMaxRAMFraction, 1GB) |
MIN(memory / 4, 1GB) |
可以看到堆内存默认值最大不会超过1G。
JVM会根据堆内存的使用情况自动决定何时扩张和缩减实际堆内存的大小,可以用VM参数 -XX:MinHeapFreeRatio=<minimum>
和 -XX:MaxHeapFreeRatio=<maximum>
使用堆内存空闲百分比来定义,一般在32位机器上的默认值如下:
Parameter Default Value
MinHeapFreeRatio |
40 |
MaxHeapFreeRatio |
70 |
-Xms |
3670k |
-Xmx |
64m |
当空闲堆内存所占堆内存百分比低于40%,JVM就会试图扩张堆内存空间;当空闲堆内存所占堆内存百分比高于70%,JVM就会试图压缩堆内存空间。
ps:以上默认值在不同平台会有不同的值,如果是64位系统,这些值一般需要扩张30%,来容纳在64位系统下变大的对象。
加上-XX:NewRatio=3
意味着 young(年轻代) 和 tenured(终生代)的比率是1:3,也就是说,eden区和survivor区容量之和将占总堆内存的1/4。
加上-XX:SurvivorRatio=6
设置eden区和 其中一个survivor space的比率是1:6,也就是说,其中一个survivor space占年轻代1/8的容量 (可以想想为什么不是1/7)。
另外还有 -XX:NewSize
-XX:MaxNewSize
指定年轻代的初始值和最大值。
32位系统下默认值如下:
Default Value Parameter Client JVM Server JVM
NewRatio |
8 |
2 |
NewSize |
2228K |
2228K |
MaxNewSize |
not limited |
not limited |
SurvivorRatio |
32 |
32
|