JVM体系架构
程序计数器
- 指向当前线程所执行字节码的行号指示器。通过改变计数器的值来选择下一条要执行的自己码指令。分支、循环、跳转、异常处理、线程恢复都需要依赖计数器完成。
- 每个线程都有自己独立的程序计数器,各个计数器之间工作互不影响,独立存储。
Java虚拟机栈
用来运行方法使用,线程私有的,生命周期和线程相同,方法执行时,会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等。
- 局部变量表
用来存储编译期间可知的基本数据类型和对象引用类型,所需内存在编译期间完成分配,运行期不会改变局部变量表的大小。
如果线程请求的栈深度大于虚拟机栈所允许的深度,将会抛出StackOverflowError(栈溢出)如果虚拟机栈可以动态拓展,但是无法申请到足够的内存,会抛出OutOfMemoryError异常。
本地方法栈
和虚拟机栈功能类似,为本地方法服务。
堆
Java堆是被线程共享的一块内存,是jvm中占用空间最大的区域,细分可分为新生代老年代,新生代又可细分为eden区和survivor 区(默认比例为8:1),在虚拟机启动时创建,用来存放对象的实例。
方法区(永久代)
用于存放已被虚拟机加载的类信息(类的字节码),常量,静态变量,编译后的代码等,线程共享的区域。
- 运行时常量池
方法区的一部分,用于存放运行时产生的常量,eg:String类的intern方法。
类加载流程
当一个classLoder启动的时候,classLoader的生存地点在jvm中的堆,然后它会去主机硬盘上将A.class装载到jvm的方法区,方法区中的这个字节文件会被虚拟机拿来new A字节码(),然后在堆内存生成了一个A字节码的对象,然后A字节码这个内存文件有两个引用一个指向A的class对象,一个指向加载自己的classLoader。
A字节码内存块
1.类型信息
2.字段信息
3.方法信息
4.常量池
5.静态常量
6.一个到类class类的引用
7.一个到classloder的引用
8.方法表
字节码内存块中的内容对应的就是该类的相关信息
package test;
import java.io.Serializable;
//1.类信息
public final class ClassStruct extends Object implements Serializable {
//2.对象字段信息
private String name;
private int id;
//4.常量池
public final int CONST_INT=0;
public final String CONST_STR="CONST_STR";
//5.类变量区
public static String static_str="static_str";
//3.方法信息
public static final String getStatic_str ()throws Exception{
return ClassStruct.static_str;
}
}
创建对象的过程
JVM遇到一条new指令的时候,会先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载解析初始化,如果没有,必须要进行加载解析初始化,之后为对象在堆中分配内存。对象分配空间的大小在类加载完就可以确定,如果内存是规整的采用“指针碰撞”的方式,如果内存是不规整的,JVM会维护一个列表,记录那块内存是可用的,这种方式成为“空闲列表”,内存是否规整取决于垃圾收集算法。在高并发下创建对对象时,可以采用CAS机制,或者预先为每个线程在java堆中分配一块内存。
对象的内存布局
对象在内存中的布局可以分为3部分,对象头,实例数据,对其填充。
-
对象头用来存储对象自身运行时数据例如:哈希码(hashCode)、GC分代年龄、锁状态标志、线程持有锁、偏向线程ID、偏向时间戳。这部分信息称为“Mark Word”;Mark Word 被设计成一个非固定的数据结构,(也就是说这些数据并不是同时存在的,根据锁状态标志确定存储内容),以便在极小的空间内存储尽量多的信息,它会根据自己的状态复用自己的存储空间。
markWord - 对象头的另外一部分是类型指针用来指向他的类元数据的指针,用于判断他是那个类的实例,如果是数组类型,还必须有一块记录数组长度的区域。
- 实例数据部分是对象真正存储的有效信息。
- 对齐填充:不是必然存在的,只有对象的大小不是8字节的整数倍才会进行填充
除了这些之外,还有一个叫做对象的类型数据,这个数据不是对象私有的,是对象共享的数据,通过指针进行引用。他应该存放的和类有关的一些信息,方法信息,编译后的代码,类变量,常量等
对象的访问定位
java程序通过栈上的reference数据来操作堆上的具体数据类型
- 句柄访问
java需要在堆上维护一个句柄池,句柄池中包含对象实例数据和类型数据的地址。 - 直接指针
reference中存放对象实例数据的引用,对象实例数据中存放对象类型数据的引用。
两种方法各有千秋,使用句柄进行访问,因为reference指向句柄池,所以如果更换对象的引用不会导致更改reference的值,但是使用直接指针进行访问可以节省寻找对象的时间,提高效率。
- 关于对象头很赞的一篇文章
https://www.cnblogs.com/zhengbin/p/6490953.html