- 当前线程所执行的字节码的行号指示器
- 字节码解释器工作时就是通过改变这个计数器的值来选取吓一跳需要执行的字节码指令。
- 是线程私有内存,每条线程都有一个独立的程序计数器。
作用:
- 若正在执行的一个java 方法,那么这个计数器记录的是正在执行的虚拟机字节码指令的地址。
- 若执行的是native方法,则为undefined,为空。
- 也是线程私有,生命周期与线程相同。
- 虚拟机栈描述的是Java方法执行的线程内存模型。
- 每个方法执行的时候,Java虚拟机都会创建一个栈帧,来存储局部变量表,操作数栈,动态链接,方法出口等信息。
- 每个方法被调用到执行完毕的过程,都对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
虚拟机栈是运行Java方法(字节码),本地方法栈运行本地方法 (native)
HotSpot将两者合二为一。
4、Java 堆目的:只存放对象实例
- 各线程共享的区域
- 是垃圾收集器管理的内存区域
- 可以处于不连续的内存空间中
- 可以是固定大小,亦可可扩展。
- 线程共享
- 存储被虚拟机加载的类型信息,常量,静态变量等。
HotSpot用永久代实现方法区,目的是Java垃圾收集器像管理堆一样管理此区域。
**弊端:**导致Java程序容易遇到内存溢出。
JDK6开始,hotspot准备放弃永久代,改用本地内存实现方法区。
JDK7,将字符串常量池,静态变量移除。
JDK8、完全放弃,本地内存的元空间实现方法区。
6、运行时常量池方法区一部分,常量池表,存放编译期生成的各种字面量,符号引用。
2.2、对象创建 2.2.1、对象创建的流程
流程:
当Java虚拟机遇到一条字节码new指令时,
-
类加载检查
- 首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并 检查 这个符号引用代表的类是否已被加载,解析,初始化过,没有,执行类加载过程。
-
分配内存
- 为对象分配内存实际上在类加载的时候便确定,内存分配就是将一块同等大小的内存从堆中分出来。
- 指针碰撞
分配内存时如何保证线程安全?
- 虚拟机采用CAS + 失败重试方式 保证更新操作的原子性。
- 把内存分配动作按照 线程 划分在不同的空间之中进行。即 每个线程在Java堆中预先分配一小块内存,本地线程分配缓冲。
-
初始化
- 保证对象的实例字段在Java代码中可以不赋值使用,初始化默认的零值。
-
设置对象头
到此,Java虚拟机认为一个新对象已经产生,但是从Java程序来说,创建对象才刚开始。
new指令之后会接着执行方法,进行对象初始化。
2.2.2、对象的内存布局 1、对象头包括两类信息,
- 第一类是存储对象自身运行时的数据,如哈希码,GC分代年龄,锁状态,线程持有的锁
- 第二类是类型指针,即 对象指向它的类型元数据的指针。Java虚拟机通过这个指针确定它是哪个类的实例。
- 查找对象元数据信息不一定需要经过对象本身。
- 若对象是个数组,对象头必须有一块地方用于记录数组长度的数据。因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但若数组长度不确定,将无法通过元数据信息推断数组的大小。这也就说明了我们初始化数组时为何必须指定长度或指定元素了。
对象真正存储的有效信息。即 我们在程序代码中所定义的各种字段内容。无论是父类继承,还是子类定义的字段都必须记录下来。
存储顺序会受到虚拟机分配策略参数,以及字段在Java源码中定义的顺序的影响。
- 相同宽度的字段会被放在一起存放
- 父类中变量会出现在子类之前。
不是必然存在的。
2.2.3、对象的访问定位Java虚拟机通过栈上的引用对象操作堆上的对象实例。
主流的虚拟机的访问方式主要使用句柄和直接指针两种。
- 使用句柄,Java堆中可能划分出一块内存作为句柄池,引用中存储的就是对象的句柄地址。而句柄中包含了实例数据及类型数据的地址。
- 使用直接指针访问,引用直接指向实例数据,实例数据中存有指向类型数据的指针。
前者在对象移动时只需改变句柄处实例数据指针,而引用本身不需要修改。
后者速度更快,节省依次开销。HotSpot直接指针访问。



