JVM会通过类装载子系统将以class结尾的字节码文件装载到JVM的运行时数据区,然后cpu通过执行引擎不断与运行时数据区进行交互来执行程序。图中紫色模块是线程私有的;黄色部分是线程共享的。
package cn.ly.jvm;
public class Math {
public int math() {
int a=2;
int b=3;
int c=(b-a)*6;
return c;
}
public static void main(String[] args) {
Math math=new Math();
math.math();
}
}
Math.java经过编译后在target目录下会生成对应的字节码文件Math.class。如下图所示,右击Math.class文件,打开terminal,通过执行 javac -c Math.class > math.txt 命令对Math.class进行反汇编并将结果保存到同一目录下的Math.txt中。
Math.txt的内容如下,
Compiled from "Math.java"
public class cn.ly.jvm.Math {
public cn.ly.jvm.Math();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public int math();
Code:
0: iconst_2
1: istore_1
2: iconst_3
3: istore_2
4: iload_2
5: iload_1
6: isub
7: bipush 6
9: imul
10: istore_3
11: iload_3
12: ireturn
public static void main(java.lang.String[]);
Code:
0: new #2 // class cn/ly/jvm/Math
3: dup
4: invokespecial #3 // Method "":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method math:()I
12: pop
13: return
}
Math.java中的程序从main开始执行,JVM会为main线程分配一个栈内存空间。此处要提到栈帧这个重要的概念。栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,他是虚拟机运行时数据区的虚拟机栈的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态链接和方法的返回地址等信息。每一个方法从调用开始直至执行完成的过程,都对应的一个栈帧在虚拟机栈里入栈和出栈的过程。简言之,即每个方法对应一个栈帧,不同方法的操作数栈、动态链接和方法的返回地址存储在不同方法的栈帧中。
字节码指令的含义可以直接参考字节码指令手册:链接:https://pan.baidu.com/s/1jBJwJlkL0LZ_bpki9oC-LQ
提取码:sdy7
执行了以下字节码指令后,局部变量1即a =2,详见下图:
Code:
0: iconst_2 // 将int类型的常量2压入操作数栈
1: istore_1 //将int类型的值2从操作数栈出栈,存入局部变量1(此处即a)中
执行了以下字节码指令后,局部变量2即 b=3, 详见下图:
Code:
2: iconst_3 //将int类型的常量3压入操作数栈
3: istore_2 //将int类型的值3从操作数栈出栈,存入局部变量2中
执行了以下字节码指令后,结果如下图:
Code:
4: iload_2 //将局部变量2的值加载到操作数栈中
5: iload_1 //将局部变量1的值加载到操作数栈中
Code:
6: isub //执行int类型的减法,此时操作数3与2先后出栈
Code:
7: bipush 6 //执行减法的结果1入操作数栈,同时常量6入操作数栈。
Code:
9: imul //执行int类型的乘法,此时操作数6与1先后出栈,执行乘法运算,并将结果6入操作数栈。
Code:
10: istore_3 //将常量6从操作数栈出栈,赋值给int型变量3
11: iload_3 //将局部变量3的值加载到操作数栈中
12: ireturn //操作数栈中的栈顶元素6出栈,根据方法返回地址的值,返回到main方法中对应的位置
当在main方法中new一个Math对象时,JVM根据方法区中类元信息的数据结构创建一个Math类的对象实例并保存到堆内存中。(在math对象的对象头中有指向方法区中Math.class类元信息的指针)
方法区:HotSpot虚拟机的设计团队使用永久代(Permanent Generation)实现方法区,因此,很多人将方法区称为永久代(PermGen)。java8开始,永久代被元空间(Metaspace)取代。由于加载到方法区的字节码大小无法确定,为了防止内存溢出,元空间并不在虚拟机内存中,而是使用本地内存。它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
本地方法栈:它与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
程序计数器:程序计数器(Program Counter Register)是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie方法,这个计数器值则为空(Undefined)。
堆:
Java堆是Java虚拟机管理的内存中最大的一块,此内存区域是为了存放对象实例而存在,所以对象实例以及数组都在堆中分配内存。它也是垃圾回收(GC)的主要区域,因此也被称为GC堆。
由于现有的收集器基本采用分代收集算法,所以Java堆可细分为:新生代和老年代,内存比例一般为2:1,新生代又可分为Eden区,From Survivor区,To Survivor区,内存比例一般为8:1:1。如下图所示: