JVM基本结构
JVM内存区域主要分为线程共享数据区域(方法区、堆)、线程私有数据区域(栈、本地方法栈、程序计数器(也可以叫PC寄存器))和直接内存。
概念介绍
方法区(线程共享数据区域)
主要存放JVM加载的类信息和运行时常量池
其中类信息包括:类型信息、类型的常量池、字段信息、方法信息、类变量、指向类加载器的引用、指向class实例的引用、方法表等。
运行时常量池:Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池 ,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中
注意:
- 在jdk7之前,HotSpot VM把GC分代收集扩展至方法区,即使用JAVA堆的永久代来实现方法区, 这样HotSpot的垃圾收集器就可以像管理Java堆一样管理这部分内存, 而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型 的卸载, 因此收益一般小),所以一般来说永久代是方法区的实现,使得JVM可以像管理堆内存一样去管理方法区的GC机制。
- 在jdk8之后,永久代已经被移除,顶替的是元空间(MetaSpace),元空间与永久代类似,都是方法区的实现。主要区别是:元空间并不在JVM中,而是使用本地内存。
-
在jdk6之前,String类型的常量存储在方法区的常量池中;在jdk7之后,常量池移入到堆中。
堆(线程共享数据区域)
- 创建的对象和数组都保存在JAVA堆中
- 垃圾收集器的主要工作空间
- 所有线程共享JAVA堆
- 由于现代GC算法主要是分代收集算法,所以JAVA堆也是分代的。可以分为:新生代(Eden区、From Survior区和To Survior区)和老年代
虚拟机栈(线程私有数据区域)
- 栈由一系列帧组成,因此虚拟机栈也叫做帧栈
- 帧保存一个方法的局部变量表、操作数栈、动态链接、方法出口(返回地址)等信息
- 每一个方法从调用直至执行完成的过程,就对应的一个帧在虚拟机栈中入栈到出栈的过程
- 位于栈顶的栈帧称做“当前活跃栈帧”
- 局部变量表:存放方法参数和方法内的局部变量
- 操作数栈:
- 动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)
一个方法调用另一个方法,或者一个类使用另一个类的成员变量时,需要知道其名字。符号引用就相当于名字,这些被调用者的名字就存放在Java字节码文件里(.class 文件)。名字是知道了,但是Java真正运行起来的时候,如何靠这个名字(符号引用)找到相应的类和方法,需要解析成相应的直接引用,利用直接引用来准确地找到。 - 方法出口
本地方法栈(线程私有数据区域)
本地方法区和JAVA栈作用类似, 区别是虚拟机栈为执行Java方法服务, 而本地方法栈则为 Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
Native方法:是Java通过调用其他语言实现的方法。在JVM层面,是Java通过JNI直接调用本地C/C++库,可以认为是 Native 方法相当于 C/C++ 暴露给 Java 的一个接口,Java 通过调用这个接口从而调用到 C/C++ 方法。(JNI和JNIEnv又是什么关系呢?)
程序计数器(线程私有数据区域)
- 一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的 程序计数器,这类内存也称为“线程私有”的内存。
- 正在执行Java方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如 果还是Native方法,则为空。
- 这个内存区域是唯一一个在虚拟机中没有规定任何OutOfMemoryError情况的区域。