JVM的内存模型与垃圾回收(整理)
一、JVM的内存模型:
从大的方面来讲,JVM的内存模型分为两大块:
永久区内存( Permanent space )和堆内存(heap space)。
栈内存(stack space)一般都不归在JVM内存模型中,因为栈内存属于线程级别。
每个线程都有个独立的栈内存空间。
Permanent space里存放加载的Class类级对象如class本身,method,field等等。
heap space主要存放对象实例和数组。
heap space由Old Generation和New Generation组成,Old Generation存放生命周期长久的实例对象,而新的对象实例一般放在New Generation。
New Generation还可以再分为Eden区(圣经中的伊甸园)、和Survivor区,新的对象实例总是首先放在Eden区,Survivor区作为Eden区和Old区的缓冲,可以向Old区转移活动的对象实例。
下图是JVM在内存空间(堆空间)中申请新对象过程的活动图:
没错,我们常见的OOM(out of memory)内存溢出异常,就是堆内存空间不足以存放新对象实例时导致。
永久区内存溢出相对少见,一般是由于需要加载海量的Class数据,超过了非堆内存的容量导致。通常出现在Web应用刚刚启动时,因此Web应用推荐使用预加载机制,方便在部署时就发现并解决该问题。
栈内存也会溢出,但是更加少见。
堆和栈:
-->Java栈是与每一个线程关联的,JVM在创建每一个线程的时候,会分配一定的栈空间给线程。它主要用来存储线程执行过程中的局部变量,方法的返回值,以及方法调用上下文。栈空间随着线程的终止而释放。StackOverflowError:如果在线程执行的过程中,栈空间不够用,那么JVM就会抛出此异常,这种情况一般是死递归造成的。
-->Java堆是由所有的线程共享的一块内存区域,堆用来保存各种JAVA对象,比如数组,线程对象等。
-->堆和栈分离的好处:面向对象的设计,当然除了面向对象的设计带来的维护性,复用性和扩展性方面的好处外,我们看看面向对象如何巧妙的利用了堆栈分离。如果从JAVA内存模型的角度去理解面向对象的设计,我们就会发现对象它完美的表示了堆和栈,对象的数据放在堆中,而我们编写的那些方法一般都是运行在栈中,因此面向对象的设计是一种非常完美的设计方式,它完美的统一了数据存储和运行。
堆内存优化:
调整JVM启动参数-Xms -Xmx -XX:newSize -XX:MaxNewSize,如调整初始堆内存和最大对内存 -Xms256M -Xmx512M。 或者调整初始New Generation的初始内存和最大内存 -XX:newSize=128M -XX:MaxNewSize=128M。
永久区内存优化:
调整PermSize参数 如 -XX:PermSize=256M -XX:MaxPermSize=512M。
栈内存优化:
调整每个线程的栈内存容量 如 -Xss2048K
最终,一个运行中的JVM所占的内存= 堆内存 + 永久区内存 + 所有线程所占的栈内存总和 。
二、垃圾回收
以下内容转自http://blog.csdn.net/dc_726/article/details/7934101
- package com.cdai.jvm.gc;
- public class ReferenceCount {
- final static int MB = 1024 * 1024;
- byte[] size = new byte[2 * MB];
- Object ref;
- public static void main(String[] args) {
- ReferenceCount objA = new ReferenceCount();
- ReferenceCount objB = new ReferenceCount();
- objA.ref = objB;
- objB.ref = objA;
- objA = null;
- objB = null;
- System.gc();
- System.gc();
- }
- }
![](http://img-my.csdn.net/uploads/201209/02/1346554910_4185.png)
- package com.cdai.jvm.gc;
- public class DeadToRebirth {
- private static DeadToRebirth hook;
- @Override
- public void finalize() throws Throwable {
- super.finalize();
- DeadToRebirth.hook = this;
- }
- public static void main(String[] args) throws Exception {
- DeadToRebirth.hook = new DeadToRebirth();
- DeadToRebirth.hook = null;
- System.gc();
- Thread.sleep(500);
- if (DeadToRebirth.hook != null)
- System.out.println("Rebirth!");
- else
- System.out.println("Dead!");
- DeadToRebirth.hook = null;
- System.gc();
- Thread.sleep(500);
- if (DeadToRebirth.hook != null)
- System.out.println("Rebirth!");
- else
- System.out.println("Dead!");
- }
- }
![](http://img-my.csdn.net/uploads/201209/02/1346555170_6180.png)
![](http://img-my.csdn.net/uploads/201209/02/1346555212_7344.png)
![](http://img-my.csdn.net/uploads/201209/02/1346555246_8570.png)
引用博文:
The Java Memory Architecture http://blog.codecentric.de/en/2010/01/the-java-memory-architecture-1-act/
JVM内存管理总结 http://blog.csdn.net/lengyuhong/article/details/5953544
JVM内存管理-深入垃圾收集器与内存分配策略 http://www.iteye.com/topic/802638
JVM内存管理-深入Java内存区域与OOM http://www.iteye.com/topic/802573
图解JVM内存模型 http://longdick.iteye.com/blog/473866
图解JVM在内存中申请对象及垃圾回收流程 http://longdick.iteye.com/blog/468368
一次Java垃圾收集调优实战 http://www.iteye.com/topic/212967
JVM参数表 http://blogs.oracle.com/watt/resource/jvm-options-list.html
JVM的内部结构如下图:
JVM主要包括两个子系统和两个组件:
1. 两个子系统分别是Class loader子系统和Execution engine(执行引擎) 子系统;
1.1 Class loader子系统的作用:根据给定的全限定名类名(如 java.lang.Object)来装载class文件的内容到 Runtime data area中的method area(方法区域)。Java程序员可以extends java.lang.ClassLoader类来写自己的Class loader。
1.2 Execution engine子系统的作用:执行classes中的指令。任何JVM specification实现(JDK)的核心都是Execution engine,不同的JDK例如Sun 的JDK 和IBM的JDK好坏主要就取决于他们各自实现的Execution engine的好坏。
2. 两个组件分别是Runtime data area (运行时数据区域)组件和Native interface(本地接口)组件。
2.1 Native interface组件:与native libraries交互,是其它编程语言交互的接口。当调用native方法的时候,就进入了一个全新的并且不再受虚拟机限制的世界,所以也很容易出现JVM无法控制的native heap OutOfMemory。
2.2 Runtime Data Area组件:这就是我们常说的JVM的内存了。它主要分为五个部分——
1、Heap (堆):一个Java虚拟实例中只存在一个堆空间,Java堆是被所有线程共享的,在虚拟机启动时创建。Java堆的唯一目的就是存放对象实例,绝大部分的对象实例都在这里分配。Java堆内还有更细致的划分:新生代、老年代,再细致一点的:eden、from survivor、to survivor,甚至更细粒度的本地线程分配缓冲(TLAB)等,无论对Java堆如何划分,目的都是为了更好的回收内存,或者更快的分配内存。
Java堆可以处于物理上不连续的内存空间,它逻辑上是连续的即可,就像我们的磁盘空间一样。实现时可以选择实现成固定大小的,也可以是可扩展的,不过当前所有商业的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。如果在堆中无法分配内存,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
2、Method Area(方法区域):被装载的class的信息存储在Method area的内存中。当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件内容并把它传输到虚拟机中。叫“方法区”可能认识它的人还不太多,如果叫永久代(Permanent Generation)它的粉丝也许就多了。它还有个别名叫 做Non-Heap(非堆)。
方法区中存放了每个Class的结构信息,包括常量池、字段描述、方法描述等等。Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量表(constant_pool table),用于存放编译期已可知的常量,这部分内容将在类加载后进入方法区(永久代)存放。但是Java语言并不要求常量一定只有编译期预置入Class的常量表的内容才能进入方法区常量池,运行期间也可将新内容放入常量池(最典型的String.intern()方法)。
运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法在申请到内存时会抛出OutOfMemoryError异常。
3、Java Stack(java的栈):虚拟机只会直接对Java stack执行两种操作:以帧为单位的压栈或出栈。栈描述的是Java方法调用的内存模型:每个方法被执行的时候,都会同时创建一个帧(Frame)用于存储本地变量表、操作栈、动态链接、方法出入口等信息。每一个方法的调用至完成,就意味着一个帧在VM栈中的入栈至出栈的过程。
4、Program Counter(程序计数器):每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。PC寄存器的内容总是指向下一条将被执行指令的饿地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。
5、Native method stack(本地方法栈):保存native方法进入区域的地址.
以上五部分只有Heap 和Method Area是被所有线程的共享使用的;而Java stack, Program counter 和Native method stack是以线程为粒度的,每个线程独自拥有自己的部分。
此外还有本机直接内存的管理(Direct Memory) -- 直接内存并不是虚拟机运行时数据区的一部分,它根本就是本机内存而不是VM直接管理的区域。
显然本机直接内存的分配不会受到Java堆大小的限制,但是即然是内存那肯定还是要受到本机物理内存(包括SWAP区或者Windows虚拟内存)的限制的,一般服务器管理员配置JVM参数时,会根据实际内存设置-Xmx等参数信息,但经常忽略掉直接内存,使得各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),而导致动态扩展时出现OutOfMemoryError异常。
JVM内存模型实例以及参数对应:
- Model1
![](http://dl.iteye.com/upload/attachment/509121/ef8ef38d-ebea-3c53-b551-177ce3f54c10.jpg)
- Model2
![](http://dl.iteye.com/upload/attachment/509123/e58d0f0c-b1f4-3a6d-b05c-c73f5c61a153.gif)
![](http://dl.iteye.com/upload/attachment/509125/b986fb34-558a-3d6e-84e4-1b3a7b6649c1.png)
- 对照表:
Model-1 |
Model-2 |
Exception |
JVM Options |
Method Area |
Perm |
java.lang.OutOfMemoryError: PermGen space |
-XX:PermSize=<value> -XX:MaxPermSize=<value> |
Heap |
Young Tenured |
java.lang.OutOfMemoryError: Java heap space … |
-Xms<size> -Xmx<size> -Xmn<size> -XX:newSize -XX:MaxNewSize -XX:NewRatio=<value> -XX:SurvivorRatio=<value> … |
Thread-1…N |
NULL |
… |
-Xss<size> … |
*Memory Size of Runtime JVM = Heap + Perm + Sum(Thread-1...N)