官网概括:https://docs.oracle.com/javase/specs/jvms/se8/html/index.html
一、总体认识
Java虚拟机定义了在程序执行期间使用的各种运行时数据区域。其中一些数据区域是在Java虚拟机启动时创建的,只有在Java虚拟机退出时才会销毁。其他数据区域是每个线程。每个线程的数据区域在线程创建时创建,在线程退出时销毁。
1.方法区
a.方法区是各个线程共享的内存区域,在虚拟机启动时创建。
b.用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
c.方法区包含运行时常量池(Run Time Constant Pool)
,用于存放编译时期生成的各种字面常量和符号引用。
d.方法区在JDK8就是Metaspace
,在JDK7中就是Perm Space
。
2.堆
a.Java堆是Java虚拟机所管理内存中最大的一块,在虚拟机启动时创建,被所有线程共享。
b.用于存放对象实例和数组。
3.虚拟机栈
a.每个Java虚拟机线程都有一个私有的Java虚拟机堆栈,与线程同时创建,每个线程独有。
b.每一个被线程执行的方法,为该栈中的栈帧,即每个方法对应一个栈帧。
c.调用一个方法,就会向栈中压入一个栈帧;一个方法调用完成,就会把该栈帧从栈中弹出。
4.本地方法栈
a.如果当前线程执行的方法是Native类型的,这些方法就会在本地方法栈中执行。
5.程序计数器
a.程序计数器占用的内存空间很小,由于Java虚拟机的多线程是通过线程轮流切换,并分配处理器执行时间的方式来实现的,在任意时刻,一个处理器只会执行一条线程中的指令。因此,为了线程切换后能够恢复到正确的执行位置,每个线程需要有一个独立的程序计数器记录执行方法的位置,线程独有。
b.如果线程正在执行Java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址。
c.如果正在执行的是Native方法,则这个计数器为空。
二、结合字节码文件理解jvm栈和栈帧
官方说明:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6
每个栈帧中包括局部变量表(Local Variables)、操作数栈(OperandStack)、指向运行时常量池的引用(A reference to the run-time constant pool)、方法返回地址(Return Address)和附加信息。
局部变量表
:
方法中定义的局部变量以及方法的参数存放在这张表中,局部变量表中的变量不可直接使用,如需要使用的话,必须通过相关指令将其加载至操作数栈中作为操作数使用。
操作数栈
:
以压栈和出栈的方式存储操作数的。
动态链接:
:
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。例如:多态则使用到动态链接。
方法返回地址
:
当一个方法开始执行后,只有两种方式可以退出,一种是遇到方法返回的字节码指令;一种是遇见异常,并且 这个异常没有在方法体内得到处理。
User.java源代码
class User{
private String name;
private Integer age;
private Integer sex;
public int calc(int op1, int op2){
op1 = 3;
int result = op1 + op2;
return result;
}
}
查看User.class文件
Compiled from "User.java"
class User {
......
public static int calc(int, int);
Code:
0: iconst_3 //将int常量3压入【操作数栈】
1: istore_0 //将int类型值存入【局部变量0】
2: iload_0 //从【局部变量0】中装载int类型值入栈
3: iload_1 //从【局部变量0】中装载int类型值入栈
4: iadd //将栈顶元素弹出栈,执行int类型的加法,结果入栈
5: istore_2 //将栈顶int类型值保存到【局部变量2】中
6: iload_2 //从【局部变量2】中装载int类型值入栈
7: ireturn //从方法中返回int类型的数据
}
局部变量表:op1、op2、result
操作数栈:10、3
参照官网字节码指令说明:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.iconst_i
三、Java对象内存布局
一个对象在内存中包括三个部分:对象头、实例数据、对齐填充