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虚拟机体系结构

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虚拟机的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程