JVM内存结构

Java运行时数据区 JVM内存结构
文章图片

JVM的运行时数据区主要有方法区,堆,虚拟机栈,程序计数器,本地方法栈这几部分,其中方法区和堆是所有线程共有,虚拟机栈,本地方法栈和程序计数器每个线程都有一个。
1、程序计数器 指的是一块内存区域,底层是bcp,字节码指针,用来存放当前线程执行字节码的行号。
如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)。
因为JVM多线程是通过线程轮流切换,分配处理器执行时间的方式实现的。也就是说在任意一个确定的时刻,一个处理器只会执行一个线程的一条指令。因此,为了线程切换的后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储。所以是线程私有。而如果程序计数器没有OOM,因为一旦它OOM了,程序就不知道该执行哪个线程的方法,JVM就会崩溃。
在JVM中类似这样一个循环:
while( not end ) {
? 取PC中的位置,找到对应位置的指令;
? 执行该指令;
? PC ++;
}
2、虚拟机栈 讲述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
Java虚拟机以方法作为最基本的执行单元,“栈帧”(Stack Frame)则是用于支持虚拟机进行方法调用和方法执行背后的数据结构,它也是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用开始至执行结束的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。
但是有一点需要注意,一个栈帧需要分配多少内存,并不会受到程序运行期变量数据的影响,而仅仅取决于程序源码和具体的虚拟机实现的栈内存布局形式。
相当于cpu里面的寄存器,用于存放方法参数和方法内部定义的局部变量。利用方法的Code属性的max_locals确定最大容量。局部变量表所需的内存空间是在编译时期确定的,在方法运行期间不会改变局部变量表的大小。
在Java虚拟机规范中,对这部分区域规定了两种异常: 1、当一个线程的栈深度大于虚拟机所允许的深度的时候,将会抛出StackOverflowError异常; 2、如果当创建一个新的线程时无法申请到足够的内存,则会抛出OutOfMemeryError异常。
3、本地方法栈 和虚拟机栈类似,但是区别是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的本地方法服务。
一个Native Method就是一个Java调用非Java代码的接囗。该方法的实现由非Java语言实现,为C或者C++实现的,和虚拟机栈类似,只不过用于管理非Java方法的。
无法调优和管理,所以一般不管它,知道有这么一个东西就行
4、Java堆 堆(heap)是虚拟机中最大的一块内存区域了,被所有线程共享,在虚拟机启动时创建。它的目的便是存放对象实例。
堆是垃圾收集器管理的主要区域,因此 很多时候也被成为‘GC’堆(Garbage Collected Heap)。
从垃圾回收的角度来讲,现在的收集器包括HotSpot都采用分代收集算法,所以堆又可以分为:新生代(Young)和老年代(Tenured),再细致一点,新生代又可分为Eden、From Survivor空间和To Survivor空间。
从内存分配的角度来讲,又可以分为若干个线程私有的分配缓冲区(Thread Local Allocation Buffer ,TLAB)。
当堆空间不足切无法扩展,会抛出OutOfMemoryError异常。
5、方法区 它用于存储已被虚拟机加载的类型信息,常量,静态变量,即时编译器编译后的代码缓存等数据。
方法区中有一个重要的概念叫运行时常量池,它用来存放编译器生成的各种字面量与符号引用,它们将在类加载以后放入方法区的运行时常量池中。JVM虚拟机规范没有很严格的规定,但一般除了保存Class文件中描述的符号引用外,还会把符号引用翻译出来的直接引用也存储在里面。运行时常量池有动态性,并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。
JVM虚拟机规范并没有规定如何实现方法区。在hotspot中,1.8以前,用永久代来实现的方法区。但是这种方案导致Java应用更容易在遇到内存溢出问题,而且极少数方法(String::intern())会因为永久代的原因导致不同虚拟机下有不同的表现。1.8以后,改用本地内存中实现的元空间(metaspace)来代替。而常量池放在JVM堆中了。
6、直接内存 【JVM内存结构】直接内存:直接内存,归操作系统管,不归JVM管。1.4以后新加入NIO类,引入了一种基于通道和缓存区的IO方式。它通过native函数库操作堆外内存,然后通过一个存储在Java堆的DirectByteBuffer对象作为这块内存的引用进行操作。这样做主要可以提高效率,避免在Java堆和Native堆中来回复制数据。

    推荐阅读