前言:首先需要理解的是JVM是一种规范,在不同的平台上,如果有对应平台的虚拟机,那么我们编写的Java程序都是可以在对应平台的虚拟机上边运行的,这也就是Java语言具有的跨平台能力。
请看这段代码:
public class Person {
public int work()throws Exception{//一个方法对应一个栈帧
int x =1;// iconst_1 、 istore_1
int y =2;// iconst_2 、 istore_2
int z =(x+y)*10;
return z;
}
public static void main(String[] args) throws Exception{
Person person = new Person();
person.work(); //这个 3 字节码的行号(针对 本方法偏移)
person.hashCode();//方法属于本地方法 ---本地方法栈 4
}
}
编译完成后的字节码指令:
C:UsersASUS>javap -c A:AndroidSeniorEngineer 1ref-jvm3outproductionref-jvm3ex1Person.class
Compiled from "Person.java"
public class ex1.Person {
public ex1.Person();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public int work() throws java.lang.Exception;
Code:
0: iconst_1 // 将值为1的变量放入操作数栈
1: istore_1 // 存储命令,将操作数栈顶的数据放入到局部变量表的下表为1的位置
2: iconst_2 // 将值为1的变量放入操作数栈
3: istore_2 // 存储命令,将操作数栈顶的数据放入到局部变量表的下表为2的位置
4: iload_1 // 加载命令,将局部变量表中的下标为1的位置加载到操作数栈
5: iload_2 // 加载命令,将局部变量表中的下标为2的位置加载到操作数栈,此时操作数栈有两个数,栈顶为2;
6: iadd // 首先将操作数栈里边的数据拿出来,算完之后再保存在操作数栈;
7: bipush 10 // 这个数太大,不能用const命令,此时栈顶为10;int型该方式只能把-1,0,1,2,3,4,5(分别采用iconst_m1,iconst_0, iconst_1, iconst_2, iconst_3, iconst_4, iconst_5)送到栈顶。 对于int型,其他的数值请使用push系列命令(比如bipush)
9: imul // 将操作数栈的数取出来,做乘法,然后重新放入操作数栈。
10: istore_3 // 将上边的结果存到局部变量表的第三个位置
11: iload_3 // 方法与方法之间,返回的数据必须经过操作数栈,因为这是属于操作的数据;将局部变量表中的下标为3的位置加载到操作数栈
12: ireturn // 在程序执行期间,栈桢与栈桢之间,能返回的数据都是在操作数栈中。
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: new #2 // class ex1/Person
3: dup
4: invokespecial #3 // Method "":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method work:()I
12: pop
13: aload_1
14: invokevirtual #5 // Method java/lang/Object.hashCode:()I
17: pop
18: return
}
为什么少了第八行指令?0,1,2,3,4,5,6这些都是本方法的字节码的偏移量,这是因为bipush指令比较大,占据了两个位置,所以导致少了第八行指令。
完成出口:这个就是work方法的出口,main方法的地址也是从0开始的,work方法也是从0开始,work在main方法里边有自己的地址。所以这就是完成出口。
程序计数器:可能会重复,这个没有问题,因为虚拟机执行的时候,只会执行最顶上的栈帧。
动态链接: 与多态有关,指向运行时常量池的方法引用。
注意:如果是静态方法,那么局部变量表就没有this,因为对象对唯一的,就是这个类对象,this指的是当前对象,用来确定是哪个对象。
Java中无法直接操作一个线程。在HotSpot中,本地方法栈和虚拟机栈用的是同一块内存,没有做区分。程序计数器只能记录虚拟机栈里边的运行的方法,本地方法栈里边的他无法记录,因为这个已经和Java没有关系了。因为里边的表现形式不再是字节码了。
运行时数据区中的其他区域:
请看下边这段代码:
public class ObjectAndClass {
static int age=18;//todo 静态变量(基本数据类型)放入方法区,准备阶段赋值0,初始化赋值18
final static int sex=1;//todo 常量(基本数据类型)放入方法区,准备阶段赋值就是1
final static ObjectAndClass object = new ObjectAndClass();//todo 成员变量指向(对象)在类加载的时候不会执行
//构造方法 -》ObjectAndClass object = new ObjectAndClass();
private boolean isKing;//todo 成员变量 放在哪里???
public static void main(String[] args) {//启动一个线程,创建一个虚拟机栈,数据结构,单个,压入一个栈帧
int x=18;//todo 局部变量(基本数据类型)放入栈帧
long y=1;//todo 局部变量(基本数据类型)放入栈帧
ObjectAndClass lobject = new ObjectAndClass();//todo 局部变量 引用放入局部变量表 对象放入堆。注意,这个成员变量也需要完成构造方法,所以还会去创建一个对象。
lobject.isKing=true;// isKing跟随对象,堆空间
lobject.hashCode();//方法中调用方法 本地方法(C++语言写 JNI)
ByteBuffer bb = ByteBuffer.allocateDirect(128*1024*1024);//todo 直接分配128M的直接内存
//这个地方 分配在哪里 128M
}
}
整体的内存分配情况:存在对象指向对象的情况。
一个类的生命周期:
参考文章:[三] java虚拟机 JVM字节码 指令集 bytecode 操作码 指令分类用法 助记符 - 腾讯云开发者社区-腾讯云



