1、JVM体系机构
2、Java虚拟机运行时数据区
2.1、程序计数器
程序计数器,线程私有,是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。
2.2、Java虚拟机栈
Java虚拟机栈,线程私有,生命周期与线程相同。虚拟机栈描述的是方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法返回地址等信息。每一个方法调用直至完成的过程,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。
2.3、本地方法栈
本地方法栈,线程私有,作用与虚拟机栈类似,是为虚拟机使用Native方法服务。
2.4、Java堆
Java堆,线程共享,用于存放对象实例,几乎所有对象实例都在这里分配内存。
字符串常量池:是Java虚拟机为字符串对象专门设立的一种常量池,在每个虚拟机中只有一份。在Java7之前,字符串常量池是方法区的一部分,在Java7及以后版本,字符串常量池被移到堆内存中,以便更好的进行垃圾回收。当你创建一个字符串常量时,会先检查字符串常量池中是否已经存在相同的字符串,如果存在,则返回常量池中的引用,如果不存在,则将字符串添加到常量池中并返回引用。字符串常量池只保存字符串的引用,而字符串的实际内容则保存在堆内存中。
2.5、方法区
方法区,线程共享,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。JDK8之前 HotSpot 虚拟机是使用永久代来实现的方法区,这种设计导致了Java应用更容易遇到内存溢出的问题(永久代有-XX:MaxPermSize的上限,即使不设置也有默认大小),到了JDK8永久代被移除,方法区移至采用本地内存实现的Metaspace(元空间)中。
运行时常量池:是在类加载时动态生成,存储在方法区,它包含类的各种字面量和符号引用。字面量包含字符串常量、声明为final的常量值等。符号引用包含三类常量:(1)类和接口的全限定名(包名和类名);(2)字段的名称和描述符;(3)方法的名称和描述符。
3、HotSpot虚拟机
3.1、对象的创建
虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
在类加载检查通过后,虚拟机将为新生对象从Java堆分配一块确定大小的内存空间。
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)。
接下来,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头中。
在以上工作完成后,从虚拟机的视角来看,一个新的对象就产生了,但从Java程序的视角来看,对象创建才刚刚开始--<init>方法还没有执行,所有的字段都还为零。当将对象按照程序员的意愿进行初始化之后,这样一个真正可用的对象才算完全产生出来。
3.2、对象的内存布局
对象在内存中存储的布局可用分为3块区域:对象头、实例数据和对齐填充。
对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。对象头的另一部分是类型指针,即对象指向它的类原数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
实例数据是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。
对齐填充并不是必然存在的,仅仅起着占位符的作用。
3.3、对象的访问定位
Java程序通过栈上的reference数据来操作堆上的具体对象。HotSpot虚拟机访问对象的方式采用直接指针访问,所以reference中存储的就是对象的实际地址。
注:以上内容学习自《深入理解Java虚拟机:JVM高级特性与最佳实践》。