java虚拟机
2021/4/22 20:25:28
本文主要是介绍java虚拟机,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
如图,运行期环境代表着java平台,开发人员编写java代码(.java文件),然后将之编译成字节码(.class文件),再然后字节码被装入内存,一旦字节码进入虚拟机,它就会被解释器解释执行,或者是被即时代码发生器有选择的转换成机器码执行, 在java平台的结构中, 可以看出java虚拟机(jvm) 处在核心的位置,是程序与底层操作系统和硬件无关的关键。
1、基本概念
如图,上图是指现实中的计算机
- jvm 是可运行 java 代码的假想计算机 ,包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收,堆 和 一个存储方法域。jvm 是运行在操作系统之上的,它与硬件没有直接的交互.
- Java虚拟机(JVM)是可运行Java代码的假想计算机。只要根据JVM规格描述将解释器移植到特定的计算机上,就能保证经过编译的任何Java代码能够在该系统上运行。
- Java虚拟机是一个想象中的机器,在实际的计算机上通过软件模拟来实现。Java虚拟机有自己想象中的硬件,如处理器、堆栈、寄存器等,还具有相应的指令系统。
- Java虚拟机规范定义了一个抽象的——而非实际的——机器或处理器。这个规范描述了一个指令集,一组寄存器,一个堆栈,一个“垃圾堆”,和一个方法区。一旦一个Java虚拟机在给定的平台上运行,任何Java程序(编译之后的程序,称作字节码)都能在这个平台上运行。Java虚拟机(JVM)可以以一次一条指令的方式来解释字节码(把它映射到实际的处理器指令),或者字节码也可以由实际处理器中称作just-in-time的编译器进行进一步的编译
2、运行过程
我们都知道 java 源文件,通过编译器,能够生产相应的.Class 文件,也就是字节码文件,而字节码文件又通过 Java 虚拟机中的解释器,编译成特定机器上的机器码 。
也就是如下:
-
java 源文件—->编译器—->字节码文件
-
字节码文件—->JVM—->机器码
每一种平台的解释器是不同的(比如windows 与linux安装jdk是不同的),但是实现的虚拟机是相同的,这也就是 java 为什么能够跨平台的原因了 ,当一个程序从开始运行,这时虚拟机就开始实例化了,多个程序启动就会存在多个虚拟机实例(比如一台服务器上运行多个java 程序,可以公用同一jdk),程序退出或者关闭,则虚拟机实例消亡,多个虚拟机实例之间数据不能共享
3、java虚拟机体系结构
3.1 Class Loader类加载器
负责加载 .class文件,class文件在文件开头有特定的文件标示,并且ClassLoader负责class文件的加载等,至于它是否可以运行,则由Execution Engine决定
3.2 Native Interface本地方法接口
本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序,Java诞生的时候C/C++横行的时候,要想立足,必须有调用C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体作法是Native Method Stack中登记native方法,在Execution Engine执行时加载native libraies。
目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机,或者Java系统管理生产设备,在企业级应用中已经比较少见, 因为现在的异构领域间的通信很发达,比如可以使用Socket通信,也可以使用Web Service等。
3.3 Execution Engine 执行引擎
执行包在装载类的方法中的指令,也就是方法。
3.4 Runtime data area 运行数据区(栈管运行,堆管存储)
虚拟机内存或者jvm内存,从整个计算机内存中开辟一块内存存储jvm需要用到的对象,变量等,运行区数据有分很多小区,分别为:方法区,虚拟机栈,本地方法栈,堆,程序计数器。jvm调优主要就是优化 Heap堆 和 Method Area 方法区。
3.4.1 Native Method Stack 本地方法栈
本地方法栈与虚拟机栈所发挥的作用是非常相似的,区别是虚拟机栈是为虚拟机执行java方法(也就是字节码)服务,而本地方法栈则为Native Method Stack中登记native方法服务,在Execution Engine执行时加载native libraies。
3.4.2 PC Register程序计数器
-
是一个非常小的内存空间,几乎可以忽略不记。
-
PC寄存器( PC register ):每个线程启动的时候,都会创建一个PC(Program Counter,程序计数器)寄存器。PC寄存器里保存有当前正在执行的JVM指令的地址。 每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。保存下一条将要执行的指令地址的寄存器是 :PC寄存器。PC寄存器的内容总是指向下一条将被执行指令的地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。
-
java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,互不影响。
引申:
-
机器语言是机器指令的集合。这些机器指令本质上就是由一组0和1组成的命令,是CPU唯一能解释执行的命令;
-
寄存器可以理解为CPU中的存储器或者内存,是CPU中可以存储数据的器件。
-
电子计算机能处理和传输的信息都是电信号,电信号是使用导线来传送的,所以CPU也是通常用导线把地址信息、控制信息和数据信息传递至存储器的存储芯片中去的
3.4.3 Method Area方法区
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间。
静态变量,常量,类信息(构造方法/接口定义),运行时常量池存在方法区中(存放所有的①类(class),②静态变量(static变量),③静态方法,④常量和⑤成员方法);但是实例变量存在堆内存中,和方法区无关。
方法区(Method Area)同Java 堆,是各个线程共享的内存区域,用于存储虚拟机已经加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是方法区却有一个别名叫做Non-Heap(非堆),目的就是把方法区和Java堆区分开来。
在HotSpot虚拟机上开发部署程序的开发者把方法区称为“永久代(Permanent Generation)”,本质上两者并不等价,仅仅是因为HotSpot虚拟机的设计者们选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已,这样HotSpot的垃圾收集器可以像管理Java堆一样管理这部分内存,能够省去专门为方法区编写内存代码的工作。对于其他虚拟机来说(如BEA JRockit, IBM J9等)是不存在永久代的概念的。原则上,怎样实现方法区是属于虚拟机的实现细节,不受虚拟机规范约束,但是使用永久代来实现方法区,现在看来并不是一个好主意,因为这样很容易遇到内存溢出问题(永久代有-XX:MaxPermSize的上限,J9和JRockit只要没有触碰到进程可用内存的上限,例如32为系统中的4G,就不会出现问题),而且有极少数方法(例如String.intern())会因为这个原因导致不同虚拟机下有不同的表现。
因此,对于HotSpot虚拟机,根据官方发布的路线图信息,现在也有放弃永久代并逐步改为采用Native Memory 来实现方法区的规划了,在目前已经发布的JDK 1.7的HotSpot中,已经把原本放在永久代的字符串常量池移出,JDK 1.8 的HotSpot中方法区已经被彻底移出了,取而代之使用的是元空间(Metaspace),元空间使用的是本地内存(Native Memory)。以下是一些常用参数:
-XX:MetaspaceSize=N //设置Metaspace的初始和最小大小
-XX : MaxMetaspaceSize=N //设置Metaspace的最大大小
与永久代不同,如果元空间不指定大小,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。
Java虚拟机规范对方法区的限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集,相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代的名字一样是永久存在了。这个区域内存回收目标主要是这对常量池的回收和对类型的卸载,但是,这个区域的回收成绩却难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分的回收确实是必要的。在SUN公司的BUG列表中,曾出现过若干个严重的bug就是由于低版本的HotSpot虚拟机对此区域未完全回收而导致内存泄露。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
JDK 1.8 废弃永久代而使用元空间就是因为,这个永久代有一个JVM本身设置的固定大小线,无法进行调整,而且设置为多大的空间很难确定,PermSize的大小依赖于很多因素,如JVM加载的class总数,常量池的大小,方法的大小等,而元空间使用的是本地内存,受本机可用内存的限制,永远也不会有OutOfMemoryError异常。JVM不再有PermGen但类的元数据信息还在,只不过放到了本地内存,用户可以为类元数据信息指定最大可利用的本地内存空间,JVM也可以增加本地内存空间来满足类元数据信息的存储。此外,在HotSpot中的每个垃圾收集器需要专门的代码来处理存储在PermGen中的类元数据信息,从PermGen分离类的元数据信息到元空间,由于元空间的分配具有和Java 堆相同的地址空间,因此元空间和Java堆可以无缝的管理,而且简化了FullGC的过程,这样将来可以并行的对元数据信息进行垃圾收集,而没有GC暂停。
方法区的实现的演变
Jdk1.7之前:hotspot虚拟机对方法区的实现为永久代 。
Jdk1.8及之后:hotspot移除了永久代用元空间(Metaspace)。
运行时常量池存和字符串常量池的变化
JDK1.7之前:
`运行时常量池`(包含`字符串常量池` )存放在`方法区`,此时 hotspot 虚拟机对方法区的实现为`永久代`。
JDK1.7:
`字符串常量池`被从`方法区`拿到了`堆`中;
`运行时常量池`剩下的东西还在`方法区`, 也就是hotspot中的永久代。
JDK1.8 :
hotspot移除了`永久代`,用`元空间`(Metaspace) 取而代之。这时候,
`字符串常量池`还在`堆`,
`运行时常量池`还在`方法区`, 只不过方法区的实现从`永久代`变成`元空间`(Metaspace)。
3.4.4 Stack 栈
-
栈是什么?
栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的。
基本类型的变量和对象的引用变量都是在函数的栈内存中分配。
驻留于常规RAM(随机访问存储器,具体到电脑上指的是内存条)区域。但可通过它的“栈指针”获取处理的直接支持。栈指针若向下移,会创建新的内存;若向上移,则会释放那些内存。这是一种特别快、特别有效的数据保存方式,仅次于寄存器
-
栈存储什么?
栈帧中主要保存3类数据:
本地变量(Local Variables):输入参数和输出参数以及方法内的变量;
栈操作(Operand Stack):记录出栈、入栈的操作;
栈帧数据(Frame Data):包括类文件、方法等等。
-
栈运行原理?
栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中, A方法又调用了B方法,于是产生栈帧F2也被压入栈, B方法又调用了C方法,于是产生栈帧F3也被压入栈…… 依次执行完毕后,先弹出后进......F3栈帧,再弹出F2栈帧,再弹出F1栈帧。
遵循“先进后出”/“后进先出”原则。
总结:
1.每个线程包含一个栈区,栈中只保存基本数据类型的数据和自定义对象的引用(不是对象),对象都存放在堆区中 2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。 3.栈分为3个部分:基本数据类型的变量区、执行环境上下文、操作指令区(存放操作指令)。
栈是存放线程调用方法时存储局部变量表,操作,方法出口等与方法执行相关的信息,Java栈所占内存的大小由Xss来调节,方法调用层次太多会撑爆这个区域。
3.4.5 Heap堆
堆这块区域是JVM中最大的,应用的对象和数据都是存在这个区域,这块区域也是线程共享的,也是 gc 主要的回收区,一个 JVM 实例只存在一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,以方便执行器执行,堆内存分为三部分:
-
新生区
新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两部分:伊甸区(Eden space)和幸存者区(Survivor pace),所有的类都是在伊甸区被new出来的。幸存区有两个:0区(Survivor 0 space)和1区(Survivor 1 space)。当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园进行垃圾回收(Minor GC),将伊甸园中的剩余对象移动到幸存0区。若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区。那如果1去也满了呢?再移动到养老区。若养老区也满了,那么这个时候将产生Major GC(FullGCC),进行养老区的内存清理。若养老区执行Full GC 之后发现依然无法进行对象的保存,就会产生OOM异常“OutOfMemoryError”。
如果出现java.lang.OutOfMemoryError: Java heap space异常,说明Java虚拟机的堆内存不够。原因有二:
a.Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整。
b.代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)。
-
养老区
养老区用于保存从新生区筛选出来的 JAVA 对象,一般池对象都在这个区域活跃。
-
永久区
永久存储区是一个常驻内存区域,用于存放JDK自身所携带的 Class,Interface 的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被回收堆内存的垃圾回收器回收掉(回收条件比较苛刻),关闭 JVM 才会释放此区域所占用的内存。
如果出现java.lang.OutOfMemoryError: PermGen space,说明是Java虚拟机对永久代Perm内存设置不够。 原因有二:
a. 程序启动需要加载大量的第三方jar包。例如:在一个Tomcat下部署了太多的应用。
b. 大量动态反射生成的类不断被加载,最终导致Perm区被占满。
说明:
1.堆的分配参数
-Xmn
- 设置新生代大小
-XX:NewRatio
- 新生代(eden+2*s)和老年代(不包含永久区)的比值
- 4 表示 新生代:老年代=1:4,即年轻代占堆的1/5
-XX:SurvivorRatio
- 设置两个Survivor区和eden的比
- 8表示 两个Survivor :eden=2:8,即一个Survivor占年轻代的1/10
2.堆的分配参数 – 总结
根据实际事情调整新生代和幸存代的大小
官方推荐新生代占堆的3/8
幸存代占新生代的1/10
在OOM时,记得Dump出堆,确保可以排查现场问题
以上,只是逻辑上的定义。 在HotSpot中,方法区仅仅只是逻辑上的独立,实际上还是包含在Java堆中,也是就说,方式区在物理上属于Java堆区中的一部分,而永久区(Permanent Generation)就是方法区的实现。
3.4.6 举例分析
例子如下:
(1)为了更清楚地搞明白程序运行时,数据区里的情况,我们来准备2个小道具(2个非常简单的小程序)。
// AppMain.java public class AppMain { //运行时,JVM把AppMain的信息都放入方法区 public static void main(String[] args) { //main成员方法本身放入方法区。 Sample test1 = new Sample( " 测试1 " ); //test1是引用,所以放到栈区里,Sample是自定义对象应该放到堆里面 Sample test2 = new Sample( " 测试2 " ); test1.printName(); test2.printName(); } } // Sample.java public class Sample { //运行时,JVM把appmain的信息都放入方法区。 private name; //new Sample实例后,name引用放入栈区里,name对象放入堆里。 public Sample(String name) { this .name = name; } public void printName() {// printName()成员方法本身放入方法区里。 System.out.println(name); } }
OK,让我们开始行动吧,出发指令就是:“java AppMain”,包包里带好我们的行动向导图。
系统收到了我们发出的指令,启动了一个Java虚拟机进程,这个进程首先从classpath中找到AppMain.class文件,读取这个文件中的二进制数据,然后把Appmain类的类信息存放到运行时数据区的方法区中。这一过程称为AppMain类的加载过程。
接着,JVM定位到方法区中AppMain类的Main()方法的字节码,开始执行它的指令。这个main()方法的第一条语句就是:
Sample test1 = new Sample("测试1");
语句很简单啦,就是让JVM创建一个Sample实例,并且呢,使引用变量test1引用这个实例。貌似小case一桩哦,就让我们来跟踪一下JVM,看看它究竟是怎么来执行这个任务的:
1、Java虚拟机一看,不就是建立一个Sample类的实例吗,简单,于是就直奔方法区(方法区存放已经加载的类的相关信息,如类、静态变量和常量)而去,先找到Sample类的类型信息再说。结果呢,没找到@@,这会儿的方法区里还没有Sample类呢(即Sample类的类信息还没有进入方法区中)。可JVM也不是一根筋的笨蛋,于是,它发扬“自己动手,丰衣足食”的作风,立马加载了Sample类, 把Sample类的相关信息存放在了方法区中。
2、Sample类的相关信息加载完成后。Java虚拟机做的第一件事情就是在堆中为一个新的Sample类的实例分配内存,这个Sample类的实例持有着指向方法区的Sample类的类型信息的引用(Java中引用就是内存地址)。这里所说的引用,实际上指的是Sample类的类型信息在方法区中的内存地址,其实,就是有点类似于C语言里的指针啦~~,而这个地址呢,就存放了在Sample类的实例的数据区中。
3、在JVM中的一个进程中,每个线程都会拥有一个方法调用栈,用来跟踪线程运行中一系列的方法调用过程,栈中的每一个元素被称为栈帧,每当线程调用一个方法的时候就会向方法栈中压入一个新栈帧。这里的帧用来存储方法的参数、局部变量和运算过程中的临时数据。OK,原理讲完了,就让我们来继续我们的跟踪行动!位于“=”前的test1是一个在main()方法中定义的变量,可见,它是一个局部变量,因此,test1这个局部变量会被JVM添加到执行main()方法的主线程的Java方法调用栈中。而“=”将把这个test1变量指向堆区中的Sample实例,也就是说,test1这个局部变量持有指向Sample类的实例的引用(即内存地址)。
OK,到这里为止呢,JVM就完成了这个简单语句的执行任务。参考我们的行动向导图,我们终于初步摸清了JVM的一点点底细了,COOL!
接下来,JVM将继续执行后续指令,在堆区里继续创建另一个Sample类的实例,然后依次执行它们的printName()方法。当JVM执行test1.printName()方法时,JVM根据局部变量test1持有的引用,定位到堆中的Sample类的实例,再根据Sample类的实例持有的引用,定位到方法区中Sample类的类型信息(包括①类,②静态变量,③静态方法,④常量和⑤成员方法),从而获取printName()成员方法的字节码,接着执行printName()成员方法包含的指令。
(2)我们再来看一个简单的例子HelloWorld.java
package com.gaogj; public class HelloWorld { public static void main(String []args){ System.out.println("Hello World"); } }
编译后生成HelloWorld.class文件,然后进行反汇编,javap -c HelloWorld.class > HelloWorld.txt得到如下指令代码 ,打开HelloWorld.txt
Compiled from "HelloWorld.java" public class com.gaogj.HelloWorld { public com.gaogj.HelloWorld(); Code: 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #22 // String Hello World 5: invokevirtual #24 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return }
下面是所有的JVM指令集,可能个别会有出入,可以参看下《Java虚拟机规范 (JavaSE8版)》的第七章操作码助记符。
指令码 助记符 说明 0x00 nop 无操作 0x01 aconst_null 将null推送至栈顶 0x02 iconst_m1 将int型-1推送至栈顶 0x03 iconst_0 将int型0推送至栈顶 0x04 iconst_1 将int型1推送至栈顶 0x05 iconst_2 将int型2推送至栈顶 0x06 iconst_3 将int型3推送至栈顶 0x07 iconst_4 将int型4推送至栈顶 0x08 iconst_5 将int型5推送至栈顶 0x09 lconst_0 将long型0推送至栈顶 0x0a lconst_1 将long型1推送至栈顶 0x0b fconst_0 将float型0推送至栈顶 0x0c fconst_1 将float型1推送至栈顶 0x0d fconst_2 将float型2推送至栈顶 0x0e dconst_0 将double型0推送至栈顶 0x0f dconst_1 将double型1推送至栈顶 0x10 bipush 将单字节的常量值(-128~127)推送至栈顶 0x11 sipush 将一个短整型常量值(-32768~32767)推送至栈顶 0x12 ldc 将int, float或String型常量值从常量池中推送至栈顶 0x13 ldc_w 将int, float或String型常量值从常量池中推送至栈顶(宽索引) 0x14 ldc2_w 将long或double型常量值从常量池中推送至栈顶(宽索引) 0x15 iload 将指定的int型本地变量推送至栈顶 0x16 lload 将指定的long型本地变量推送至栈顶 0x17 fload 将指定的float型本地变量推送至栈顶 0x18 dload 将指定的double型本地变量推送至栈顶 0x19 aload 将指定的引用类型本地变量推送至栈顶 0x1a iload_0 将第一个int型本地变量推送至栈顶 0x1b iload_1 将第二个int型本地变量推送至栈顶 0x1c iload_2 将第三个int型本地变量推送至栈顶 0x1d iload_3 将第四个int型本地变量推送至栈顶 0x1e lload_0 将第一个long型本地变量推送至栈顶 0x1f lload_1 将第二个long型本地变量推送至栈顶 0x20 lload_2 将第三个long型本地变量推送至栈顶 0x21 lload_3 将第四个long型本地变量推送至栈顶 0x22 fload_0 将第一个float型本地变量推送至栈顶 0x23 fload_1 将第二个float型本地变量推送至栈顶 0x24 fload_2 将第三个float型本地变量推送至栈顶 0x25 fload_3 将第四个float型本地变量推送至栈顶 0x26 dload_0 将第一个double型本地变量推送至栈顶 0x27 dload_1 将第二个double型本地变量推送至栈顶 0x28 dload_2 将第三个double型本地变量推送至栈顶 0x29 dload_3 将第四个double型本地变量推送至栈顶 0x2a aload_0 将第一个引用类型本地变量推送至栈顶 0x2b aload_1 将第二个引用类型本地变量推送至栈顶 0x2c aload_2 将第三个引用类型本地变量推送至栈顶 0x2d aload_3 将第四个引用类型本地变量推送至栈顶 0x2e iaload 将int型数组指定索引的值推送至栈顶 0x2f laload 将long型数组指定索引的值推送至栈顶 0x30 faload 将float型数组指定索引的值推送至栈顶 0x31 daload 将double型数组指定索引的值推送至栈顶 0x32 aaload 将引用型数组指定索引的值推送至栈顶 0x33 baload 将boolean或byte型数组指定索引的值推送至栈顶 0x34 caload 将char型数组指定索引的值推送至栈顶 0x35 saload 将short型数组指定索引的值推送至栈顶 0x36 istore 将栈顶int型数值存入指定本地变量 0x37 lstore 将栈顶long型数值存入指定本地变量 0x38 fstore 将栈顶float型数值存入指定本地变量 0x39 dstore 将栈顶double型数值存入指定本地变量 0x3a astore 将栈顶引用型数值存入指定本地变量 0x3b istore_0 将栈顶int型数值存入第一个本地变量 0x3c istore_1 将栈顶int型数值存入第二个本地变量 0x3d istore_2 将栈顶int型数值存入第三个本地变量 0x3e istore_3 将栈顶int型数值存入第四个本地变量 0x3f lstore_0 将栈顶long型数值存入第一个本地变量 0x40 lstore_1 将栈顶long型数值存入第二个本地变量 0x41 lstore_2 将栈顶long型数值存入第三个本地变量 0x42 lstore_3 将栈顶long型数值存入第四个本地变量 0x43 fstore_0 将栈顶float型数值存入第一个本地变量 0x44 fstore_1 将栈顶float型数值存入第二个本地变量 0x45 fstore_2 将栈顶float型数值存入第三个本地变量 0x46 fstore_3 将栈顶float型数值存入第四个本地变量 0x47 dstore_0 将栈顶double型数值存入第一个本地变量 0x48 dstore_1 将栈顶double型数值存入第二个本地变量 0x49 dstore_2 将栈顶double型数值存入第三个本地变量 0x4a dstore_3 将栈顶double型数值存入第四个本地变量 0x4b astore_0 将栈顶引用型数值存入第一个本地变量 0x4c astore_1 将栈顶引用型数值存入第二个本地变量 0x4d astore_2 将栈顶引用型数值存入第三个本地变量 0x4e astore_3 将栈顶引用型数值存入第四个本地变量 0x4f iastore 将栈顶int型数值存入指定数组的指定索引位置 0x50 lastore 将栈顶long型数值存入指定数组的指定索引位置 0x51 fastore 将栈顶float型数值存入指定数组的指定索引位置 0x52 dastore 将栈顶double型数值存入指定数组的指定索引位置 0x53 aastore 将栈顶引用型数值存入指定数组的指定索引位置 0x54 bastore 将栈顶boolean或byte型数值存入指定数组的指定索引位置 0x55 castore 将栈顶char型数值存入指定数组的指定索引位置 0x56 sastore 将栈顶short型数值存入指定数组的指定索引位置 0x57 pop 将栈顶数值弹出 (数值不能是long或double类型的) 0x58 pop2 将栈顶的一个(long或double类型的)或两个数值弹出(其它) 0x59 dup 复制栈顶数值并将复制值压入栈顶 0x5a dup_x1 复制栈顶数值并将两个复制值压入栈顶 0x5b dup_x2 复制栈顶数值并将三个(或两个)复制值压入栈顶 0x5c dup2 复制栈顶一个(long或double类型的)或两个(其它)数值并将复制值压入栈顶 0x5d dup2_x1 复制栈顶的一个或两个值,将其插入栈顶那两个或三个值的下面 0x5e dup2_x2 复制栈顶的一个或两个值,将其插入栈顶那两个、三个或四个值的下面 0x5f swap 将栈最顶端的两个数值互换(数值不能是long或double类型的) 0x60 iadd 将栈顶两int型数值相加并将结果压入栈顶 0x61 ladd 将栈顶两long型数值相加并将结果压入栈顶 0x62 fadd 将栈顶两float型数值相加并将结果压入栈顶 0x63 dadd 将栈顶两double型数值相加并将结果压入栈顶 0x64 isub 将栈顶两int型数值相减并将结果压入栈顶 0x65 lsub 将栈顶两long型数值相减并将结果压入栈顶 0x66 fsub 将栈顶两float型数值相减并将结果压入栈顶 0x67 dsub 将栈顶两double型数值相减并将结果压入栈顶 0x68 imul 将栈顶两int型数值相乘并将结果压入栈顶 0x69 lmul 将栈顶两long型数值相乘并将结果压入栈顶 0x6a fmul 将栈顶两float型数值相乘并将结果压入栈顶 0x6b dmul 将栈顶两double型数值相乘并将结果压入栈顶 0x6c idiv 将栈顶两int型数值相除并将结果压入栈顶 0x6d ldiv 将栈顶两long型数值相除并将结果压入栈顶 0x6e fdiv 将栈顶两float型数值相除并将结果压入栈顶 0x6f ddiv 将栈顶两double型数值相除并将结果压入栈顶 0x70 irem 将栈顶两int型数值作取模运算并将结果压入栈顶 0x71 lrem 将栈顶两long型数值作取模运算并将结果压入栈顶 0x72 frem 将栈顶两float型数值作取模运算并将结果压入栈顶 0x73 drem 将栈顶两double型数值作取模运算并将结果压入栈顶 0x74 ineg 将栈顶int型数值取负并将结果压入栈顶 0x75 lneg 将栈顶long型数值取负并将结果压入栈顶 0x76 fneg 将栈顶float型数值取负并将结果压入栈顶 0x77 dneg 将栈顶double型数值取负并将结果压入栈顶 0x78 ishl 将int型数值左移位指定位数并将结果压入栈顶 0x79 lshl 将long型数值左移位指定位数并将结果压入栈顶 0x7a ishr 将int型数值右(符号)移位指定位数并将结果压入栈顶 0x7b lshr 将long型数值右(符号)移位指定位数并将结果压入栈顶 0x7c iushr 将int型数值右(无符号)移位指定位数并将结果压入栈顶 0x7d lushr 将long型数值右(无符号)移位指定位数并将结果压入栈顶 0x7e iand 将栈顶两int型数值作“按位与”并将结果压入栈顶 0x7f land 将栈顶两long型数值作“按位与”并将结果压入栈顶 0x80 ior 将栈顶两int型数值作“按位或”并将结果压入栈顶 0x81 lor 将栈顶两long型数值作“按位或”并将结果压入栈顶 0x82 ixor 将栈顶两int型数值作“按位异或”并将结果压入栈顶 0x83 lxor 将栈顶两long型数值作“按位异或”并将结果压入栈顶 0x84 iinc 将指定int型变量增加指定值(i++, i--, i+=2) 0x85 i2l 将栈顶int型数值强制转换成long型数值并将结果压入栈顶 0x86 i2f 将栈顶int型数值强制转换成float型数值并将结果压入栈顶 0x87 i2d 将栈顶int型数值强制转换成double型数值并将结果压入栈顶 0x88 l2i 将栈顶long型数值强制转换成int型数值并将结果压入栈顶 0x89 l2f 将栈顶long型数值强制转换成float型数值并将结果压入栈顶 0x8a l2d 将栈顶long型数值强制转换成double型数值并将结果压入栈顶 0x8b f2i 将栈顶float型数值强制转换成int型数值并将结果压入栈顶 0x8c f2l 将栈顶float型数值强制转换成long型数值并将结果压入栈顶 0x8d f2d 将栈顶float型数值强制转换成double型数值并将结果压入栈顶 0x8e d2i 将栈顶double型数值强制转换成int型数值并将结果压入栈顶 0x8f d2l 将栈顶double型数值强制转换成long型数值并将结果压入栈顶 0x90 d2f 将栈顶double型数值强制转换成float型数值并将结果压入栈顶 0x91 i2b 将栈顶int型数值强制转换成byte型数值并将结果压入栈顶 0x92 i2c 将栈顶int型数值强制转换成char型数值并将结果压入栈顶 0x93 i2s 将栈顶int型数值强制转换成short型数值并将结果压入栈顶 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压入栈顶 0x99 ifeq 当栈顶int型数值等于0时跳转 0x9a ifne 当栈顶int型数值不等于0时跳转 0x9b iflt 当栈顶int型数值小于0时跳转 0x9c ifge 当栈顶int型数值大于等于0时跳转 0x9d ifgt 当栈顶int型数值大于0时跳转 0x9e ifle 当栈顶int型数值小于等于0时跳转 0x9f if_icmpeq 比较栈顶两int型数值大小,当结果等于0时跳转 0xa0 if_icmpne 比较栈顶两int型数值大小,当结果不等于0时跳转 0xa1 if_icmplt 比较栈顶两int型数值大小,当结果小于0时跳转 0xa2 if_icmpge 比较栈顶两int型数值大小,当结果大于等于0时跳转 0xa3 if_icmpgt 比较栈顶两int型数值大小,当结果大于0时跳转 0xa4 if_icmple 比较栈顶两int型数值大小,当结果小于等于0时跳转 0xa5 if_acmpeq 比较栈顶两引用型数值,当结果相等时跳转 0xa6 if_acmpne 比较栈顶两引用型数值,当结果不相等时跳转 0xa7 goto 无条件跳转 0xa8 jsr 跳转至指定16位offset位置,并将jsr下一条指令地址压入栈顶 0xa9 ret 返回至本地变量指定的index的指令位置(一般与jsr, jsr_w联合使用) 0xaa tableswitch 用于switch条件跳转,case值连续(可变长度指令) 0xab lookupswitch 用于switch条件跳转,case值不连续(可变长度指令) 0xac ireturn 从当前方法返回int 0xad lreturn 从当前方法返回long 0xae freturn 从当前方法返回float 0xaf dreturn 从当前方法返回double 0xb0 areturn 从当前方法返回对象引用 0xb1 return 从当前方法返回void 0xb2 getstatic 获取指定类的静态域,并将其值压入栈顶 0xb3 putstatic 为指定的类的静态域赋值 0xb4 getfield 获取指定类的实例域,并将其值压入栈顶 0xb5 putfield 为指定的类的实例域赋值 0xb6 invokevirtual 调用实例方法 0xb7 invokespecial 调用超类构造方法,实例初始化方法,私有方法 0xb8 invokestatic 调用静态方法 0xb9 invokeinterface 调用接口方法 0xba invokedynamic 调用动态链接方法 0xbb new 创建一个对象,并将其引用值压入栈顶 0xbc newarray 创建一个指定原始类型(如int, float, char…)的数组,并将其引用值压入栈顶 0xbd anewarray 创建一个引用型(如类,接口,数组)的数组,并将其引用值压入栈顶 0xbe arraylength 获得数组的长度值并压入栈顶 0xbf athrow 将栈顶的异常抛出 0xc0 checkcast 检验类型转换,检验未通过将抛出ClassCastException 0xc1 instanceof 检验对象是否是指定的类的实例,如果是将1压入栈顶,否则将0压入栈顶 0xc2 monitorenter 获得对象的锁,用于同步方法或同步块 0xc3 monitorexit 释放对象的锁,用于同步方法或同步块 0xc4 wide 扩大本地变量索引的宽度 0xc5 multianewarray 创建指定类型和指定维度的多维数组(执行该指令时,操作栈中必须包含各维度的长度值),并将其引用值压入栈顶 0xc6 ifnull 为null时跳转 0xc7 ifnonnull 不为null时跳转 0xc8 goto_w 无条件跳转 0xc9 jsr_w 跳转至指定32位offset位置,并将jsr_w下一条指令地址压入栈顶 ============================================ 0xca breakpoint 调试时的断点标记 0xfe impdep1 为特定软件而预留的语言后门 0xff impdep2 为特定硬件而预留的语言后门 最后三个为保留指令
这篇关于java虚拟机的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南