我们知道java虚拟机在运行时会把它管理的内存分为若干个区域,有的随着虚拟机的启动而启动,有的随着应用线程的启动和创建,那下面我们就看看虚拟机运行时各个分区及其作用。
其实JVM对运行时内存进行分区的主要目的就是为了更好的进行垃圾回收和数据管理。
这张图截自 周志明 的《深入理解java虚拟机》一书。
(1)方法区:
方法区是各个线程共享的区域,它用于存储已经被JVM加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
方法区有时也被称为“永久代”,当然这只是JVM垃圾回收策略的划分方法。
实际上方法区很少进行垃圾回收,jdk1.7之前的垃圾回收基本上只是对常量池回收和java已加载类的类型卸载。
方法区可以通过PermSize和MaxPermSize参数进行设置大小如: -XX:PermSize=5M -XX:MaxPermSize=7M
说明: PermSize为永久区大小, MaxPermSize为最大的永久区大小。
/**
* 常量池位于方法区中,我们通过不停往常量区中添加常量来填充方法区内存
* @author yujie.wang3
* -XX:PermSize=5M -XX:MaxPermSize=5M -XX:+HeapDumpOnOutOfMemoryError
*/
public class MthodOutOfMemoryErrorTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
List<String> list = new ArrayList<String>();
int i = 0;
while(true){
list.add(String.valueOf(i++).intern());
}
}
}
当常量池溢出则抛出OutOfMemoryError: PermGen space
java.lang.OutOfMemoryError: PermGen space
Dumping heap to java_pid7176.hprof ...
Heap dump file created [10644158 bytes in 0.168 secs]
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at com.renren.yujie.c2.MthodOutOfMemoryErrorTest.main(MthodOutOfMemoryErrorTest.java:17)
(2)堆区
同方法区一样,堆区也是被所有应用程序共享的一块内存区域,在虚拟机启动时创建。这块区域的目的主要是存放对象和数组数据。同时堆区也是JVM所管理的最大的一块内存,垃圾收集器主要管理的区域也是堆区,因此很多时候堆区也被称为“GC堆”,
如果采用分代垃圾回收策略,那么java堆还可以细分为:新生代和老年代;在细致一点有Eden空间,From Survivor空间,
To Survivor空间等。从内存分配的角度来看,java堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。
堆区可以是物理上连续的内存空间,也可以是不连续的内存空间。
堆区可以通过Xms和Xmx参数进行设置大小如: -Xms500M -Xmx500M
说明: Xms为初始堆大小, Xmx为最大堆大小。
/**
* @author yujie.wang3
* 通过在堆区创建大量对象来占用堆区内存
* 用于演示java 堆内存的溢出
* -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
*/
import java.util.ArrayList;
import java.util.List;
public class HeapOOM {
static class OOMObject{
}
public static void main(String[] args){
List<OOMObject> list = new ArrayList<OOMObject>();
while(true){
list.add(new OOMObject());
}
}
}
抛出内存溢出异常:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid13156.hprof ...
Heap dump file created [15802426 bytes in 0.099 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2760)
at java.util.Arrays.copyOf(Arrays.java:2734)
at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
at java.util.ArrayList.add(ArrayList.java:351)
at com.renren.yujie.c2.HeapOOM.main(HeapOOM.java:19)
(3)虚拟机栈和本地方法栈
虚拟机栈是线程私有的一块内存,它的生命周期和线程的周末周期相同,虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行时都会创建一个栈帧,用于存储局部变量表,操作数栈,动态连接,方法出口信息,每一个方法从调用到完成都对应着入栈和出栈的过程。
虚拟机栈可以使用Xss参数进行设置大小如:-Xss256K
本地方法栈和虚拟机栈的功能是类似的,只不过本地方法栈是为虚拟机使用到的Native方法服务,有一些虚拟机直接将虚拟机栈和本地方法栈合二为一。
/**
* 验证本地方法栈OOM SOF
* 通过不设深度的递归调用 将抛出 StackOverflowError
* @author yujie.wang3
* -Xss128k -XX:+HeapDumpOnOutOfMemoryError
*/
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
JavaVMStackSOF oom = new JavaVMStackSOF();
try {
oom.stackLeak();
} catch (Exception e) {
// TODO: handle exception
System.out.println("lentho"+oom.stackLength);
}
}
}
如果请求的栈深度大约虚拟机所允许的最大深度,将抛出
StackOverflowError
Exception in thread "main" java.lang.StackOverflowError
at com.renren.yujie.c2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:13)
(4)程序计数器
程序计数器、虚拟机栈和本地方法栈都是线程私有的内存区域,这是一块很小的内存空间,用于存储线程的上下文。我们知道并发程序的执行是通过使用CPU的时间片来执行,当线程继续切换时,就回将当前线程执行的一些信息存储到程序计数器这块内存中,以便之后恢复执行。
这个区域是java虚拟机规范中唯一一个没有规定任何OutOfMemoryError的区域