深入理解Java虚拟机运行原理及调优方案
Author: Lijb
一、JVM介绍
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。
- 从Java平台的逻辑结构上来看,我们可以从下图来了解JVM:
- 从物理结构上来看:
二、JAVA代码编译和执行过程
- Java代码编译是由Java源码编译器来完成,流程图如下所示:
- Java字节码的执行是由JVM执行引擎来完成,流程图如下所示:
Java代码编译和执行的整个过程包含了以下三个重要的机制:
- Java源码编译机制
- 类加载机制
- 类执行机制
Java源码编译机制
Java 源码编译由以下三个过程组成:
- 分析和输入到符号表
- 注解处理
- 语义分析和生成class文件
流程图如下所示:
最后生成的class文件由以下部分组成:
· 结构信息。包括class文件格式版本号及各部分的数量与大小的信息
· 元数据。对应于Java源码中声明与常量的信息。包含类/继承的超类/实现的接口的声明信息、域与方法声明信息和常量池
· 方法信息。对应Java源码中语句和表达式对应的信息。包含字节码、异常处理器表、求值栈与局部变量区大小、求值栈的类型记录、调试符号信息
类加载机制
JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:
1)Bootstrap ClassLoader
负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类
2)Extension ClassLoader
负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
3)App ClassLoader
负责记载classpath中指定的jar包及目录中class
4)Custom ClassLoader
属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。
类执行机制
JVM是基于栈的体系结构来执行class字节码的。线程创建后,都会产生程序计数器(PC)和栈(Stack),程序计数器存放下一条要执行的指令在方法内的偏移量,栈中存放一个个栈帧,每个栈帧对应着每个方法的每次调用,而栈帧又是由局部变量表、操作数栈、动态链接、方法返回地址等组成,局部变量区用于存放方法中的局部变量和参数,操作数栈中用于存放方法执行过程中产生的中间结果。栈的结构在下文会展开详解:
三、JVM内存结构详解
虚拟机栈
每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区、操作数栈、动态链接、方法返回地址【附加信息】,用于存放此次方法调用过程中的临时变量、参数和中间结果。
-Xss**????*设置每个线程的堆栈大小. JDK1.5+ 每个线程堆栈大小为 1M,一般来说如果栈不是很深的话, 1M 是绝对够用了的。
A**、JVM栈**:伴随每一个线程的启动,在内存中创建一个对应的JVM栈,用于存储“栈帧。
栈帧结构:栈帧是JVM栈描述方法执行的内存模型。包含了局部变量表、操作数栈、动态链接、方法返回地址等。
异常:*Error、OutOfMemoryError.
B**、栈帧结构(Stack Frame)**:栈帧是JVM栈描述方法执行的内存模型,每一个方法在执行的同时都会创建一个栈帧,其中包含了局部变量表、操作数栈、动态链接、方法返回地址等。
局部变量表(Local Variable Table)
一组变量值的存储空间,用于存放方法在执行过程中所需要的参数和局部变量,局部变量表中的每一个存储空间称为“变量槽”(Variable Slot),可存储的数据类型:byte、boolean、short、char、int、float、reference、returnAddress**(已过时)**。对于double、long类型需要2个连续的Slot进行存储,局部变量表的容量由Code属性的max_locals在编译期确定。
操作数栈(Operand Stack)
操作数栈又称为操作栈、表达式栈、是虚拟机的工作区。通过JVM字节码指令对“局部变量表”中的数据进行提取、调用、计算、存储等操作。操作数栈的深度由Code属性的max_stacks在编译期确定。
动态链接
每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用。在每一次方法运行期间,动态的将符号引用转换成直接引用(入口地址)。
静态解析是指,类加载或首次调用期间将 #1 methodref xxx.xxx.xxx.fn()V 转换成该方法的如接口地址。
动态解析是指,每次调用期间将 #2 methodref xxx.xxx.xxx.fn1()V转换成该方法的入口地址。
如下代码:
public class TestStack {
public static void main(String[] args) {
int x=10;
int y=20;
int z=x+y;
}
}
反编译之后在栈空间运行情况如下图:
方法返回地址(Method Renturn Address)
方法的结束分为正常结束和异常结束。
正常结束:当前栈帧承担着恢复调用者状态的责任,其中包括恢复调用者的局部变量表、操作数栈、正确递增程序计数器、将返回值压入调用者的操作数栈。
异常结束:如果当前方法中没有处理此种异常,当前栈帧恢复调用者状态的同时,不会返回任何返回值,而是通过athrow指令将当前异常抛给调用者。
除了要给予调用者操作结果以外,还要调整为PC计数器的值,以指向方法调用指令的后一行指令。
C、栈空间的异常:
*Error:
无穷递归
可用内存下自动扩充栈深度,如果空间不足、无法扩充时,则抛出*Erro。
OutOfMemoryError:
无限创建线程对象。
所有可用内存空间全部分配给JVM去创建线程,会导致OutOfMemoryError,因虚拟机线程会调用操作系统线程,可能导致计算机假死。
本地方法栈
本地方法栈内存结构和运行原理与虚拟机栈一样,不同之处在于本地方法栈用于支持Native方法的执行,存储了每个Native方法调用的状态
堆
Java堆(Java Heap)
由JVM自动管理的线程区域,在JVM启动时创建,用于存储对象。堆可以处于逻辑上连续、物理上不连续的空间当中,既可以实现为固定大小,也可以实现为可扩展的,当前主流虚拟机实现都是可扩展的,可通过相关的虚拟机参数进行配置。
所有通过new关键字创建的对象都在堆中分配,堆的大小可以通过-Xmx和-Xms来控制。堆被划分为新生代和老年代,新生代又被进一步划分为Eden和Survivor区, Survivor又由From Space和To Space组成。(注:有些资料里也会把两个一样的survivor区称为s1和s2区,本质都是一样的)
新生代。新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例
老年代。用于存放新生代中经过多次垃圾回收仍然存活的对象
持久带(Permanent Space)实现方法区,主要存放所有已加载的类信息,方法信息,常量池等等。可通过-XX:PermSize和-XX:MaxPermSize来指定持久带初始化值和最大值。Permanent Space并不等同于方法区,只不过是Hotspot JVM用Permanent Space来实现方法区而已,有些虚拟机没有Permanent Space而用其他机制来实现方法区。
-Xmx:最大堆内存,如:-Xmx512m
-Xms:初始时堆内存,如:-Xms256m
-Xmn:新生代大小
-XX:MaxNewSize:最大年轻区内存
-XX:NewSize:初始时年轻区内存.通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90%。
-XX:MaxPermSize:最大持久带内存
-XX:PermSize:初始时持久带内存
-XX:+PrintGCDetails:打印 GC 信息
-XX:NewRatio 新生代与老年代的比例,如 –XX:NewRatio=2,则新生代占整个堆空间的1/3,老年代占2/3
-XX:SurvivorRatio 新生代中 Eden 与 Survivor 的比值。默认值为 8。即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10,在jdk7 update23版本以前,默认Eden:surivor1=8:1的比例,之后版本默认为6:1,该比例都是可以通过参数改变的。
![](https://oscimg.oschina.net/oscnet/7230e7646d97c2d6d1e7927c5374d588e91.jpg)
-XX:PretenureSizeThreshold:设置对象的最大阈值(在Parallel收集器下无效)配上-XX:+UseSerialGC,使用Serial收集器即可。
堆空间的异常:OutOfMemoryError
新生代+老年代超过-Xmx上限。则会抛出OutOfMemoryError
非堆
线程共享区域,主要存储Class文件被加载后的运行时相关信息。其中包含:代码缓存、方法区、类指针压缩空间(jdk8)。
方法区(Method Area)
JVM自动管理的线程共享区域,在JVM启动时创建,用于存储所有Class文件相关的“元数据”或者叫类信息、静态变量、字符串、常量、常量池(字面值、符号引用)、字段描述、方法描述。
类型描述信息:
类的全限定名、父类的全限定名、类或接口的标记、类的修饰符、已排序的接口列表。
类型主体信息:
实例变量信息、静态变量信息、方法信息、指向该类的引用、指向该类的类加载器引用。
运行时常量池
常量值、字段引用、方法引用、接口方法引用。
方法代码:
方法字节码操作数栈大小局部变量表大小、局部变量表、异常表、每个异常处理器、开始点、结束点、异常处理代码的程序计数器、被捕获的异常对应的常量池下标。
方法区相关的参数:
·-XX:MetaspaceSize=?元空间初始大小(jdk1.7之前-XX:PermSize)
·-XX:MaxMetaspaceSize=?元空间最大上限(jdk1.7之前-XX:MaxPermSize)
对于方法区如果不给上限,那最大上限就是整个操作系统的内存最大容量。
·-XX:+TraceClassLoading:跟踪类的加载信息
·-XX:+TraceClassLoadingPreorder:跟踪被引用到的所有类的加载信息
·-XX:+TraceClassResolution:跟踪常量池
·-XX:+TraceClassUnloading:跟踪类的卸载信息
·-XX:TraceLoaderConstraints:跟踪类加载器约束的相关信息
类指针压缩空间(Compressed Class Space)
每个对象的对象头中除了有MarkWord(mark)以外,还有一个指向它自身类的指针(klass),在32bit和64bit虚拟机中,klass指针大小分别是32bit和64bit。64bit平台上默认开启类指针压缩后可将klass压缩为32bit.
类指针压缩空间相关的参数:
·-XX:+UseCompressedClassPointers 开启类指针压缩空间
·-XX:+ CompressedClassSpaceSize=?设置类指针压缩空间的大小
·-XX:+UseCompressedOops开启对象的类指针压缩功能
代码缓存区(Code Cache)
HotSpot具有即时编译,可以基于计数器的热点探测功能发现“热点代码”(频繁调用的方法或方法中的循环体)。
缓存区包含Client Compiler和Server Compiler,简称C1、C2.
通过参数-XX:CompileThreshold=?可进行触发条件的控制。
Code Cache主要用于存储被“JIT Compiler”编译生成的本地平台相关的机器码。
非堆区的异常:OutOfMemoryError
Metaspace超过-XX:MaxMetaspaceSize的上限,则抛出OutOfMemoryError,默认无上限。
Compressed Class Spac超过-XX:CompressedClassSpaceSize的上限,则抛出OutOfMemoryError,默认1G.
对整个运行时数据区的总结:
四、JVM垃圾回收
垃圾的判定:
1)引用计数算法(Reference Counting):
比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法的缺点是无法处理循环引用或者相互引用的问题。目前虚拟机不会采用该算法!!!
2)可达性分析算法(Reachability Analysis):
也称为“根搜索算法”,使用“常量引用的对象”、“静态属性引用的对象”、“局部变量引用的对象”作为“GC Root”对象。以“GC Root”作为起始节点,依次向下查找对象间的引用关系(引用链),如果引用关系可达,则表示该对象为存活对象;如果引用关系不可达,则表示为垃圾对象。对于相互引用,但“GC Root”不可达的对象,也视为垃圾对象。
枚举根节点(Enumeration GC Root):
JVM维护了一组GC Root的集合,称为“GC Roo Set”(根集合),以每个GC Root 为地点,展开“可达性分析”进而标记垃圾对象。此操作必须在确保一致性的快照中进行,保证所有引用在此期间不存在变化的情况,会触发所有用户线程暂停(挂起),官方称为“Stop The Word”(STW)。
垃圾回收算法:
1、标记-清除(Mark-Sweep):
此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,缺点:会产生内存碎片。造成内存不连续,如果以后出现大对象可能会导致对象没法直接存放,需要压缩整理内存,从而导致效率很慢。
2、复制算法(Copying):
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理(相当于的复制过程中直接进行了压缩整理),不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。如果有4G的内存时每次只能使用2G.浪费内存。
3、标记-整理(Mark-Compact)
也称为标记-压缩算法,此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。
4、分代收集算法(Generational Collection)
新生代:采用复制算法,Eden+From存储保留To;Eden+To存储保留From.
老年代:采用标记-清楚算法或者标记-压缩算法。
1、新生代的GC:
新生代通常存活时间较短,因此基于Copying算法来进行回收,所谓Copying算法就是扫描出存活的对象,并复制到一块新的完全未使用的空间中(在新生代的两个survivor区域之间复制来复制去。),对应于新生代,就是在Eden和From Space或To Space之间copy。新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。当连续分配对象时,对象会逐渐从eden到survivor,最后到老年代。
在执行机制上JVM提供了串行GC(Serial GC)、并行回收GC(Parallel Scavenge)和并行GC(ParNew)
1)串行GC
在整个扫描和复制过程采用单线程的方式来进行,适用于单CPU、新生代空间较小及对暂停时间要求不是非常高的应用上,是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定
2)并行回收GC
在整个扫描和复制过程采用多线程的方式来进行,适用于多CPU、对暂停时间要求较短的应用上,是server级别默认采用的GC方式,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数
3)并行GC
与老年代的并发GC配合使用
2、老年代的GC:
老年代与新生代不同,对象存活的时间比较长,比较稳定,因此采用标记压缩(Mark)算法来进行回收,所谓标记就是扫描出存活的对象,然后再进行回收未被标记的对象,把内存压缩出来。回收后对用空出的空间要么进行合并,要么标记出来便于下次进行分配,总之就是要减少内存碎片带来的效率损耗。在执行机制上JVM提供了串行GC(Serial MSC)、并行GC(parallel MSC)和并发GC(CMS),具体算法细节还有待进一步深入研究。
以上各种GC机制是需要组合使用的,指定方式由下表所示:
指定方式 新生代GC方式 老年代GC方式
-XX:+UseSerialGC 串行GC 串行GC
-XX:+UseParallelGC 并行回收GC 并行GC
-XX:+UseConeMarkSweepGC 并行GC 并发GC
-XX:+UseParNewGC 并行GC 串行GC
-XX:+UseParallelOldGC 并行回收GC 并行GC
-XX:+ UseConeMarkSweepGC -XX:+UseParNewGC 串行GC 并发GC
不支持的组合 1、-XX:+UseParNewGC -XX:+UseParallelOldGC 2、-XX:+UseParNewGC -XX:+UseSerialGC
五、JVM优化原则
JVM型号:
首先需要注意的是在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。
对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数,过多的GC和Full GC是会占用很多的系统资源(主要是CPU),影响系统的吞吐量(吞吐量:就是这个时刻这个CPU能够同时处理的线程或者进程)。特别要关注Full GC,因为它会对整个堆进行整理,导致Full GC一般由于以下几种情况:
-
老年代空间不足
调优时尽量让对象在新生代GC时被回收、让对象在新生代多存活一段时间和不要创建过大的对象及数组避免直接在老年代创建对象 -
Pemanet Generation空间不足
增大Perm Gen空间,避免太多静态对象统计得到的GC后晋升到老年代的平均大小大于老年代剩余空间
控制好新生代和老年代的比例
-
System.gc()被显示调用
垃圾回收不要手动触发,尽量依靠JVM自身的机制调优手段主要是通过控制堆内存的各个部分的比例和GC策略来实现,下面来看看各部分比例不良设置会导致什么后果
1)新生代设置过小
一是新生代GC次数非常频繁,增大系统消耗;二是导致大对象直接进入老年代,占据了老年代剩余空间,诱发Full GC2)新生代设置过大
一是新生代设置过大会导致老年代过小(堆总量一定),从而诱发Full GC;二是新生代GC耗时大幅度增加, 一般说来新生代占整个堆1/3比较合适;新生代分配太大或者太小都会诱发Full GC的触发。3)Survivor设置过小
导致对象从eden直接到达老年代,降低了在新生代的存活时间4)Survivor设置过大
导致eden过小,增加了GC频率。另外,通过-XX:MaxTenuringThreshold=n来控制新生代存活时间,尽量让对象在新生代被回收由内存管理和垃圾回收可知新生代和老年代都有多种GC策略和组合搭配,选择这些策略对于我们这些开发人员是个难题,JVM提供两种较为简单的GC策略的设置方式
1)吞吐量优先
JVM以吞吐量为指标,自行选择相应的GC策略及控制新生代与老年代的大小比例,来达到吞吐量指标。这个值可由-XX:GCTimeRatio=n来设置2)暂停时间优先
JVM以暂停时间为指标,自行选择相应的GC策略及控制新生代与老年代的大小比例,尽量保证每次GC造成的应用停止时间都在指定的数值范围内完成。这个值可由-XX:MaxGCPauseRatio=n来设置
六、JVM参数配置
一)、堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置持久代大小
归纳:
-verbose:class:后面没有X的都是相对比较持久的参数
-X:一个X为可变或可动态的参数,相对来来说可能在后续的版本会有一些改变。
-XX:两个X这样的参数就是频繁变化的,频繁增删的,比如可能在jdk6有而在jdk7就没有了,或者jdk8又有了。。。
基础分配分配策略
优化分配策略
A.快速分配:
使用bump-the-pointer(指针碰撞)进行快速的连续空间分配,多线程下必须线程安全。
Thread-Local Allocation Buffer(线程局部分配缓存):
在Eden区默认划分低于1%de线程私有空间,可在不加锁情况下使用“指针碰撞”,单个对象过大无法存入TLAB时,再存人Eden区-XX:+UseTLAB
-XX:+UseTLAB:jdk7之后默认是开启的。
B.栈上分配:
逃逸分析:分析对象的动态作用域。-XX:+DoEscapeAnalysis.在jdk7也默认开启。以下两种情况下对象在栈上分配比在堆上分配效率高10倍之多。
线程逃逸:该对象无法在线程以外的位置访问,则伴随JVM栈销毁而消失。
方法逃逸:该对象无法在方法以外的位置访问,则伴随栈帧出栈而消失。
标量替换:-XX:+EliminateAllocations.-如果不存在逃逸,直接存储对象的各个成员变量。标量替换和逃逸分析相辅相成,二者缺一不可,只有这样才能在栈上分配,才能把一个对象拆解成基本类型进而存储到我们的栈帧结构当中。
同步消除:如果不存在逃逸同步措施即可直接清除。-XX:+EliminateLocks.
二)、收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行年老代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
三)、垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
四)、并行收集器设置
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
七、JVM字节码指令
常量入栈指令
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0x01 aconst_null null值入栈。
0x02 iconst_m1 -1(int)值入栈。
0x03 iconst_0 0(int)值入栈。
0x04 iconst_1 1(int)值入栈。
0x05 iconst_2 2(int)值入栈。
0x06 iconst_3 3(int)值入栈。
0x07 iconst_4 4(int)值入栈。
0x08 iconst_5 5(int)值入栈。
0x09 lconst_0 0(long)值入栈。
0x0a lconst_1 1(long)值入栈。
0x0b fconst_0 0(float)值入栈。
0x0c fconst_1 1(float)值入栈。
0x0d fconst_2 2(float)值入栈。
0x0e dconst_0 0(double)值入栈。
0x0f dconst_1 1(double)值入栈。
0x10 bipush valuebyte valuebyte值带符号扩展成int值入栈。
0x11 sipush valuebyte1 valuebyte2 (valuebyte1 << 8) | valuebyte2 值带符号扩展成int值入栈。
0x12 ldc indexbyte1 常量池中的常量值(int, float, string reference, object reference)入栈。
0x13 ldc_w indexbyte1 indexbyte2 常量池中常量(int, float, string reference, object reference)入栈。
0x14 ldc2_w indexbyte1 indexbyte2 常量池中常量(long, double)入栈。
局部变量值转载到栈中指令
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0x19 (wide)aload indexbyte 从局部变量indexbyte中装载引用类型值入栈。
0x2a aload_0 从局部变量0中装载引用类型值入栈。
0x2b aload_1 从局部变量1中装载引用类型值入栈。
0x2c aload_2 从局部变量2中装载引用类型值入栈。
0x2d aload_3 从局部变量3中装载引用类型值入栈。
0x15 (wide)iload indexbyte 从局部变量indexbyte中装载int类型值入栈。
0x1a iload_0 从局部变量0中装载int类型值入栈。
0x1b iload_1 从局部变量1中装载int类型值入栈。
0x1c iload_2 从局部变量2中装载int类型值入栈。
0x1d iload_3 从局部变量3中装载int类型值入栈。
0x16 (wide)lload indexbyte 从局部变量indexbyte中装载long类型值入栈。
0x1e lload_0 从局部变量0中装载int类型值入栈。
0x1f lload_1 从局部变量1中装载int类型值入栈。
0x20 lload_2 从局部变量2中装载int类型值入栈。
0x21 lload_3 从局部变量3中装载int类型值入栈。
0x17 (wide)fload indexbyte 从局部变量indexbyte中装载float类型值入栈。
0x22 fload_0 从局部变量0中装载float类型值入栈。
0x23 fload_1 从局部变量1中装载float类型值入栈。
0x24 fload_2 从局部变量2中装载float类型值入栈。
0x25 fload_3 从局部变量3中装载float类型值入栈。
0x18 (wide)dload indexbyte 从局部变量indexbyte中装载double类型值入栈。
0x26 dload_0 从局部变量0中装载double类型值入栈。
0x27 dload_1 从局部变量1中装载double类型值入栈。
0x28 dload_2 从局部变量2中装载double类型值入栈。
0x29 dload_3 从局部变量3中装载double类型值入栈。
0x32 aaload 从引用类型数组中装载指定项的值。
0x2e iaload 从int类型数组中装载指定项的值。
0x2f laload 从long类型数组中装载指定项的值。
0x30 faload 从float类型数组中装载指定项的值。
0x31 daload 从double类型数组中装载指定项的值。
0x33 baload 从boolean类型数组或byte类型数组中装载指定项的值(先转换为int类型值,后压栈)。
0x34 caload 从char类型数组中装载指定项的值(先转换为int类型值,后压栈)。
0x35 saload 从short类型数组中装载指定项的值(先转换为int类型值,后压栈)。
将栈顶值保存到局部变量中指令
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0x3a (wide)astore indexbyte 将栈顶引用类型值保存到局部变量indexbyte中。
0x4b astroe_0 将栈顶引用类型值保存到局部变量0中。
0x4c astore_1 将栈顶引用类型值保存到局部变量1中。
0x4d astore_2 将栈顶引用类型值保存到局部变量2中。
0x4e astore_3 将栈顶引用类型值保存到局部变量3中。
0x36 (wide)istore indexbyte 将栈顶int类型值保存到局部变量indexbyte中。
0x3b istore_0 将栈顶int类型值保存到局部变量0中。
0x3c istore_1 将栈顶int类型值保存到局部变量1中。
0x3d istore_2 将栈顶int类型值保存到局部变量2中。
0x3e istore_3 将栈顶int类型值保存到局部变量3中。
0x37 (wide)lstore indexbyte 将栈顶long类型值保存到局部变量indexbyte中。
0x3f lstore_0 将栈顶long类型值保存到局部变量0中。
0x40 lstore_1 将栈顶long类型值保存到局部变量1中。
0x41 lstore_2 将栈顶long类型值保存到局部变量2中。
0x42 lstroe_3 将栈顶long类型值保存到局部变量3中。
0x38 (wide)fstore indexbyte 将栈顶float类型值保存到局部变量indexbyte中。
0x43 fstore_0 将栈顶float类型值保存到局部变量0中。
0x44 fstore_1 将栈顶float类型值保存到局部变量1中。
0x45 fstore_2 将栈顶float类型值保存到局部变量2中。
0x46 fstore_3 将栈顶float类型值保存到局部变量3中。
0x39 (wide)dstore indexbyte 将栈顶double类型值保存到局部变量indexbyte中。
0x47 dstore_0 将栈顶double类型值保存到局部变量0中。
0x48 dstore_1 将栈顶double类型值保存到局部变量1中。
0x49 dstore_2 将栈顶double类型值保存到局部变量2中。
0x4a dstore_3 将栈顶double类型值保存到局部变量3中。
0x53 aastore 将栈顶引用类型值保存到指定引用类型数组的指定项。
0x4f iastore 将栈顶int类型值保存到指定int类型数组的指定项。
0x50 lastore 将栈顶long类型值保存到指定long类型数组的指定项。
0x51 fastore 将栈顶float类型值保存到指定float类型数组的指定项。
0x52 dastore 将栈顶double类型值保存到指定double类型数组的指定项。
0x54 bastroe 将栈顶boolean类型值或byte类型值保存到指定boolean类型数组或byte类型数组的指定项。
0x55 castore 将栈顶char类型值保存到指定char类型数组的指定项。
0x56 sastore 将栈顶short类型值保存到指定short类型数组的指定项。
wide指令
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0xc4 wide 使用附加字节扩展局部变量索引(iinc指令特殊)。
通用(无类型)栈操作指令
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0x00 nop 空操作。
0x57 pop 从栈顶弹出一个字长的数据。
0x58 pop2 从栈顶弹出两个字长的数据。
0x59 dup 复制栈顶一个字长的数据,将复制后的数据压栈。
0x5a dup_x1 复制栈顶一个字长的数据,弹出栈顶两个字长数据,先将复制后的数据压栈,再将弹出的两个字长数据压栈。
0x5b dup_x2 复制栈顶一个字长的数据,弹出栈顶三个字长的数据,将复制后的数据压栈,再将弹出的三个字长的数据压栈。
0x5c dup2 复制栈顶两个字长的数据,将复制后的两个字长的数据压栈。
0x5d dup2_x1 复制栈顶两个字长的数据,弹出栈顶三个字长的数据,将复制后的两个字长的数据压栈,再将弹出的三个字长的数据压栈。
0x5e dup2_x2 复制栈顶两个字长的数据,弹出栈顶四个字长的数据,将复制后的两个字长的数据压栈,再将弹出的四个字长的数据压栈。
0x5f swap 交换栈顶两个字长的数据的位置。Java指令中没有提供以两个字长为单位的交换指令。
类型转换指令
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0x86 i2f 将栈顶int类型值转换为float类型值。
0x85 i2l 将栈顶int类型值转换为long类型值。
0x87 i2d 将栈顶int类型值转换为double类型值。
0x8b f2i 将栈顶float类型值转换为int类型值。
0x8c f2l 将栈顶float类型值转换为long类型值。
0x8d f2d 将栈顶float类型值转换为double类型值。
0x88 l2i 将栈顶long类型值转换为int类型值。
0x89 l2f 将栈顶long类型值转换为float类型值。
0x8a l2d 将栈顶long类型值转换double类型值。
0x8e d2i 将栈顶double类型值转换为int类型值。
0x90 d2f 将栈顶double类型值转换为float类型值。
0x8f d2l 将栈顶double类型值转换为long类型值。
0x91 i2b 将栈顶int类型值截断成byte类型,后带符号扩展成int类型值入栈。
0x92 i2c 将栈顶int类型值截断成char类型值,后带符号扩展成int类型值入栈。
0x93 i2s 将栈顶int类型值截断成short类型值,后带符号扩展成int类型值入栈。
整数运算
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0x60 iadd 将栈顶两int类型数相加,结果入栈。
0x64 isub 将栈顶两int类型数相减,结果入栈。
0x68 imul 将栈顶两int类型数相乘,结果入栈。
0x6c idiv 将栈顶两int类型数相除,结果入栈。
0x70 irem 将栈顶两int类型数取模,结果入栈。
0x74 ineg 将栈顶int类型值取负,结果入栈。
0x61 ladd 将栈顶两long类型数相加,结果入栈。
0x65 lsub 将栈顶两long类型数相减,结果入栈。
0x69 lmul 将栈顶两long类型数相乘,结果入栈。
0x6d ldiv 将栈顶两long类型数相除,结果入栈。
0x71 lrem 将栈顶两long类型数取模,结果入栈。
0x75 lneg 将栈顶long类型值取负,结果入栈。
0x84 (wide)iinc indexbyte constbyte 将整数值constbyte加到indexbyte指定的int类型的局部变量中。
浮点运算
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0x62 fadd 将栈顶两float类型数相加,结果入栈。
0x66 fsub 将栈顶两float类型数相减,结果入栈。
0x6a fmul 将栈顶两float类型数相乘,结果入栈。
0x6e fdiv 将栈顶两float类型数相除,结果入栈。
0x72 frem 将栈顶两float类型数取模,结果入栈。
0x76 fneg 将栈顶float类型值取反,结果入栈。
0x63 dadd 将栈顶两double类型数相加,结果入栈。
0x67 dsub 将栈顶两double类型数相减,结果入栈。
0x6b dmul 将栈顶两double类型数相乘,结果入栈。
0x6f ddiv 将栈顶两double类型数相除,结果入栈。
0x73 drem 将栈顶两double类型数取模,结果入栈。
0x77 dneg 将栈顶double类型值取负,结果入栈。
逻辑运算——移位运算
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0x78 ishl 左移int类型值。
0x79 lshl 左移long类型值。
0x7a ishr 算术右移int类型值。
0x7b lshr 算术右移long类型值。
0x7c iushr 逻辑右移int类型值。
0x7d lushr 逻辑右移long类型值。
逻辑运算——按位布尔运算
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0x73 iand 对int类型按位与运算。
0x7f land 对long类型的按位与运算。
0x80 ior 对int类型的按位或运算。
0x81 lor 对long类型的按位或运算。
0x82 ixor 对int类型的按位异或运算。
0x83 lxor 对long类型的按位异或运算。
控制流指令——条件跳转指令
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0x99 ifeq branchbyte1 branchbyte2 若栈顶int类型值为0则跳转。
0x9a ifne branchbyte1 branchbyte2 若栈顶int类型值不为0则跳转。
0x9b iflt branchbyte1 branchbyte2 若栈顶int类型值小于0则跳转。
0x9e ifle branchbyte1 branchbyte2 若栈顶int类型值小于等于0则跳转。
0x9d ifgt branchbyte1 branchbyte2 若栈顶int类型值大于0则跳转。
0x9c ifge branchbyte1 branchbyte2 若栈顶int类型值大于等于0则跳转。
0x9f if_icmpeq branchbyte1 branchbyte2 若栈顶两int类型值相等则跳转。
0xa0 if_icmpne branchbyte1 branchbyte2 若栈顶两int类型值不相等则跳转。
0xa1 if_icmplt branchbyte1 branchbyte2 若栈顶两int类型值前小于后则跳转。
0xa4 if_icmple branchbyte1 branchbyte2 若栈顶两int类型值前小于等于后则跳转。
0xa3 if_icmpgt branchbyte1 branchbyte2 若栈顶两int类型值前大于后则跳转。
0xa2 if_icmpge branchbyte1 branchbyte2 若栈顶两int类型值前大于等于后则跳转。
0xc6 ifnull branchbyte1 branchbyte2 若栈顶引用值为null则跳转。
0xc7 ifnonnull branchbyte1 branchbyte2 若栈顶引用值不为null则跳转。
0xa5 if_acmpeq branchbyte1 branchbyte2 若栈顶两引用类型值相等则跳转。
0xa6 if_acmpne branchbyte1 branchbyte2 若栈顶两引用类型值不相等则跳转。
控制流指令——比较指令
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0x94 lcmp 比较栈顶两long类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈。
0x95 fcmpl 比较栈顶两float类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈;有NaN存在,-1入栈。
0x96 fcmpg 比较栈顶两float类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈;有NaN存在,-1入栈。
0x97 dcmpl 比较栈顶两double类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈;有NaN存在,-1入栈。
0x98 dcmpg 比较栈顶两double类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈;有NaN存在,-1入栈。
控制流指令——无条件跳转指令
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0xa7 goto branchbyte1 branchbyte2 无条件跳转到指定位置。
0xc8 goto_w branchbyte1 branchbyte2 branchbyte3 branchbyte4 无条件跳转到指定位置(宽索引)。
控制流指令——表跳转指令
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0xaa tableswitch <0-3bytepad> defaultbyte1 defaultbyte2 defaultbyte3 defaultbyte4 lowbyte1 lowbyte2 lowbyte3 lowbyte4 highbyte1 highbyte2 highbyte3 highbyte4 jump offsets… 通过索引访问跳转表,并跳转。
0xab lookupswitch <0-3bytepad> defaultbyte1 defaultbyte2 defaultbyte3 defaultbyte4 npairs1 npairs2 npairs3 npairs4 match offsets 通过键值访问跳转表,并跳转。
控制流指令——异常和finally
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0xbf athrow 抛出异常。
0xa8 jsr branchbyte1 branchbyte2 跳转到子例程序。
0xc9 jsr_w branchbyte1 branchbyte2 branchbyte3 branchbyte4 跳转到子例程序(宽索引)。
0xa9 (wide)ret indexbyte 返回子例程序。
对象操作指令
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0xbb new indexbyte1 indexbyte2 创建新的对象实例。
0xc0 checkcast indexbyte1 indexbyte 类型强转。
0xc1 instanceof indexbyte1 indexbyte2 判断类型。
0xb4 getfield indexbyte1 indexbyte2 获取对象字段的值。
0xb5 putfield indexbyte1 indexbyte2 给对象字段赋值。
0xb2 getstatic indexbyte1 indexbyte2 获取静态字段的值。
0xb3 putstatic indexbyte1 indexbyte2 给静态字段赋值。
数组操作指令
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0xbc newarray atype 创建type类型的数组。
0xbd anewarray indexbyte1 indexbyte2 创建引用类型的数组。
0xbe arraylength 获取一维数组的长度。
0xc5 multianewarray indexbyte1 indexbyte2 dimension 创建dimension维度的数组。
方法调用指令
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0xb7 invokespecial indexbyte1 indexbyte2 编译时方法绑定调用方法。
0xb6 invokevirtual indexbyte1 indexbyte2 运行时方法绑定调用方法。
0xb8 invokestatic indexbyte1 indexbyte2 调用静态方法。
0xb9 invokeinterface indexbyte1 indexbyte2 count 0 调用接口方法。
方法返回指令
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0xac ireturn 返回int类型值。
0xad lreturn 返回long类型值。
0xae freturn 返回float类型值。
0xaf dreturn 返回double类型值。
0xb0 areturn 返回引用类型值。
0xb1 return void函数返回。
线程同步指令
指令码 操作码(助记符) 操作数 描述(栈指操作数栈)
0xc2 monitorenter 进入并获得对象监视器。
0xc3 monitorexit 释放并退出对象监视器。
说明:
本文档虚拟机参数以Hot Spot 64bit/JDK8为准,不同的项目参数可能不同,找准适合当前项目的才是最优的。有不同观点者欢迎指导和交流!!!
推荐阅读