JVM内存模型深度解析
JVM 的主要组成部分及其作用
JVM包含两个子系统和两个组件:
两个子系统为
- Class loader(类加载器)
- 根据给定的全限定名类名(如:java.lang.Object)来装载class文件到Runtime data area(JVM内存)中的method area(方法区)
- Execution engine(字节码执行引擎)
- 执行java字节码,解释/编译字节码为本地机器指令。(不同的平台需要解释成的机器指令也不同,所以不同平台有不同的JVM)
两个组件为
- Runtime data area(运行时数据区)
- 这就是我们常说的JVM的内存。
- Native Interface(本地接口)
- 与native libraries交互,是其它编程语言交互的接口。
作用:
首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。
xx.java -> xx.class -> load class -> 内存-方法区 - > 执行引擎 -> Native Interface -> 操作系统
JVM内存模型
Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有些区域随着虚拟机进程的启动而存在,有些区域则是依赖线程的启动和结束而建立和销毁。Java 虚拟机所管理的内存被划分为如下几个区域:
不同虚拟机的运行时数据区可能略微有所不同,但都会遵从 Java 虚拟机规范,Java 虚拟机规范规定的区域分为以下 5 个部分:
- 程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个 计数器来完成;
- Java 虚拟机栈(Java Virtual Machine Stacks)或者叫线程栈:用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
- 本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;
- Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;
- 方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。
虚拟机栈/线程栈
当java运行这个类的main方法时,虚拟机会为执行这个方法的线程在线程栈中分配一块区域,这块区域被该线程独享(假设叫main线程)
public class Math { public static final int initData = 666; public static User user = new User(); public int compute() { //一个方法对应一块栈帧内存区域 int a = 1; int b = 2; int c = (a + b) * 10; return c; } public static void main(String[] args) { Math math = new Math(); math.compute(); } }
栈帧
当main线程执行main方法时,会为main方法生成一个栈帧,这个栈帧内存区域用于存放main()方法的局部变量;当mian线程执行到compute()方法时,会为compute()方法生成一个栈帧,用于存放该方法的局部变量。
事实上栈帧存放的不仅仅是局部变量,栈帧中存放的有:
- 局部变量表
- 存放局部变量
- 局部变量0指的是调用方法的对象,即this
- 自己定义的局部变量索引是从1开始的
- 操作数栈
- 在程序执行过程成,存放临时数据的一块内存空间(数据的中转站)
- 动态链接
- 对应静态链接,静态链接是在程序加载的过程中完成的,指在程序加载的过程中将静态方法的符号引用指向方法所在的内存地址(这些数据存放在方法区当中)。
- 动态链接指:程序在运行过程中,解析非静态方法的符号引用,找到这些符号在方法区中对应的内存地址,然后把地址链接到符号上。
- 例如实例程序当中的math.compute();方法,将compute();这个符号链接到方法区中对应这个符号的指令的地址。
- 方法出口
- 记录方法运行完之后,代码应该继续往哪里运行的位置。
- 例如math.compute();方法运行完之后,程序根据方法出口的信息,知道下一步应该执行哪一步代码
通过反汇编读可读字节码文件理解这些概念:
public class Math { public static final int initData = 666; public static User user = new User(); public int compute() { //一个方法对应一块栈帧内存区域 int a = 1; int b = 2; int c = (a + b) * 10; return c; } public static void main(String[] args) { Math math = new Math(); math.compute(); System.out.println(""); } }
在终端输入这个命令,反汇编Math.class文件,生成可读的字节码文件,这里将文件输出到Math.txt中
javap -c Math.class > Math.txt # 如果使用-v参数,会输出附加信息,这里还用不到
字节码文件:
Compiled from "Math.java" public class basic.jvm.Math { public static final int initData; public static basic.jvm.User user; public basic.jvm.Math(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public int compute(); Code: 0: iconst_1 1: istore_1 2: iconst_2 3: istore_2 4: iload_1 5: iload_2 6: iadd 7: bipush 10 9: imul 10: istore_3 11: iload_3 12: ireturn public static void main(java.lang.String[]); Code: 0: new #2 // class basic/jvm/Math 3: dup 4: invokespecial #3 // Method "<init>":()V 7: astore_1 8: aload_1 9: invokevirtual #4 // Method compute:()I 12: pop 13: return static {}; Code: 0: new #5 // class basic/jvm/User 3: dup 4: invokespecial #6 // Method basic/jvm/User."<init>":()V 7: putstatic #7 // Field user:Lbasic/jvm/User; 10: return }
这里只拿computer方法举例:
public int compute(); Code: 0: iconst_1 // 查手册: 将int类型常量1压入操作数栈 1: istore_1 // 将int类型值存入局部变量1(局部变量1就是局部变量表中1索引所指向的变量) 2: iconst_2 // 将int类型常量2压入栈 3: istore_2 // 将int类型值存入局部变量2 4: iload_1 // 从局部变量1中装载int类型值 5: iload_2 // 从局部变量2中装载int类型值 6: iadd // 执行int类型的加法 7: bipush 10 // 将一个8位带符号整数压入栈 9: imul // 执行int类型的乘法 10: istore_3 // 将int类型值存入局部变量3 11: iload_3 // 从局部变量3中装载int类型值 12: ireturn // 从方法中返回int类型的数据 ----------------------------------------------------------------------- compute()方法: public int compute() { //一个方法对应一块栈帧内存区域 int a = 1; int b = 2; int c = (a + b) * 10; return c; }
第1步:0: iconst_1 // 查手册: 将int类型常量1压入操作数栈
第2步:1: istore_1 // 将int类型值存入局部变量1(局部变量1就是局部变量表中1索引所指向的变量),此时操作数栈中的1已经弹出
先在局部变量表中为局部变量1分配一块内存空间,再将int类型值存入局部变量1
对应代码:int a = 1;
局部变量存储在局部变量表中,并通过索引指向,从0开始,局部变量0表示调用这个方法的对象,即this
局部变量1指的是compute()方法中的局部变量a
第3步:2: iconst_2 // 将int类型常量2压入栈
第4步:3: istore_2 // 将int类型值存入局部变量2
先在局部变量表中为局部变量2分配一块内存空间,再将int类型值存入局部变量2
对应代码:int b = 2;
这里插一个概念:程序计数器
程序计数器
程序计数器也是线程独有的,也就是main线程也会分配一个程序计数器,它存放程序正在执行的那行代码的位置(内存地址)或者叫行号。
第5步:4: iload_1 // 从局部变量1中装载int类型值(装载到操作数栈)
这里标红的4就是程序计数器记录的值
程序计数器的值由执行引擎修改
为什么要有程序计数器:
因为多线程环境下,如果有一个线程优先级高的线程抢占了CPU的时间片,那么这个线程就要挂起,等到这个线程可以运行时,根据程序计数器的值继续运行即可。
第6步:5: iload_2 // 从局部变量2中装载int类型值
第7步:6: iadd // 执行int类型的加法
执行这一步时,会把操作数栈中的数弹出来,并执行加法,再把结果返回到操作数栈。
对应代码:(a + b)
第8步:7: bipush 10 // 将一个8位带符号整数压入栈
这里的这个整数就是10,将10压入操作数栈
第9步:9: imul // 执行int类型的乘法
执行这一步时,会把操作数栈中的数弹出来,并执行乘法,再把结果返回到操作数栈。
问题:为什么程序计数器的值直接到9了?
因为执行第8步时,压入操作:bipush,和整数:10,在内存中都占了一块区域,都需要指针指向。
第10步:10: istore_3 // 将int类型值存入局部变量3
在局部变量表中先为局部变量3分配一块内存空间
将乘法得出的结果,存入局部变量3
对应代码:int c = (a + b) * 10;
第11步:11: iload_3 // 从局部变量3中装载int类型值
把局部变量3的值装载到操作数栈
第12步:12: ireturn // 从方法中返回int类型的数据
对应代码:return c;
局部变量表
丰富一下局部变量表的概念,在上面的内容中我们了解到,局部变量表保存一些局部变量,但上面的都是基本类型。看一下示例程序的main();方法
其中有Math math = new Math();这样一行代码
那么math这个局部变量的值是基本类型,它保存的是什么?
math会保存当前生成对象在堆当中的地址
math这个局部变量指向了堆中的一块区域。
方法区
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。
使用javap指令反编译生成字节码可读文件
javap -v Math.class > Math.txt
Classfile /E:/Project/interview/out/production/interview/basic/jvm/Math.class Last modified 2023-3-27; size 748 bytes MD5 checksum ce161fee5acfff43e5a3f7cc9fd3e6d4 Compiled from "Math.java" public class basic.jvm.Math minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #8.#35 // java/lang/Object."<init>":()V #2 = Class #36 // basic/jvm/Math #3 = Methodref #2.#35 // basic/jvm/Math."<init>":()V #4 = Methodref #2.#37 // basic/jvm/Math.compute:()I #5 = Class #38 // basic/jvm/User #6 = Methodref #5.#35 // basic/jvm/User."<init>":()V #7 = Fieldref #2.#39 // basic/jvm/Math.user:Lbasic/jvm/User; #8 = Class #40 // java/lang/Object #9 = Utf8 initData #10 = Utf8 I #11 = Utf8 ConstantValue #12 = Integer 666 #13 = Utf8 user #14 = Utf8 Lbasic/jvm/User; #15 = Utf8 <init> #16 = Utf8 ()V #17 = Utf8 Code #18 = Utf8 LineNumberTable #19 = Utf8 LocalVariableTable #20 = Utf8 this #21 = Utf8 Lbasic/jvm/Math; #22 = Utf8 compute #23 = Utf8 ()I #24 = Utf8 a #25 = Utf8 b #26 = Utf8 c #27 = Utf8 main #28 = Utf8 ([Ljava/lang/String;)V #29 = Utf8 args #30 = Utf8 [Ljava/lang/String; #31 = Utf8 math #32 = Utf8 <clinit> #33 = Utf8 SourceFile #34 = Utf8 Math.java #35 = NameAndType #15:#16 // "<init>":()V #36 = Utf8 basic/jvm/Math #37 = NameAndType #22:#23 // compute:()I #38 = Utf8 basic/jvm/User #39 = NameAndType #13:#14 // user:Lbasic/jvm/User; #40 = Utf8 java/lang/Object { public static final int initData; descriptor: I flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL ConstantValue: int 666 public static basic.jvm.User user; descriptor: Lbasic/jvm/User; flags: ACC_PUBLIC, ACC_STATIC public basic.jvm.Math(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 9: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lbasic/jvm/Math; public int compute(); descriptor: ()I flags: ACC_PUBLIC Code: stack=2, locals=4, args_size=1 0: iconst_1 1: istore_1 2: iconst_2 3: istore_2 4: iload_1 5: iload_2 6: iadd 7: bipush 10 9: imul 10: istore_3 11: iload_3 12: ireturn LineNumberTable: line 14: 0 line 15: 2 line 16: 4 line 17: 11 LocalVariableTable: Start Length Slot Name Signature 0 13 0 this Lbasic/jvm/Math; 2 11 1 a I 4 9 2 b I 11 2 3 c I public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: new #2 // class basic/jvm/Math 3: dup 4: invokespecial #3 // Method "<init>":()V 7: astore_1 8: aload_1 9: invokevirtual #4 // Method compute:()I 12: pop 13: return LineNumberTable: line 21: 0 line 22: 8 line 23: 13 LocalVariableTable: Start Length Slot Name Signature 0 14 0 args [Ljava/lang/String; 8 6 1 math Lbasic/jvm/Math; static {}; descriptor: ()V flags: ACC_STATIC Code: stack=2, locals=0, args_size=0 0: new #5 // class basic/jvm/User 3: dup 4: invokespecial #6 // Method basic/jvm/User."<init>":()V 7: putstatic #7 // Field user:Lbasic/jvm/User; 10: return LineNumberTable: line 11: 0 } SourceFile: "Math.java"
这些代码中可以看到:Constant pool,即常量池,这个常量池中存放了关于该类的所有符号,当类被加载到虚拟机中时,这些静态常量会被加载到方法区中作为运行时常量池。
值得注意的是,方法区中存放的常量,静态变量可能指向的是一个对象,我们都知道对象的实例是保存在堆中的,所以这些常量、静态变量也可能指向堆。
例如示例代码中的user静态变量指向了堆中的User对象。
public static User user = new User();
方法区的详解会在后面的文章提到。
本地方法栈
为本地方法服务的线程栈
本地方法就是Java中使用了native关键字的方法
private native void start0();
堆
这块区域是JVM所管理的内存中最大的一块,堆内存主要用于存放Java对象,所有类的实例和数组内存均从此处分配。
堆内存被分为年轻代和老年代,年轻代被分为Eden区、Survivor区。
其中,年轻代占比1/3,老年代占比2/3。
Survivor又分为两块区域,分别是s0和s1
Eden和s0、s1的占比关系是8:1:1
GC垃圾回收
对象实例最先会放在Eden区,当Eden区放满时,字节码执行引擎会触发minor GC,也就是垃圾回收。做GC时,首先会在线程栈(局部变量)、本地方法栈、方法区(静态变量)中找变量,如果哪些变量引用了堆中的对象,他们就可以作为GC root。因为这些对象也可能引用其他对象,这就从引用了对象的变量开始形成了一个树结构。
GC认为,只要是被引用的对象就认为是非垃圾对象,非垃圾对象会被移入Survivor区中的s0中,并在对象头中为对象的分代年龄加1。如果是垃圾对象,那么它就会被minor GC回收,这是Eden区已经被清空。
当Eden区再次被放满时,字节码执行引擎会再次执行minor GC,它会再次从变量中找GC root,最后判断哪些对象是非垃圾对象。Eden区和s0区的非垃圾对象会被移入s1,并对对象的分代年龄加1。如果判断为垃圾对象,就会把对象回收,这时Eden区和s0区已经被清空。
当Eden区再次被放满,字节码执行引擎会再次触发minor GC,继续找GC root,判断垃圾对象,如果是非垃圾对象,Eden区和s1区的非垃圾对象会被移入s0,分代年龄加1。然后对垃圾对象进行回收,Eden区和s1区再次被清空。
当对象的分代年龄加到15,对象就会被移入老年代。此外,占用内存较大的对象会被直接放入老年代,这个“大”由参数
-XX:PretenureSize Threhold来决定。例如很长的字符串、很大的数组。
老年代的对象回收是通过Full GC来完成的。在Full GC时,会对整个堆进行回收,包括新生代和老年代,此外还会对方法区进行回收。在回收老年代时,会对老年代中的所有对象进行标记,标记完后,会对所有被标记的对象进行整理,把所有存活的对象都向一端移动,然后把另一端的内存空间全部释放。
这里写一个GC回收的实例代码:
public class HeapTest { public static void main(String[] args) throws InterruptedException { // list作为GC root 它指向了ArrayList对象 List<HeapTest> list = new ArrayList<>(); while (true) { // ArrayList对象又指向HeapTest对象,这些对象都被GC root间接引用,所以不会被回收 list.add(new HeapTest()); Thread.sleep(10); } } }
在控制台输入jvisualvm
在这里可以查看Eden,s0,s1,Old Gen区域的变化。
当老年代放满时,会触发Full GC。如果最终老年代还是被放满,那么程序就会报OOM异常,程序内存溢出。
STW机制
STW是Stop-The-World的缩写,指的是在垃圾回收算法(Minor GC、Full GC)执行过程中,将JVM内存冻结、应用程序停顿的一种状态。在STW状态下,Java的所有线程都是停止执行的,除了GC线程之外。
停止线程给用户带来的体验是很差的,java虚拟机调优主要调的就是JVM进行GC的次数,主要减少Full GC的次数,因为Full GC执行的时间比较长。所以要较少Full GC的执行次数或者是Full GC执行的时间。
为什么需要STW机制?
STW机制是为了避免在GC的过程中,对象之间的引用关系发生新的变更使得GC的结果发生错误(比如GC过程中新增了一个引用,但是由于未扫描到该引用导致将被引用的对象清除了),停止所有正在运行的协程。
JVM内存参数设置
Spring Boot程序的JVM参数设置格式(Tomcat启动直接加在bin目录下catalina.sh文件里):
java -Xms2048M -Xmx2048M -Xmn1024M -Xss512K -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -jar microservice-eureka-server.jar
-Xss:每个线程的栈大小
-Xms:设置堆的初始可用大小,默认物理内存的1/64
-Xmx:设置堆的最大可用大小,默认物理内存的1/4
-Xmn:新生代大小
-XX:NewRatio:默认2表示新生代占年老代的1/2,占整个堆内存的1/3。
-XX:SurvivorRatio:默认8表示一个survivor区占用1/8的Eden内存,即1/10的新生代内存。
关于元空间的JVM参数有两个:-XX:MetaspaceSize=N和 -XX:MaxMetaspaceSize=N,对于64位JVM来说,元空间的默认初始值大小是21MB,默认的元空间的最大值是无限。
-XX:MaxMetaspaceSize: 设置元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存大小。
-XX:MetaspaceSize: 指定元空间触发Fullgc的初始阈值(元空间无固定初始大小), 以字节为单位,默认是21M左右,达到该值就会触发full gc进行类型卸载, 同时收集器会对该值进行调整: 如果释放了大量的空间, 就适当降低该值; 如果释放了很少的空间, 那么在不超过-XX:MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值。这个跟早期jdk版本的-XX:PermSize参数意思不一样,-XX:PermSize代表永久代的初始容量。
由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大,对于8G物理内存的机器来说,一般我会将这两个值都设置为256M。
StackOverflowError示例:
public class StackOverflowTest { // JVM设置 // -Xss128k, -Xss默认1M static int count = 0; static void redo() { count++; redo(); } public static void main(String[] args) { try { redo(); } catch (Throwable t) { t.printStackTrace(); System.out.println(count); } } } /** * 运行结果: * java.lang.StackOverflowError * at basic.jvm.StackOverflowTest.redo(StackOverflowTest.java:16) * at basic.jvm.StackOverflowTest.redo(StackOverflowTest.java:17) * at basic.jvm.StackOverflowTest.redo(StackOverflowTest.java:17) * at basic.jvm.StackOverflowTest.redo(StackOverflowTest.java:17) * ...... * 1077 */
结论:
-Xss设置越小count值越小,说明一个线程栈里能分配的栈帧就越少,但是对JVM整体来说能开启的线程数会更多
总结
JVM内存中,线程栈,本地方法栈,程序计数器都是线程独享的。堆和方法区是线程共享的。
附录
JVM指令手册:
栈和局部变量操作 将常量压入栈的指令 aconst_null 将null对象引用压入栈 iconst_m1 将int类型常量-1压入栈 iconst_0 将int类型常量0压入栈 iconst_1 将int类型常量1压入操作数栈 iconst_2 将int类型常量2压入栈 iconst_3 将int类型常量3压入栈 iconst_4 将int类型常量4压入栈 iconst_5 将int类型常量5压入栈 lconst_0 将long类型常量0压入栈 lconst_1 将long类型常量1压入栈 fconst_0 将float类型常量0压入栈 fconst_1 将float类型常量1压入栈 dconst_0 将double类型常量0压入栈 dconst_1 将double类型常量1压入栈 bipush 将一个8位带符号整数压入栈 sipush 将16位带符号整数压入栈 ldc 把常量池中的项压入栈 ldc_w 把常量池中的项压入栈(使用宽索引) ldc2_w 把常量池中long类型或者double类型的项压入栈(使用宽索引) 从栈中的局部变量中装载值的指令 iload 从局部变量中装载int类型值 lload 从局部变量中装载long类型值 fload 从局部变量中装载float类型值 dload 从局部变量中装载double类型值 aload 从局部变量中装载引用类型值(refernce) iload_0 从局部变量0中装载int类型值 iload_1 从局部变量1中装载int类型值 iload_2 从局部变量2中装载int类型值 iload_3 从局部变量3中装载int类型值 lload_0 从局部变量0中装载long类型值 lload_1 从局部变量1中装载long类型值 lload_2 从局部变量2中装载long类型值 lload_3 从局部变量3中装载long类型值 fload_0 从局部变量0中装载float类型值 fload_1 从局部变量1中装载float类型值 fload_2 从局部变量2中装载float类型值 fload_3 从局部变量3中装载float类型值 dload_0 从局部变量0中装载double类型值 dload_1 从局部变量1中装载double类型值 dload_2 从局部变量2中装载double类型值 dload_3 从局部变量3中装载double类型值 aload_0 从局部变量0中装载引用类型值 aload_1 从局部变量1中装载引用类型值 aload_2 从局部变量2中装载引用类型值 aload_3 从局部变量3中装载引用类型值 iaload 从数组中装载int类型值 laload 从数组中装载long类型值 faload 从数组中装载float类型值 daload 从数组中装载double类型值 aaload 从数组中装载引用类型值 baload 从数组中装载byte类型或boolean类型值 caload 从数组中装载char类型值 saload 从数组中装载short类型值 将栈中的值存入局部变量的指令 istore 将int类型值存入局部变量 lstore 将long类型值存入局部变量 fstore 将float类型值存入局部变量 dstore 将double类型值存入局部变量 astore 将将引用类型或returnAddress类型值存入局部变量 istore_0 将int类型值存入局部变量0 istore_1 将int类型值存入局部变量1 istore_2 将int类型值存入局部变量2 istore_3 将int类型值存入局部变量3 lstore_0 将long类型值存入局部变量0 lstore_1 将long类型值存入局部变量1 lstore_2 将long类型值存入局部变量2 lstore_3 将long类型值存入局部变量3 fstore_0 将float类型值存入局部变量0 fstore_1 将float类型值存入局部变量1 fstore_2 将float类型值存入局部变量2 fstore_3 将float类型值存入局部变量3 dstore_0 将double类型值存入局部变量0 dstore_1 将double类型值存入局部变量1 dstore_2 将double类型值存入局部变量2 dstore_3 将double类型值存入局部变量3 astore_0 将引用类型或returnAddress类型值存入局部变量0 astore_1 将引用类型或returnAddress类型值存入局部变量1 astore_2 将引用类型或returnAddress类型值存入局部变量2 astore_3 将引用类型或returnAddress类型值存入局部变量3 iastore 将int类型值存入数组中 lastore 将long类型值存入数组中 fastore 将float类型值存入数组中 dastore 将double类型值存入数组中 aastore 将引用类型值存入数组中 bastore 将byte类型或者boolean类型值存入数组中 castore 将char类型值存入数组中 sastore 将short类型值存入数组中 wide指令 wide 使用附加字节扩展局部变量索引 通用(无类型)栈操作 nop 不做任何操作 pop 弹出栈顶端一个字长的内容 pop2 弹出栈顶端两个字长的内容 dup 复制栈顶部一个字长内容 dup_x1 复制栈顶部一个字长的内容,然后将复制内容及原来弹出的两个字长的内容压入 栈 dup_x2 复制栈顶部一个字长的内容,然后将复制内容及原来弹出的三个字长的内容压入 栈 dup2 复制栈顶部两个字长内容 dup2_x1 复制栈顶部两个字长的内容,然后将复制内容及原来弹出的三个字长的内容压入 栈 dup2_x2 复制栈顶部两个字长的内容,然后将复制内容及原来弹出的四个字长的内容压入 栈 swap 交换栈顶部两个字长内容 类型转换 i2l 把int类型的数据转化为long类型 i2f 把int类型的数据转化为float类型 i2d 把int类型的数据转化为double类型 l2i 把long类型的数据转化为int类型 l2f 把long类型的数据转化为float类型 l2d 把long类型的数据转化为double类型 f2i 把float类型的数据转化为int类型 f2l 把float类型的数据转化为long类型 f2d 把float类型的数据转化为double类型 d2i 把double类型的数据转化为int类型 d2l 把double类型的数据转化为long类型 d2f 把double类型的数据转化为float类型 i2b 把int类型的数据转化为byte类型 i2c 把int类型的数据转化为char类型 i2s 把int类型的数据转化为short类型 整数运算 iadd 执行int类型的加法 ladd 执行long类型的加法 isub 执行int类型的减法 lsub 执行long类型的减法 imul 执行int类型的乘法 lmul 执行long类型的乘法 idiv 执行int类型的除法 ldiv 执行long类型的除法 irem 计算int类型除法的余数 lrem 计算long类型除法的余数 ineg 对一个int类型值进行取反操作 lneg 对一个long类型值进行取反操作 iinc 把一个常量值加到一个int类型的局部变量上 逻辑运算 移位操作 ishl 执行int类型的向左移位操作 lshl 执行long类型的向左移位操作 ishr 执行int类型的向右移位操作 lshr 执行long类型的向右移位操作 iushr 执行int类型的向右逻辑移位操作 lushr 执行long类型的向右逻辑移位操作 按位布尔运算 iand 对int类型值进行“逻辑与”操作 land 对long类型值进行“逻辑与”操作 ior 对int类型值进行“逻辑或”操作 lor 对long类型值进行“逻辑或”操作 ixor 对int类型值进行“逻辑异或”操作 lxor 对long类型值进行“逻辑异或”操作 浮点运算 fadd 执行float类型的加法 dadd 执行double类型的加法 fsub 执行float类型的减法 dsub 执行double类型的减法 fmul 执行float类型的乘法 dmul 执行double类型的乘法 fdiv 执行float类型的除法 ddiv 执行double类型的除法 frem 计算float类型除法的余数 drem 计算double类型除法的余数 fneg 将一个float类型的数值取反 dneg 将一个double类型的数值取反 对象和数组 对象操作指令 new 创建一个新对象 checkcast 确定对象为所给定的类型 getfield 从对象中获取字段 putfield 设置对象中字段的值 getstatic 从类中获取静态字段 putstatic 设置类中静态字段的值 instanceof 判断对象是否为给定的类型 数组操作指令 newarray 分配数据成员类型为基本上数据类型的新数组 anewarray 分配数据成员类型为引用类型的新数组 arraylength 获取数组长度 multianewarray 分配新的多维数组 控制流 条件分支指令 ifeq 如果等于0,则跳转 ifne 如果不等于0,则跳转 iflt 如果小于0,则跳转 ifge 如果大于等于0,则跳转 ifgt 如果大于0,则跳转 ifle 如果小于等于0,则跳转 if_icmpcq 如果两个int值相等,则跳转 if_icmpne 如果两个int类型值不相等,则跳转 if_icmplt 如果一个int类型值小于另外一个int类型值,则跳转 if_icmpge 如果一个int类型值大于或者等于另外一个int类型值,则跳转 if_icmpgt 如果一个int类型值大于另外一个int类型值,则跳转 if_icmple 如果一个int类型值小于或者等于另外一个int类型值,则跳转 ifnull 如果等于null,则跳转 ifnonnull 如果不等于null,则跳转 if_acmpeq 如果两个对象引用相等,则跳转 if_acmpnc 如果两个对象引用不相等,则跳转 比较指令 lcmp 比较long类型值 fcmpl 比较float类型值(当遇到NaN时,返回-1) fcmpg 比较float类型值(当遇到NaN时,返回1) dcmpl 比较double类型值(当遇到NaN时,返回-1) dcmpg 比较double类型值(当遇到NaN时,返回1) 无条件转移指令 goto 无条件跳转 goto_w 无条件跳转(宽索引) 表跳转指令 tableswitch 通过索引访问跳转表,并跳转 lookupswitch 通过键值匹配访问跳转表,并执行跳转操作 异常 athrow 抛出异常或错误 finally子句 jsr 跳转到子例程 jsr_w 跳转到子例程(宽索引) rct 从子例程返回 方法调用与返回 方法调用指令 invokcvirtual 运行时按照对象的类来调用实例方法 invokespecial 根据编译时类型来调用实例方法 invokestatic 调用类(静态)方法 invokcinterface 调用接口方法 方法返回指令 ireturn 从方法中返回int类型的数据 lreturn 从方法中返回long类型的数据 freturn 从方法中返回float类型的数据 dreturn 从方法中返回double类型的数据 areturn 从方法中返回引用类型的数据 return 从方法中返回,返回值为void 线程同步 montiorenter 进入并获取对象监视器 monitorexit 释放并退出对象监视器 JVM指令助记符 变量到操作数栈:iload,iload_,lload,lload_,fload,fload_,dload,dload_,aload,aload_ 操作数栈到变量: istore,istore_,lstore,lstore_,fstore,fstore_,dstore,dstor_,astore,astore_ 常数到操作数栈: bipush,sipush,ldc,ldc_w,ldc2_w,aconst_null,iconst_ml,iconst_,lconst_,fconst_,dconst_ 加:iadd,ladd,fadd,dadd 减:isub,lsub,fsub,dsub 乘:imul,lmul,fmul,dmul 除:idiv,ldiv,fdiv,ddiv 余数:irem,lrem,frem,drem 取负:ineg,lneg,fneg,dneg 移位:ishl,lshr,iushr,lshl,lshr,lushr 按位或:ior,lor 按位与:iand,land 按位异或:ixor,lxor 类型转换:i2l,i2f,i2d,l2f,l2d,f2d(放宽数值转换) i2b,i2c,i2s,l2i,f2i,f2l,d2i,d2l,d2f(缩窄数值转换) 创建类实便:new 创建新数组:newarray,anewarray,multianwarray 访问类的域和类实例域:getfield,putfield,getstatic,putstatic 把数据装载到操作数栈:baload,caload,saload,iaload,laload,faload,daload,aaload 从操作数栈存存储到数组: bastore,castore,sastore,iastore,lastore,fastore,dastore,aastore 获取数组长度:arraylength 检相类实例或数组属性:instanceof,checkcast 操作数栈管理:pop,pop2,dup,dup2,dup_xl,dup2_xl,dup_x2,dup2_x2,swap 有条件转移:ifeq,iflt,ifle,ifne,ifgt,ifge,ifnull,ifnonnull,if_icmpeq,if_icmpene, if_icmplt,if_icmpgt,if_icmple,if_icmpge,if_acmpeq,if_acmpne,lcmp,fcmpl fcmpg,dcmpl,dcmpg 复合条件转移:tableswitch,lookupswitch 无条件转移:goto,goto_w,jsr,jsr_w,ret 调度对象的实便方法:invokevirtual 调用由接口实现的方法:invokeinterface 调用需要特殊处理的实例方法:invokespecial 调用命名类中的静态方法:invokestatic 方法返回:ireturn,lreturn,freturn,dreturn,areturn,return 异常:athrow finally关键字的实现使用:jsr,jsr_w,ret#JVM内存模型#