深入理解JVM(二) JVM的运行时数据区

一、 JVM运行时数据区的组成

JDK < 1.8 时运行时数据区的组成

深入理解JVM(二) JVM的运行时数据区_第1张图片

运行时数据区包括JVM堆、 JVM栈、方法区、本地方法栈和程序计数器。 下面具体说明各个区的作用。

1. 程序计数器

    每个线程启动的时候都会创建一个程序计数器,用来保存当前正在执行的JVM指令的地址,程序计数器归该线程私有

    程序计数器可以看做一个指向字节码指令的行号指示器,字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖计数器来完成。

    由于java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令,因此,为了线程切换后能恢复到正常的执行位置,每条线程都需要一个独立的程序计数器,各线程之间计数器互不影响,独立存储。

    如果线程正在执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器的值则为空(Undefined)。此内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

 

2.  虚拟机栈

    虚拟机栈也是线程私有的。当每个线程创建的时候,就会创建一个虚拟机栈,因此虚拟机栈也是有多个的,其生命周期与线程保持一致。  它用于存储当前线程运行方法时所需要的数据、指令、返回地址。

    在每个方法执行时,虚拟机栈都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至完成的过程,就对应着一个栈桢在虚拟机栈中由进栈到出栈的过程。

深入理解JVM(二) JVM的运行时数据区_第2张图片

    思考: 一方法的调用产生一个栈桢,那么如果是递归调用同一个方法,那么该方法会产生一个栈桢还是多个栈桢呢?

    public void func(){

         func();   //递归调用

     }

    很明显,这样无限的递归很快将产生StackOverflowError。 如果递归调用一直只有一个栈桢的话,显然是不会造成栈溢出的。因此,递归调用的过程每一次调用方法都会产生一个栈桢。

 

    局部变量表存放了编译时可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用和返回地址。局部变量表的大小在编译期间就已经确定了。

    虚拟机栈会出现两种异常:

(1)如果线程请求的栈深度大于虚拟机所允许的深度(如过深的递归),将会抛出StackOverflowError异常;

(2)如果虚拟机栈动态扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
 

3. 本地方法栈

    本地方法栈与虚拟机栈的作用非常相似,区别就是:

    虚拟机栈为执行Java方法(也就是字节码)服务, 而本地方法栈为执行Native方法服务。

 

4. JVM堆

    java堆是被所有线程共享的一块内存区域,用来就是存放实例对象,几乎所有的对象实例都在这里分配内存。(本地栈中存放的是对象的引用

    JVM的对象存放区分为年轻代、老年代、永久代,我们将在下一节进行深入讲解。

   如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

 

5. 方法区

    方法区由各个线程共享,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码数据

    方法区中还包括运行时常量池,用于存放编译期生成的各种字面量、常量符号引用,这部分内容将在类加载后进入方法区的运行时常量中存放。当常量池无法再申请到内存时也会抛出OutOfMemoryError异常。

    从1.7版本开始,字符串常量从方法区移到了堆里面

思考:

以下信息分别放在哪个区呢?
 

​
String info = "hello world";   //字面值放方法区中常量池, 对象放堆

int age = 0;                   //变量放虚拟机栈

static float PI = 3.14;        //常量3.14放方法区中常量池, 静态变量放方法区中常量池

Object obj = new Factory();    //类放在方法区中, 实例放堆


​public void func(){           //方法放在方法区,常量放方法区常量池, 变量放虚拟机栈
    boolean sex = FEMALE;
}

int[] arr = new int[3];       //new产生了对象,放在堆中

二、 JDK1.7与1.8的区别

    方法区通常被称为“永久代”。JDK1.8之后元数据区取代了永久代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别是:元数据空间并不在虚拟机中,而是使用本地内存。

深入理解JVM(二) JVM的运行时数据区_第3张图片

思考: 为什么要用元数据区代替永久代?这样做的好处是什么?

1. 官网给出的解释

  移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。

2. 个人的观点

   永久代的最大值在启动时已经由参数-XX:MaxPermSize设置好。 而类、方法、字符串的大小只有在编译时才能确定。如果设置过小,容易导致OOM, 设置过大容易导致老年代溢出。而元数据区可以在默认情况下设置为动态扩容,方便根据实际需要灵活扩展。

    简化Full GC:每一个回收器有专门的元数据迭代器,可以在GC不进行暂停的情况下并发地释放类数据。

 

 

 

 

 

参考内容: 《深入理解Java虚拟机:JVM高级特性与最佳实践  周志明》。

 

你可能感兴趣的:(JVM,JVM,Java)