JVM运行时数据区——JDK1.7、JDK1.8

JDK1.7

在这里插入图片描述

1、程序计数器

程序计数器是一个较小的内存空间,是线程私有的且不发生OOM,它记录的是当前线程字节码指令的行号。
由于JAVA虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,一个处理器只会执行一条线程中的指令。所以,为了线程切换时恢复到正确的位置执行,每个线程都有自己的一个程序计数器来记录字节码指令的行号。
如果正在执行的是JAVA方法,则程序计数器的值为字节码指令的行号;如果是Native方法,则程序计数器的值为空。

2、虚拟机栈

虚拟机栈也是线程私有的,它的生命周期跟线程相同。每个方法在执行时,JAVA虚拟机栈就会创建一个栈帧,里面存储了局部变量表、操作数栈、动态链接、方法出口等信息。一个方法被调用到执行完毕的过程,就是一个栈帧在Java虚拟机栈中入栈出栈的过程。

局部变量表

局部变量表的内存空间大小,在编译期间就已经决定好的,在方法运行期间,也不会改变其大小。局部变量表中,可存放八大基本数据类型(byte、char、short、int、long、float、double、boolean)、对象引用(可以是对象本身、可以是指向对象的指针或者句柄等)、returnAddress类型(指向一条字节码指令的地址)。
各种数据类型在局部变量表的存储用局部变量槽来表示,其中long和double是两个局部变量槽,其余的数据类型只有一个。
如果创建栈帧时请求的深度大于栈的最大深度,就会发生StackOverFlowError异常,比如方法的递归调用;如果Java虚拟机栈申请不到足够的内存时,就会抛出OutOfMemoryError异常,比如创建了一个很大的对象

操作数栈

跟局部变量表一样,在编译期间也决定了该内存大小。当一个方法被执行时,操作数栈一开始是空的,在执行过程中,各种字节码指令通过 入栈/出栈 的方式进出操作数栈。操作数栈用于存储字节码指令在执行过程中的中间计算结果。

动态链接

将符号引用转为直接引用,就是动态链接。由于方法是存放在运行时常量池中的,栈帧又对应每个方法,所以每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用。当方法1调用方法2时,就可以通过动态链接,得到方法2在运行时常量池中的引用了。从方法名转为地址引用,即符号引用转为直接引用。
值得注意的是
在类加载的解析阶段,符号引用转为直接引用是静态解析,在方法运行时,符号引用转为直接引用才是动态链接。

方法出口

执行引擎遇到一个方法返回的指令,这时就退出这个方法,即栈帧出栈。如果遇到异常且没有做异常处理,那么就不正常退出。

需要注意的是
HotSpot虚拟机的栈容量是不可以拓展的,所以它不会由于虚拟机无法扩展而产生OOM,但是会因为申请不到足够的内存空间而发生OOM。

3、本地方法栈

存放的一些本地方法,用native修饰。本地方法通过本地方法接口访问虚拟机的运行时数据区,它被调用时不会形成一个新的栈帧。本地方法栈也会发生栈溢出StackOverFlowError和堆溢出OutOfMenoryError异常。

4、堆

堆是内存最大的一块区域。它是线程共享的,在虚拟机启动时,堆存放的是对象的实例。
堆中分为新生代和老年代,比例为1:2。新生代又分为Eden区和Survivor区,其比例为8:2。Survivor区又分为to区和from区,比例为1:1。
堆是可扩展的,通-Xms,-Xmx参数设定最小和最大内存,如果堆中没有内存可以分配给实例了,或者堆无法再扩展时,就会发生OOM。

5、方法区

方法区和堆一样,也是线程共享的一块内存,主要存放的是虚拟机加载的类信息、静态变量、常量、即时编译器编译后的代码缓存等数据。JDK1.7时,此时的方法区也叫永久代。这里会发生垃圾回收GC,主要是针对常量池的回收和对类型的卸载。

运行时常量池

运行时常量池是方法区(永久代或元空间)中的一部分,Class文件中除了类的版本、字段、方法、接口等描述之外,还有一项就是常量池表(class常量池),用于存放编译时期生成的字面量和符号引用,这部分内容将在类加载之后存放到运行时常量池中。需要注意的是,符号引用转变为直接引用,也会存储在常量池中。并非只有在编译时期才能把常量放入常量池,在运行期间也可以通过String类的intern()方法。

值得注意的是
当类加载到内存之后,JVM会将class常量池中的内容存放到运行时常量池,也就是说一个类对应一个class常量池和运行时常量池。

String.intern()原理
String.intern()是一个native方法,底层调用C++写的StringTable::intern方法实现。当通过语句str.intern()时,JVM会去常量池中查找是否有值等于str,如果有则返回常量池中对这个str的引用,如果没有,则创建一个等值的str,然后再返回这个str的引用。所以,调用这个方法后,可以将str放入常量池中。

当常量池申请不到内存时,也会报OOM异常。

6、直接内存

直接内存不是JVM里面的内存,JDK1.4新加入了NIO(New Input/Output),引入了一种基于通道与缓冲区的I/O方式。它可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆里面的对象DirectByteBuffer作为这块内存的引用进行操作。
直接内存不受堆大小的影响,但是受本机内存的影响,我们通过“-XX:MaxDirectMemorySize”指定了直接内存的最大值,如果超过最大值则会抛出OOM异常;另外,当请求的直接内存和JVM内存总和大于本机内存,也会抛出OOM异常。
在这里插入图片描述

JDK1.8

在这里插入图片描述
JDK1.8中,永久代被取消了,改成了元空间(也是方法区的实现形式),而且为了不发生OOM,被放在了本地内存,默认情况下元空间没有设置最大值,所以只要本地内存够用,就不会发生OOM。

JDK1.7和JDK1.8的不同:
1、JDK1.7的方法区实现叫做永久代,放在JVM内存中,经常发生OOM;JDK1.8的方法区实现叫做元空间,存放在本地内存中,基本不会发生OOM。
2、JDK1.7中,运行时常量池存放在永久代中,字符串常量池存放在堆中;JDK1.8中由于永久代改名为元空间,所以运行时常量池存放在元空间,字符串常量池依旧在堆中。
3、JDK1.6时,运行时常量池逻辑包含字符串常量池,存放在永久代;JDK1.7、1.8时字符串常量池被单独拿出来存放在堆中,运行时常量池其余的东西都存放在永久代或元空间中。


版权声明:本文为weixin_44883854原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。