一、程序计数器(ProgramCounter)
- 具有线程隔离性
- 占用的内存空间非常小,可以忽略不计
- java虚拟机规范中唯一一个没有规定任何OutofMemeryError的区域
- 程序执行的时候,程序计数器是有值的,其记录的是程序正在执行的字节码的地址
二、虚拟机栈( Java Stack )
一个线程对应一个栈,一个栈对应多个方法栈帧, 栈帧包含局部变量表、操作数栈、动态连接、方法出口等
int i = 8;
i = i++;
0 bipush 8 压栈
2 istore_1 赋值出栈
3 iload_1 压栈
4 iinc 1 by 1 +1
7 istore_1 赋值出栈
8 getstatic #3 <java/lang/System.out>
11 iload_1
12 invokevirtual #4 <java/io/PrintStream.println>
15 return
-
基于寄存器的指令集
-
基于栈的指令集
- HotSpot中的Local Variable table = JVM中的寄存器
-
JVM指令主要分为:本地变量表到操作数栈类指令、操作数栈到本地变量表类指令、常数到操作数栈类指令、将数组指定索引的数组推送至操作数栈类指令、将操作数栈数存储到数组指定索引类指令、操作数栈其他相关类指令、运算相关类指令、条件转移类指令、类和数组类指令和其他指令。
-
i开头的指令操作数类型是integer类型,l开头的指令操作数类型是long类型,f开头的指令操作数类型是float类型,d开头的指令操作数类型是double,a开头的指令操作数类型是引用类型(reference)。
-
load类指令将数据从本地变量表加载到操作数栈,store类指令将数据从操作数栈存储到本地变量表中。其他的指令主要用于操作数栈。
三、本地方法栈( Native Method Stack )
- Java虚拟机栈于管理Java方法的调用,而本地方法栈用于管理本地方法的调用。
- 本地方法栈,也是线程私有的。
- 允许被实现成固定或者是可动态扩展的内存大小
- 如果线程请求分配的栈容量超过本地方法栈允许的最大容量,Java虚拟机将会抛出一个stackoverflowError 异常。
- 如果本地方法栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的本地方法栈,那么Java虚拟机将会抛出一个outofMemoryError异常。
- 本地方法一般是使用C语言实现的。
- 它的具体做法是Native Method Stack中登记native方法,在Execution Engine 执行时加载本地方法库。
注意点:
当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。它和虚拟机拥有同样的权限。
- 本地方法可以通过本地方法接口来访问虚拟机内部的运行时数据区
- 它甚至可以直接使用本地处理器中的寄存器
- 直接从本地内存的堆中分配任意数量的内存
并不是所有的JVM都支持本地方法。因为Java虚拟机规范并没有明确要求本地方法栈的使用语言、具体实现方式、数据结构等。如果JVM产品不打算支持native方法,也可以无需实现本地方法栈。
在Hotspot JVM中,直接将本地方法栈和虚拟机栈合二为一
四、堆内存(Direct Memory)
- 年轻代:新对象和没达到一定年龄的对象都在年轻代。
- 老年代:被长时间使用的对象,内存空间应该要比年轻代更大。
- 元空间:元空间不在虚拟机设置的内存中,而是使用本地内存。是对方法区的一种实现
- 最初的永久代是需要在JVM堆内存里面进行划分。
五、方法区(Method Area)
-
存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等
-
方法区的垃圾收集主要回收两部分内容:常量池中废弃的常量和不再使用的类型。
- 该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。
- 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如OSGi、 JSP的重加载等,否则通常是很难达成的。
- 该类对应的java. lang. Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
-
方法区在JVM启动的时候被创建,并且它实际的物理内存空间中和Java堆区一样都可以是不连续的。
-
方法区的小大,跟堆空间一样,可以选择固定大小或者可扩展
-
方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误:java.lang.OutOfMenoryError:Metaspace
-
关闭JVM就会释放这个区域的内存
变化
JDK1.6及其以前:有永久代,静态变量存放在永久代上。
JDK1.7:有永久代,但已经逐步“去永久代”,字符串常量池、静态变量移除,保存在堆中。
JDK1.8及其之后:无永久代,类型信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池、静态变量仍在堆。