Java运行时数据区域是如何工作的原创
导读
1. JVM里面是怎么划分的?
2. JVM里面各个区直接是怎么配合工作的?
3. 对象在内存中是如何布局的?
4. 什么是操作数栈和局部变量表?可否在class文件中展示下?
5. 运行时常量和字符串常量有什么区别?
6. 对象访问定位方式有哪些?各有什么优点
1、运行时数据区域
注:方法区,Java 8之前也叫PermGen永久带,Java8去掉了永久带,引入了Metaspace元空间。 OOM
:OutOfMemorySO
:stackOverflow
1.1、程序计数器
1.2、Java虚拟机栈
局部变量表
存放了编译期可知的各种基本数据类型、对象引用和return Address(指向了一条字节码指令的地址)类型。-Xss
1.4、Java堆
-
-Xmx
最大值 -
-Xms
最小值
1.5、方法区
运行时常量池
是方法区的一部分:用于存放编译器生成的各种字面量(static final)和符号引用。方法区包含class文件信息,class文件信息包含:魔数,版本号,常量池,类父类和接口数组,字段,方法等信息。 方法区和常量池 一个class文件对应一个常量池,为静态常量池。JDK运行时,会根据静态常量池生成对应的运行时常量池。 JDK7之前 运行时常量池
逻辑包含字符串常量池
,此时hotspot虚拟机对方法区的实现为永久带
;JDK7 字符串常量池
从方法区移到了堆中,这里只是把字符串常量池单独拿到了堆中,运行时常量池剩下的东西还在方法区,也就是hotspot的永久带;JDK8中 hotspot移除了永久带,用 元空间
取代,此时字符串常量池还在堆中,运行时常量池还在方法区,只不过方法区的实现从永久带变成了元空间。JDK8的元空间不在JVM运行时数据区,而在本地内存,此时本地内存包括:元数据区(元空间)和直接内存。
1.6、直接内存
-XX:MaxDirectMemorySize
。2、JVM运行时数据区是如何工作的
-
线程
:每个线程都有对应的程序计数器,虚拟机栈,本地方法栈; -
虚拟机栈
:假设有如下调用关系:ClassA.invokeA() -> ClassB.invokeB() -> ClassB.doInvokeB() -> ClassC.execute()
,则会生成以上的虚拟机栈,每个方法调用,都有一个栈帧入栈,调用完成,栈帧从虚拟机栈上出栈;虚拟机栈是一个LIFO的栈; -
栈帧
:栈帧包含以下内容: -
恢复上层方法局部变量表和操作数栈;
-
如有有返回值,把该值压入调用者栈帧的操作数栈中;
-
调整PC计数器的值以指向方法调用指令的下一条指令地址。
-
静态解析
:一部分在类加载阶段或者第一次使用的时候转换为直接引用; -
动态链接
:在运行期间转换为直接引用; -
本地变量表
:本地变量表长度编译期确定,一个本地变量
(Slot)可以存32位以内的数据,可以保存类型为 int, short, reference, byte, char, floath和returnAddress的数据,两个本地变量
可以保存类型为long和double的数据; -
操作数栈
:每个栈帧内部都包含一个称为操作数栈的后进先出栈,提供给方法计算过程使用; -
动态链接
:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用来支持方法调用过程中的动态链接;Class文件的常量池中包含了大量的符号引用,字节码中的方法调用指令以常量池中指向的符号引用作为参数,这些符号引用通过两种方式最终转换为直接引用: -
返回地址
:在方法退出之后,需要返回到方法被调用的位置继续执行;退出时可能需要执行的操作: -
线程可以访问
Java堆
和方法区
。
思考
局部对象和局部int类型分别存储在哪里?
有如下代码,尝试使用javap工具生成虚拟机汇编代码,查看汇编代码中的本地变量表,以及使用操作数栈和局部变量表的相关指令:
1
2
3
4
5
6
7
8
9> package com.itzhai.jvm;
> public class Test {
> public static addOperation() {
> int a = 1;
> int b = 2;
> int c = a + b;
> }
> }
>
线程是如何访问堆中创建的对象的呢?对象在虚拟机中如何存储?详细参考下一小节
3、HotSpot虚拟机中的对象
3.1、对象的内存布局
3.2、对象的访问定位
3.2.1、直接使用指针访问
3.2.2、通过句柄访问
-
使用指针访问方式的好处是速度快;
-
使用句柄访问的最大好处是reference中存储的是稳定的句柄地址,垃圾回收移动对象的时候,只会改变句柄中实例数据指针,而reference本身不需要修改。
欢迎关注微信公众号《Java架构杂谈》。