JAVA虚拟机的根基,JVM内存模型详解

抽象

解析

程序计数器 程序计数器(Program Counter Register)是JVM中一块较小的内存区域,保存着当前线程执行的虚拟机字节码指令的内存地址(可以看作当前线程所执行的字节码的行号指示器)。

如果线程执行的是java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址(可以理解为上图所示的行号),如果正在执行的是native方法,这个计数器的值为undefined。

JVM的多线程是通过线程轮流切换并分配CPU执行时间片的方式来实现的,任何一个时刻,一个CPU都只会执行一条线程中的指令。为了保证线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程间的程序计数器独立存储,互不影响。

此区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域,因为程序计数器是由虚拟机内部维护的,不需要开发者进行操作。

虚拟机栈

虚拟机栈(Java Virtual Machine Stacks)是线程隔离的,每创建一个线程时就会对应创建一个Java栈,即每个线程都有自己独立的虚拟机栈。

这个栈中又会对应包含多个栈帧,每调用一个方法时就会往栈中创建并压入一个栈帧,栈帧存储局部变量表、操作栈、动态链接、方法出口等信息,每一个方法从调用到最终返回结果的过程,就对应一个栈帧从入栈到出栈的过程。

虚拟机栈是一个后入先出的数据结构,线程运行过程中,只有处于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法,当前活动帧栈始终是虚拟机栈的栈顶元素。

局部变量表存放了编译期可知的各种基本数据类型和对象引用类型。通常我们所说的“栈内存”指的就是局部变量表这一部分。

局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧分配多少内存是固定的,运行期间不会改变局部变量表的大小。 64位的long和double类型的数据会占用2个局部变量空间,其余的数据类型只占用1个。

栈的大小可以固定也可以动态扩展。

在固定大小的情况下,JVM会为每个线程的虚拟机栈分配一定的内存大小(-Xss参数),因此虚拟机栈能够容纳的栈帧数量是有限的,若栈帧不断进栈而不出栈,最终会导致当前线程虚拟机栈的内存空间耗尽,会抛出StackOverflowError异常。 在动态扩展的情况下,当整个虚拟机栈内存耗尽,并且无法再申请到新的内存时,就会抛出OutOfMemoryError异常。

栈溢出代码:

```java
public class StackOverFlow {
          
   
	public static void main(String[] args){
          
   
		new StackOverFlow().test();
	}

	private void test() {
          
   
		System.out.println("run...");
		test();
	}
}
(在单个线程下,无论是由于栈帧太大,还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是 StackOverflowError 异常,而不是OOM。)

```java
run...Exception in thread "main" java.lang.StackOverflowError
	at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:691)
	at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:579)

堆溢出代码:

(由于在Windows 平台的虚拟机中, Java 的线程是映射到操作系统的内核线程上的,所以多线程代码执行时有较大的风险,可能会导致操作系统崩溃。)

```java
public class JavaVMStackOOM {
          
   

    private void dontStop() {
          
   
        while (true) {
          
   
        }
    }

    //通过不断的创建新的线程使Stack内存耗尽
    public void stackLeakByThread() {
          
   
        while (true) {
          
   
            Thread thread = new Thread(() -> dontStop());
            thread.start();
        }
    }

    public static void main(String[] args) {
          
   
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }

}
经验分享 程序员 微信小程序 职场和发展