深入理解JVM之Java内存区域划分

01-运行时数据区域

程序计数器、虚拟机栈、本地方法栈、Java堆、方法区(包含运行时常量池)、直接内存

1-1程序计数器

当前线程执行的字节码的行号指示器,如果执行的是Native方法,则这个计数器值为空。

在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令,因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器

1-2虚拟机栈

每个线程私有的,线程在运行时,在执行每个方法的时候都会打包成一个栈帧,存储了局部变量表,操作数栈,动态链接,方法出口等信息。每个时刻正在执行的当前方法就是虚拟机栈顶的栈桢。方法的执行就对应着栈帧在虚拟机栈中入栈和出栈的过程。局部变量表存放着基本数据类型和对象引用类型,局部变量表所需的内存空间在编译期间完成分配,方法运行期间不会改变局部变量表的大小。

1.局部变量表

    局部变量表是一组局部变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java文件编译为Class文件时,就在方法表的Code属性的max_locals数据项中确定了该方法需要分配的最大局部变量表的容量。

2.操作数栈

    操作数栈也常被称为操作栈,它是一个后入先出栈。JVM底层字节码指令集是基于栈类型的,所有的操作码都是对操作数栈上的数据进行操作,对于每一个方法的调用,JVM会建立一个操作数栈,以供计算使用。和局部变量一样。操作数栈的最大深度也是编译的时候写入到方法表的code属性的max_stacks数据项中。操作数栈的每一个元素可以是任意的Java数据类型,包括long、double。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。栈容量的单位为“字宽”,对于32位虚拟机来说,一个“字宽”占4个字节,64位虚拟机来说,一个“字宽”占8个字节。当一个方法刚刚执行的时候,这个方法的操作数栈是空的,在方法执行的过程中,会有各种字节码指向操作数栈中写入和提取值,也就是入栈与出栈操作。例如,在做算术运算的时候就是通过操作数栈来进行的,又或者调用其它方法的时候是通过操作数栈来行参数传递的。 另外,在概念模型中,两个栈帧作为虚拟机栈的元素,相互之间是完全独立的,但是大多数虚拟机的实现里都会作一些优化处理,令两个栈帧出现一部分重叠。让下栈帧的部分操作数栈与上面栈帧的部分局部变量表重叠在一起,这样在进行方法调用返回时就可以共用一部分数据,而无须进行额外的参数复制传递了。

3.动态连接

    每个栈帧都包含一个指向运行时常量池中该栈帧所属性方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。在Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。另外一部分将在每一次的运行期期间转化为直接引用,这部分称为动态连接。

4.方法返回地址

    当一个方法被执行后,有两种方式退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者(调用当前方法的的方法称为调用者),是否有返回值和返回值的类型将根据遇到何种方法返回指令来决定,这种退出方法方式称为正常完成出口(Normal Method Invocation Completion)。另外一种退出方式是,在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方式称为异常完成出口(Abrupt Method Invocation Completion)。一个方法使用异常完成出口的方式退出,是不会给它的调用都产生任何返回值的。     无论采用何种方式退出,在方法退出之前,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者PC计数器的值就可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。 方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用都栈帧的操作数栈中,调用PC计数器的值以指向方法调用指令后面的一条指令等。

5.附加信息

    虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中,例如与高度相关的信息,这部分信息完全取决于具体的虚拟机实现。在实际开发中,一般会把动态连接,方法返回地址与其它附加信息全部归为一类,称为栈帧信息两种异常:

StackOverflowError异常:线程请求的栈深度大于虚拟机所允许的栈深度

OutOfMemoryError异常:可动态扩展的JAVA虚拟机栈在扩展时无法申请到足够的内存时

栈桢大小缺省为1M,可用参数 –Xss调整大小,例如:-Xss256k

1-3本地方法栈

https://blog.csdn.net/shen_ming/article/details/81712391

指得就是Java程序调用了非Java代码,算是一种引入其它语言程序的接口

与虚拟机栈作用类似,不同时虚拟机栈执行的时Java方法,而本地方法栈则为虚拟机使用到的Native方法服务。也会抛出StackOverflowError和OutOfMemoryError异常。

1-4 Java堆

被所有线程共享,在虚拟机启动时创建。几乎所有对象都分配在这里,也是垃圾回收发生的主要区域,

从内存分配的角度看:TLAB区(多个线程的私有分配缓冲区) 更好的分配内存

从分代收集的角度看:新生代(Eden、From Survivor1: To Survivor2)、老年代 更好的回收内存

可用以下参数调整:

-Xms:堆的最小值;

-Xmx:堆的最大值;

-Xmn:新生代的大小;

-XX:NewSize;新生代最小值;

-XX:MaxNewSize:新生代最大值;

例如:- Xmx256m

1-5方法区/永久代

线程共享,存储被加载的类信息、常量("zdy","123"等),静态变量(static变量)、即时编译器编译后的代码等数据,JDK1.8后称元空间,只受物理内存大小影响,也会进行GC回收(废弃常量和无用类)。无法满足内存分配需求时出现OutOfMemoryError异常

可用以下参数调整:

jdk1.7及以前:-XX:PermSize;-XX:MaxPermSize;

jdk1.8以后:-XX:MetaspaceSize; -XX:MaxMetaspaceSize

jdk1.8以后大小就只受本机总内存的限制(元空间)

如:-XX:MaxMetaspaceSize=3M

1-6运行时常量池

https://www.cnblogs.com/tiancai/p/9321338.html

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

运行时常量池是方法区的一部分,方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等,具有动态性

受方法区内存限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常

1-7直接内存

不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域;如果使用了NIO(New Input/Output),这块区域会被频繁使用,在java堆内可以用directByteBuffer对象直接引用并操作;这块内存不受java堆大小限制,但受本机总内存的限制,可以通过-XX:MaxDirectMemorySize来设置(默认与堆内存最大值一样),所以也会出现OOM异常。不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域这块内存,不受java堆大小限制,但受本机总内存的限制。它可以使用Native函数库直接分配堆外内存,通过Java堆中的DirectByteBuffer对象作为直接内存的引用进行操作,减少了Java堆和Native堆数据拷贝 提升了性能

原文地址:https://www.cnblogs.com/lanmao123/p/10530106.html