JVM 可以分为 5 个部分,分别是:
类加载器(Class Loader):加载字节码文件到内存。
运行时数据区(Runtime Data Area):JVM 核心内存空间结构模型。
执行引擎(Execution Engine):对 JVM 指令进行解析,翻译成机器码,解析完成后提交到操作系统中。
本地库接口(Native Interface):供 Java 调用的融合了不同开发语言的原生库。
本地方法库(Native Libraies):Java 本地方法的具体实现。
JVM 的结构如下图所示:
这其中最复杂的是运行时数据区,它也是 JVM 内存结构最重要的部分。运行时数据区又可以分为方法区、虚拟机栈、本地方法栈、堆以及程序计数器,并且方法区和堆是线程共享的,虚拟机栈、本地方法栈、程序计数器是线程隔离的。下面详细讲解运行时数据区的各个组成部分。
1.线程共享
方法区:方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
堆:该区域是所有线程共享的,存放几乎所有的对象实例。jvm管理的内存中最大的一块,也是GC收集器主要管理的区域。按回收内存的角度可以分为新生代,老年代和永久代;再细分的话有Eden空间,From survior,To survior空间;不管如何划分都是为了更好的回收内存,存储的也是对象实例。
新生代:新生代又可分为 Eden,from Survivor,to Survivor。Eden 区用来存放刚刚创建的对象,如果 Eden 区放不下,则放在 Survivor 区,甚至老年代中。Survivor 区又可分为 Survivor From 和 Survivor To,GC 回收时使用,将 Eden 中存活的对象存入 Survior From 中,下一次回收时,将 Survior From 中的对象存入 Survior To 中,清除 Survior From ,下一次回收时重复此步骤,Survior From 变成 Survior To,Survivor To 变成 Survivor From,依次循环,同时每次回收,对象的年龄都 +1,年龄增加到一定程度的对象,移动到老年代中。
老年代:存放年龄大的对象
永久代:jdk7之前就是用永久代来实现方法区,本质上来讲两者并不等价,仅因为Hotspot将GC分代扩展至方法区,所以将其称为永久代。在物理内存上是和堆是连续的但逻辑上是隔离的;jdk8后移除了永久代,用元空间代替。使用的是本地内存,这样减少了内存溢出的问题。
2.线程私有
本地方法栈:栈中方法都是用native修饰的,这些方法在Java中只有定义没有具体实现。
- 由javah命令从.class文件转换成.h文件(头文件,里面包含函数原型和宏定义)
- 用.cpp文件实现 .h 文件中的方法
- 将 .cpp 文件编译成动态链接库文件 .dll
- 使用 System.loadLibrary() 加载动态连接库文件
调用native方法的基本原理是利用反射机制,在运行时找到 .dll 文件并且解析,根据动态链接库中的文件名称创建出对象和方法,然后就可以利用对象调用方法了。
虚拟机栈:实际上,Java 虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。
局部变量表主要存放了编译器可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
Java 虚拟机栈会出现两种错误:StackOverFlowError 和 OutOfMemoryError。
- StackOverFlowError: 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。
- OutOfMemoryError: 若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出 OutOfMemoryError 错误。
程序计数器:它是一块较小的空间,可以看做当前线程执行字节码的行号指示器。字节码解释器通过改变计数器的值来选取下一条需要执行的字节码指令。 另外,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。它也是jvm内存里唯一一个不会出现OOM的区域。