1.Execution Engine:
Execution Engine执行引擎负责解释命令,提交操作系统执行。在 HotSpot 里面,将字节码翻译为机器码的翻译过程有两种形式:第一种是解释执行,即逐条将字节码翻译成机器码并执行;第二种是即时编译(Just-In-Time compilation,JIT),即将一个方法中包含的所有字节码编译成机器码后再执行。
前者的优势在于无需等待编译,而后者的优势在于实际运行速度更快。HotSpot 默认采用混合模式,综合了解释执行和即时编译两者的优点。它会先解释执行字节码,而后将其中反复执行的热点代码,以方法为单位进行即时编译。
2.Native Method Stack:
在每个操作系统内部,都定义了很多本地方法库,例如windows中以.dll文件为主,Linux总以.so文件为主。
这些本地方法库中,定义了很多调用本地操作系统的方法,也称之为本地方法接口。
本地方法接口的作用是融合不同的编程语言为 Java 所用,它的初衷是融合 C/C++程序,Java 诞生的时候是 C/C++横行的时候,要想立足,必须有调用 C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是 Native Method Stack中登记 native方法,在Execution Engine 执行时加载native libraie(本地方法库)。
3.PC寄存器(程序计数器):
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。
PC寄存器主要负责计数和调度。它可以看作是当前线程所执行的字节码的行号指示器。由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。程序计数器内存区域是虚拟机中唯一没有规定OutOfMemoryError情况的区域。
4.方法区(Method Area):
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,此区属于共享区间。
静态变量+常量+类信息(构造方法/接口定义)+运行时常量池存在方法区中。
实例变量存在堆内存中,和方法区无关。
方法区是《Java虚拟机规范》中规定的一个概念,是一个规范。在JDK1.7之前,HotSpot使用永久代实现方法区,JDK1.8之后,由元空间实现。
5.Stack 栈:
栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就结束,生命周期和线程一致,是线程私有的。
8种基本类型的变量+对象的引用变量+实例方法都是在栈内存中分配。
在栈区域规定了两种异常状态:如果线程请求的栈深度大于虚拟机所允许的深度,则抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,在扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
5.1 栈帧:
一个线程的每个方法在调用时都会在栈上划分一块区域,用于存储方法所需要的变量等信息,这块区域称之为栈帧(stack frame)。栈由多个栈帧构成,好比一部电影由多个帧的画面构成。
5.2 栈运行原理:
栈中的数据都是以栈帧(Stack Frame)为载体存在。在栈中,方法的调用顺序遵循“先进后出”/“后进先出”原则。
图示在一个栈中有两个栈帧:
栈帧 2是最先被调用的方法,先入栈,然后方法 2 又调用了方法1,栈帧 1处于栈顶的位置,栈帧 2 处于栈底,执行完毕后,依次弹出栈帧 1和栈帧 2,线程结束,栈释放。
每执行一个方法都会产生一个栈帧,保存到栈(后进先出)的顶部,顶部栈就是当前的方法,该方法执行完毕 后会自动将此栈帧出栈。
栈帧中主要保存3 类数据:
- 本地变量(Local Variables):输入参数和输出参数以及方法内的变量;
- 栈操作(Operand Stack):记录出栈、入栈的操作;
- 栈帧数据(Frame Data):包括类文件、方法等等。
5.3 堆栈方法区的交互关系:
HotSpot(JDK)是使用指针的方式来访问对象,Java堆中会存放访问类元数据的地址,reference存储的就直接是对象的地址。
6.Heap 堆:
堆是java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。堆内存的大小是可以调节的(通过 -Xmx 和 -Xms 控制)。
所有的对象实例以及数组都要在堆上分配。
如果堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
java堆是垃圾收集器管理的主要区域,因此也被成为“GC堆”(Garbage Collected Heap)。
堆内存逻辑上分为三部分:
- Yong Genneration Space 新生区 Young/New
- Tenure Genneration Space 养老区 Old/Tenure
- Permanent Space 永久区(1.8以后为原空间) Perm
物理设计:
在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收
新生代分为eden区、s0区、s1区,s0和s1也被称为from和to区域,他们是两块大小相等并且可以互换角色的空间。绝大多数情况下,对象首先分配在eden区,在新生代回收后,如果对象还存活,则进入s0或s1区,之后每经过一次新生代回收,如果对象存活则它的年龄就加1,对象达到一定的年龄(默认15)后,则进入老年代。
永久区:
永久存储区是一个常驻内存区域,用于存放JDK自身所携带的 Class,Interface 的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会轻易被垃圾回收器回收掉的,关闭 JVM 才会释放此区域所占用的内存。
如果出现java.lang.OutOfMemoryError: PermGen space,说明是Java虚拟机对永久代Perm内存设置不够。一般出现这种情况,都是程序启动需要加载大量的第三方jar包。例如:在一个Tomcat下部署了太多的应用。或者大量动态反射生成的类不断被加载,最终导致Perm区被占满。
Jdk1.6及之前: 有永久代, 字符串常量池1.6在方法区
Jdk1.7: 有永久代,但已经逐步“去永久代”,字符串常量池1.7在堆
Jdk1.8及之后: 无永久代
实际而言,方法区(Method Area)和堆一样,是各个线程共享的内存区域,它用于存储虚拟机加载的:类信息+普通常量+静态常量+编译器编译后的代码等等,虽然JVM规范将方法区描述为堆的一个逻辑部分,但它却还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。
对于HotSpot虚拟机,很多开发者习惯将方法区称之为“永久代(Parmanent Gen)” ,但严格本质上说两者不同,或者说使用永久代来实现方法区而已,永久代是方法区(相当于是一个接口interface)的一个实现,jdk1.7的版本中,已经将原本放在永久代的字符串常量池移走。