JVM内存模型
JVM定义了许多不同的运行时数据区,他们是用来执行应用程序的。某些区域随着JVM启动及销毁,另外一些区域的数据是线程性独立的,随着线程创建和销毁。
程序计数器
程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
由于Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
如果线程正在执行的是一个Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie 方法,这个计数器值则为空(Undefined)。
此内存区域是唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
Java堆
堆是JVM所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC 堆”。
堆的大小可以通过-Xms(最小值)和-Xmx(最大值)参数设置,-Xms为JVM启动时申请的最小内存,默认为操作系统物理内存的1/64但小于1G,-Xmx为JVM可申请的最大内存,默认为物理内存的1/4但小于1G。
默认当空余堆内存小于40%时,JVM会增大Heap到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列;当空余堆内存大于70%时,JVM会减小heap的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列,对于运行系统,为避免在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样。
如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError 异常。
Java堆OutOfMemoryError异常代码流程:
/**
* 名称:Java堆内存溢出:
* 描述:几乎所有的对象实例都在这里分配内存,Java堆可以处于物理上不连续的内存空间,
* 只要逻辑上连续即可。如果堆中没有内存完成实例分配,并且堆也无法扩展时,将会
* 抛出OutOfMemoryError异常。
* 参数:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
* 把堆得最大值和最小值都设置成20M,同时让虚拟机出现内存溢出异常时dump当前的
* 内存堆转储快照
*/
package StackTest;
public class StackTest{
static class TestObject {
}
public static void main (String[] args) {
List<TestObject> testObjects = new ArrayList<TestObject> ();
while(true) {
testObjects.add(new TestObject);
}
}
}
输出:
Dumping heap to java_pid6864.hprof ...
Heap dump file created [27959416 bytes in 0.070 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Unknown Source)
at java.util.Arrays.copyOf(Unknown Source)
at java.util.ArrayList.grow(Unknown Source)
at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
at java.util.ArrayList.add(Unknown Source)
at StackTest.StackTest.main(StackTest.java:13)
虚拟机栈
Java虚拟机栈是线程私有的,它的生命周期与线程相同。每个方法执行时都会创建一个桢栈来存储方法的私有变量、操作数栈、动态链接方法、返回值、返回地址等信息。栈的大小决定了方法调用的可达深度(递归多少层次,或嵌套调用多少层其他方法,-Xss参数可以设置虚拟机栈大小)。栈的大小可以是固定的,或者是动态扩展的。如果栈的深度是固定的,请求的栈深度大于最大可用深度,则抛出stackOverflowError;如果栈是可动态扩展的,但没有内存空间支持扩展,则抛出OutofMemoryError。
Java栈stackOverflowError异常代码流程:
/**
* 名称:Java栈溢出:
* 描述:几乎所有的对象实例都在这里分配内存,Java堆可以处于物理上不连续的内存空间,
* 只要逻辑上连续即可。如果堆中没有内存完成实例分配,并且堆也无法扩展时,将会
* 抛出OutOfMemoryError异常。
* 参数:-Xss128k
*
*/
package JavaVMStackTest;
public class JavaVMStackTest {
private int stackTemp = 0;
public void VMStackTest() {
stackTemp++;
VMStackTest();
}
public static void main (String[] args) {
JavaVMStackTest testJavaVMStack = new JavaVMStackTest();
try {
testJavaVMStack.VMStackTest();
} catch (StackOverflowError e) {
e.printStackTrace();
}
}
}
输出:
java.lang.StackOverflowError
at JavaVMStackTest.JavaVMStackTest.VMStackTest(JavaVMStackTest.java:8)
at JavaVMStackTest.JavaVMStackTest.VMStackTest(JavaVMStackTest.java:8)
at JavaVMStackTest.JavaVMStackTest.VMStackTest(JavaVMStackTest.java:8)
at JavaVMStackTest.JavaVMStackTest.VMStackTest(JavaVMStackTest.java:8)
.....
方法区
方法区与Java堆一样,是各个线程共享的内存区域。用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
方法区主要有以下几个特点:
1、方法区是线程安全的。由于所有的线程都共享方法区,所以,方法区里的数据访问必须被设计成线程安全的。例如,假如同时有两个线程都企图访问方法区中的同一个类,而这个类还没有被装入JVM,那么只允许一个线程去装载它,而其它线程必须等待
2、方法区的大小不必是固定的,JVM可根据应用需要动态调整。同时,方法区也不一定是连续的,方法区可以在一个堆(甚至是JVM自己的堆)中自由分配。
3、方法区也可被垃圾收集,当某个类不在被使用(不可触及)时,JVM将卸载这个类,进行垃圾收集。
名称 | 特征 | 作用 | 配置参数 | 异常 |
---|---|---|---|---|
程序计数器 | 占用内存小,线程私有,生命周期与线程相同 | 大致为字节码行号指示器 | 无 | 无 |
虚拟机栈 | 线程私有,生命周期与线程相同,使用连续的内存空间 | Java方法执行的内存模型,存储局部变量表、操作栈、动态链接、方法出口等信息 | -Xss | StackOverflowError OutOfMemoryError |
java堆 | 线程共享,生命周期与虚拟机相同,可以不使用连续的内存地址 | 保存对象实例,所有对象实例(包括数组)都要在堆上分配 | -Xms -Xsx | OutOfMemoryError |
方法区 | 线程共享,生命周期与虚拟机相同,可以不使用连续的内存地址 | 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据 | -XX:PermSize=16M -XX:MaxPermSize=64M | OutOfMemoryError |
运行时常量池 | 方法区的一部分,具有动态性 | 存放字面量及符号引用 |
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/14002.html