当编写并运行一个Java程序时,就同时体验了这四种技术。用Java语言编写源代码,编译成Java Class文件,然后再在Java虚拟机上运行class文件。当编写程序时,通过调用类中的方法来访问系统资源。当程序运行的时候,它通过调用class文件中的方法来满足程序的Java API调用。
jvm全称是Java Virtual Machine(java虚拟机)。它之所以被称之为是“虚拟”的,就是因为它仅仅是由一个规范来定义的抽象计算机。我们平时经常使用的Sun HotSpot虚拟机只是其中一个具体的实现(另外还有BEA JRockit、IBM J9等等虚拟机)。在实际的计算机上通过软件来实现一个虚拟计算机。与VMWare等类似软件不同,你是看不到jvm的,它存在于内存。
当启动一个Java程序时,一个虚拟机实例也就诞生了。当该程序关闭退出,这个虚拟机实例也就随之消亡。如果在同一台计算机上同时运行三个Java程序,将得到三个Java虚拟机实例。每个Java程序都运行于它自己的Java虚拟机实例中。
一、jvm体系结构
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。根据《Java虚拟机规范(第2版)》的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域,如下图1所示。
图1 Java虚拟机的内部体系结构
下面先对图中各部分做个简单的说明:
1.class文件:虚拟机并不关心Class的来源是什么语言,只要它符合Java class文件格式就可以在Java虚拟机中运行。使用Java编译器可以把Java代码编译为存储字节码的Class文件,使用JRuby等其他语言的 编译器一样可以把程序代码编译成Class文件。
2.类装载器子系统:负责查找并装载Class 文件到内存,最终形成可以被虚拟机直接使用的Java类型。
3.方法区:在类装载器加载class文件到内存的过程中,虚拟机会提取其中的类型信息,并将这 些信息存储到方法区。方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。由于所有线程都共享方法区,因此它们对方法 区数据的访问必须被设计为是线程安全的。
4.堆:存储Java程序创建的类实例。所有线程共享,因此设计程序时也要考虑到多线程访问对象(堆数据)的同步问题。
5.Java栈:Java栈是线程私有的。每当启动一个新线程时,Java虚拟机都会为它分配一 个Java栈。Java栈以帧为单位保存线程的运行状态。虚拟机只会直接对Java栈执行两种操作:以帧为单位的压栈或出栈。当线程调用java方法时, 虚拟机压入一个新的栈帧到该线程的java栈中。当方法返回时,这个栈帧被从java栈中弹出并抛弃。一个栈帧包含一个java方法的调用状态,它存储有 局部变量表、操作栈、动态链接、方法出口等信息。
6.程序计数器:一个运行中的Java程序,每当启动一个新线程时,都会为这个新线程创建一个 自己的PC(程序计数器)寄存器。程序计数器的作用可以看做是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取 下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。如果线程正在执行的是一个Java方法,这个 计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie方法,这个计数器值则为空(Undefined)。
7.本地方法栈:本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。任何本地方法接口都会使用某种本地方法栈。当线程调用Java方法时,虚拟机会创建一个新的栈帧并压入Java栈。然而当它调用的是本地方法时,虚拟机会保持Java栈不变,不再在线程的Java栈中压入新的帧,虚拟机只是简单地动态链接并直接调用指定的本地方法。如果某个虚拟机实现的本地方法接口是使用C连接模型的话,那么它的本地方法栈就是C栈。
8.执行引擎: 负责执行字节码。方法的字节码是由Java虚拟机的指令序列构成的。每一条指令包含一个单字节的操作码,后面跟随0个或多个操作数。执行引擎执行字节码 时,首先取得一个操作码,如果操作码有操作数,取得它的操作数。它执行操作码和跟随的操作数规定的动作,然后再取得下一个操作码。这个执行字节码的过程在 线程完成前将一直持续。
下面详细说明下具体构成:
Java语言中所有的基本类型同样也都是Java虚拟机中的基本类型。但boolean有点特别,指令集对boolean只有很有限的支持。当编译器把Java源码编译为字节码的时,它会用int或byte来表示boolean。Boolean数组是当byte数组来访问的。
returnAddress是Java虚拟机内部使用的基本类型,这个类型被用来实现Java程序中的finally子句。
2、类装载器子系统
负责查找并装载的那部分被称为类装载器子系统。
分为启动类装载器和用户自定义类装载器
由不同的类装载器装载的类将放在虚拟机内部的不同命名空间中。
用户自定义的类装载器以及Class类的实例都放在内存的堆区,而装载的类型信息则都位于方法区。
装载顺序:
1)装载——查找并装载类型的二进制数据
2)连接——执行验证(确保被导入类型的正确性),准备(为类变量分配内存,并将其初始化为默认值),以及解析(把类变量中的符号引用转换为正确的初始值)
3)初始化——把类变量初始化为正确的初始值
3方法区
在java虚拟机中,关于被装载类型的信息存储在一个逻辑上被称为方法区的内存中。
所有线程都共享方法区。
类型信息:
这个类型的全限定名
这个类型的直接超类的全限定名
这个类型是类类型还是接口类型
这个类型的访问修饰符
任何直接超接口的全限定名的有序列表
该类型的常量池
字段信息
方法信息
除了常量以外的所有类(静态)变量
一个到类ClassLoader的引用
一个到Class类的引用
其中字段信息包括
字段名
字段类型
字段的修饰符
方法信息包括
方法名
方法的返回类型
方法参数的数量和类型
方法的修饰符
如果方法不是抽象的和本地的还须有
方法的字节码
操作数栈和该方法的栈帧中的局部变量的大小
异常表
4 堆
Java程序在运行时所创建的所有类实例或数组都放在同一个堆中。
Java对象中包含的基本数据由它所属的类及其所有超类声明的实例变量组成。只要有一个对象引用,虚拟机就必须能快速的定位对象实例的数据,另外,它必须能通过该对象引用访问相应的类数据,因此对象中通常有一个指向方法区的指针。
一种可能的堆空间设计就是,把堆分为两部分:一个句柄池,一个对象池。
这种设计的好处是有利于堆碎片的整理,缺点是每次访问对象的实例变量都需要经过两次指针传递。
堆中其他数据:
1、对象锁,用于协调多个线程访问一个对象时的同步。
2、等待集合
3、与垃圾收集器有关的数据。
4、方法表:加快了调用实例方法时的效率。
方法表指向的实例方法数据包括以下信息:
此方法的操作数栈和局部变量区的大小
此方法的字节码
异常表
这些信息足够虚拟机去调用一个方法了,方法表包含有方法指针——指向类活或超类声明的方法的数据
对于一个运行中的Java程序而言,其中的每一个线程都有它自己的PC(程序计数器),在线程启动时创建。大小是一个字长。当线程执行某个Java方法时,PC的内容总是下一条将被指向指令的“地址”。如果该线程正在执行一个本地方法,那么此时PC的值为”undefined”。
6、Java栈
每当启动一个线程时,Java虚拟机都会为它分配一个Java栈,Java栈以帧为单位保存线程的运行状态,虚拟机只会直接对Java栈执行两种操作:以帧为单位的压栈和出栈。
某个线程正在执行的方法被称为该线程的当前方法,当前方法使用的栈帧称为当前帧,当前方法所属的类称为当前类,当前类的常量池称为当前常量池,在线程执行一个方法时,它会跟踪当前类和当前常量池。
每当线程调用一个方法时,虚拟机都会在该线程的Java栈中压入一个新帧,而这个新栈自然就成为当前帧。在执行这个方法时,它使用这个帧来存储参数、局部变量、中间运算结果等等数据。
Java栈上的所有数据都是数据都是此线程私有的。
7、栈帧
栈帧由三部分组成:局部变量区、操作数栈和帧数据区。局部变量区和操作数栈的大小要视对应的方法而定,编译器在编译器时就确定的确定了这些值并放在class文件中。帧数据区的大小依赖于具体的实现。
当虚拟机调用一个方法时,它从对应类的类型信息中得到此方法的局部变量区和操作数栈的大小,并据此分配栈帧内存,然后压入Java栈中。
局部变量区:Java栈帧的局部变量区被组织为以一个字长为单位、从0开始计数的数组。字节码指令通过从0开始的索引来使用其中的数据。
局部变量区对应方法的参数和局部变量。编译器首先按声明的顺序把这些参数放入局部变量数组。
在java中,所有的对象都按引用传递,并且都存储在堆中,永远都不会在局部变量区或操作数栈中发现对象的拷贝,只会有对象的引用。
操作数栈:操作数栈也是被组织为一个字长为单位的数组。但它不是通过索引来访问,而是通过标准的栈操作——压栈和出栈来访问的。
帧数据区:支持解析常量池解析、正常方法返回以及异常派发机制。每当虚拟机要执行某个需要用到常量池数据的指令时,它都会通过帧数据区中指向常量池的指针来访问它。常量池中对类型、字段和方法的引用在开始时都是符号。当虚拟机在常量池中搜索时,如果遇到类、接口、字段或者方法的入口,假若它们仍然是符号,虚拟机那时候才会进行解析。
8、执行引擎
指令集:方法的字节码流是由Java虚拟机的指令序列构成的。每一条指令包含一个单字节的操作码,后面跟随0个或多个操作数。操作码本身就已经规定了它是否需要跟随操作数,以及如果有操作数它是什么形式的。当虚拟机执行一条指令的时候,可能使用当前常量池中的项、当前帧的局部变量中的值,或者当前帧操作数栈顶端的值。
执行技术:解释、即时编译、字适应优化、芯片级直接执行。
Hotspot虚拟机就采用了自适应优化。自适应优化虚拟机开始的时候对所有的代码都是解释执行,但是它会监视代码的执行情况。大多数程序花费80%-90%的时间来执行10%-20%的代码。虚拟机可以意识到那些方法是程序的热区——就是那10%-20%的代码,他们占整个执行时间的80%-90%。当自适应优化的虚拟机判断出某个特定的方法是瓶颈的时候,它启动一个后台线程,把字节码编译成本地代码,非常仔细的优化这些本地代码。
Java的安全模型是其多个重要结构特点之一,它使Java成为适于网络环境的技术。因为网络提供了一条攻击连人的计算机的潜在途径,因此安全性是非常重要的。Java安全模型侧重于保护终端用户免受从网络下载的、来自不可靠来源的、恶意程序(以及善意程序中的bug)的侵犯。为了达到这个目的,Java提供了一个用户可配置的“沙箱”,在沙箱中可以放置不可靠的Java程序。
例如:原来在版本1.0中的沙箱对很多不可靠Java applet的活动做了限制,包括:
对本地硬盘的读写操作。
进行任何网络连接,但不能连接到提供这个applect的源主机。
创建新的进程。
装载新的动态连接库。
Java虚拟机装载了一个类,并且对它进行了每一到第三趟的class文件检查,这些字节码就可以被运行了。除了对符号引用的检验,Java虚拟机在执行字节码时还进行了其他一些内置的安全机制的操作:
Class.forName(String className) | 装载并返回该类型的Class对象引用 | 如果不能装载类型,则抛出ClassNotFoundException |
obj.getClass() | 直接通过实例得到类型的Class对象引用 |
getName() | 返回类型的全限定名 | |
getSuperClass() | 得到直接超类 | 如果是Object类型或是一个接口,则返回null |
isInterface() | 判断是否是接口 | 如果是返回true,否则返回false |
getInterfaces() | 得到所有直接接口的数组 | 如果没有实现任何接口,返回长度为0的数组 |
getClassLoader() | 得到装载该类型的类装载器 | 如果类型是由启动类装载器装载的,则返回null |
一、class文件内容
Java class文件是对Java程序二进制文件格式的精确定义。每一个Java class文件都对一个Java类或者Java接口作出了全面描述。一个class文件只 能包含一个类或接口;
ACC_PUBLIC | 0x0001 | public类型 |
ACC_FINAL | 0x0010 | 类为final类型 |
ACC_SUPER | 0x0020 | 使用新型的invokespecial语义 |
ACC_INTERFACE | 0x0200 | 接口类型,不是类类型 |
ACC_ABSTRACT | 0x0400 | abstract类型 |
附:invokespecial和invokevirtual
invokespecial指静态绑定后,由JVM产生调用的方法。如super(),以及super.someMethod(),都属于 invokespecial;
invokevirtual指动态绑定后,由JVM产生调用的方法。如obj.someMethod(),属于invokevirtual;
正是由于这两种绑定的不同,在子类覆盖超类的方法、并向上转型引用后,才产生了多态以及其他特殊的调用结果。
常量池指向类或者接口时,给出的是全限定名,形如java/util/Hashtable;
字段名和方法名以简单名称形式出现在常量池入口中,如上例中的常量池索引3中的"hello";
B | byte |
C | char |
D | double |
F | float |
I | int |
J | long |
S | short |
Z | boolean |
I | int i; |
[Ljava/lang/Object; | java.lang.Object[] obj; |
([BII)Ljava/lang/String; | String method(byte[] b, int i, int j) |
ZILjava/lang/String;II()Z | boolean method(boolean b, int i, String s, int j, int k) |
Young Generation | 即图中的Eden + From Space + To Space 1.Eden存放新生的对象 2.Survivor Space有两个,存放每次垃圾回收后存活的对象 |
Old Generation | Tenured Generation 即图中的Old Space 主要存放应用程序中生命周期长的存活对象 |
Permanent Generation | 即图中的Permanent Space 存放JVM自己的反射对象,比如类对象和方法对象 |
native heap | JVM内部处理或优化 |
-Xms/-Xmx | 定义YOUNG+OLD段的总尺寸,ms为JVM启动时YOUNG+OLD的内存大小;mx为最大可占用的YOUNG+OLD内存大小。 | 默认是物理内存的1/64但小于1G。 | 在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销。 |
-XX:NewSize/-XX:MaxNewSize | 定义YOUNG段的尺寸,NewSize为JVM启动时YOUNG的内存大小;MaxNewSize为最大可占用的YOUNG内存大小。 | 默认是物理内存的1/4但小于1G。 | 在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销。 |
-Xmn | 设置young generation的内存大小。 | 整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。 | |
-XX:PermSize/-XX:MaxPermSize | 定义Perm段的尺寸,PermSize为JVM启动时Perm的内存大小;MaxPermSize为最大可占用的Perm内存大小。 | 在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销。 | |
-XX:NewRaito | 设置YOUNG与OLD段的比值。 | ||
-XX:SurvivorRaito | 设置YOUNG段中Eden区与Survivor区的比值,如此值为4,则Eden为4/6,两个Survivor分别为1/6。 | ||
-XX:MaxTenuringThreshold | 设置垃圾最大年龄。 | 如果设置为0的话,则新生对象不经过Survivor区,直接进入OLD段。对于OLD对象比较多的应用,可以提高效率。如果将此值设置为一个较大值,则 新生对象会在Survivor区进行多次复制,这样可以增加对象的存活时间,增加在minor collection即被回收的概率。 | |
-Xss | 设置栈的大小。 | JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。 | 在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。 |
-XX:+UseParallelGC | 选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。 | ||
-XX:ParallelGCThreads | 配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。 | 此值最好配置与处理器数目相等。 | |
-XX:+UseParallelOldGC | 配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。 | ||
-XX:MaxGCPauseMillis | 设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。 | ||
-XX:+UseAdaptiveSizePolicy | 设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等。 | 此值建议使用并行收集器时,一直打开。 |
MEM_ARGS="-Xms512m -Xmx512m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:PermSize=128m -XX:MaxPermSize=128m -XX:SurvivorRatio=6"
上例中,
YOUNG+OLD: 512M
YOUNG: 256M
Perm: 128M
Eden: YOUNG*6/(6+1+1)=192M
单个Survivor: YOUNG/(6+1+1)=32M
Java栈和局部变量操作
Java虚拟机是基于栈的机器,几乎所有Java虚拟机的指令都与操作数栈相关。栈操作包括把常量压入操作数栈、执行通用的栈操作、在操作数栈和局部变量之间往返传输值。
1常量入栈操作:
操作码在执行常量入栈操作之前,使用三种方式指明常量的值:常量值隐含包含在操作码内部、常量值在字节码中如同操作数一样跟随在操作码之后,或者从常量池中取出常量。
1.1常量值隐含包含在操作码内部:
将一个字长的常量压入栈
操作码 |
操作数 |
说明 |
---|---|---|
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压入栈 |
fconst_0 |
(无) |
将float类型值0压入栈 |
fconst_1 |
(无) |
将float类型值1压入栈 |
fconst_2 |
(无) |
将float类型值2压入栈 |
将两个字长的常量压入栈
操作码 |
操作数 |
说明 |
---|---|---|
lconst_0 |
(无) |
将long类型值0压入栈 |
lconst_1 |
(无) |
将long类型值1压入栈 |
dconst_0 |
(无) |
将double类型值0压入栈 |
dconst_1 |
(无) |
将double类型值1压入栈 |
给一个对象引用赋空值时会用到aconst_null指令
将空(null)对象引用压入栈
操作码 |
操作数 |
说明 |
---|---|---|
aconst_null |
(无) |
将空(null)对象引用压入栈 |
例如下面代码:
public class StackTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
int i = 0;
int j = 4;
int k;
k = i + j;
float a = 0;
float b = 1;
float c = a + b;
long x = 0;
long y = 1;
long z = x + y;
String string = null;
}
}
用javap工具查看其字节码为:
Compiled from "StackTest.java"
public class StackTest extends java.lang.Object{
public StackTest();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_0 //常量int类型的0入栈
1: istore_1 //弹出栈顶元素0存入位置1的局部变量中
2: iconst_4 //常量int类型的4入栈
3: istore_2 //弹出栈顶元素4存入位置2的局部变量中
4: iload_1 //从位置为1的局部变量中取出元素int类型的0压入栈
5: iload_2 //从位置为2的局部变量中取出元素int类型的4压入栈
6: iadd //从栈顶弹出两个元素然后做加法,把结果压入栈
7: istore_3 //弹出栈顶元素4存入位置为3的局部变量中
8: fconst_0 //常量float类型的0入栈
9: fstore 4 //弹出栈顶元素0存入位置为4的局部变量中
11: fconst_1 //常量float类型的1入栈
12: fstore 5 //弹出栈顶元素1存入位置为5的局部变量中
14: fload 4 //从位置为4的局部变量中取出元素float类型的0压入栈
16: fload 5 //从位置为5的局部变量中取出元素float类型的1压入栈
18: fadd //从栈顶弹出两个元素然后做加法,把结果压入栈
19: fstore 6 //弹出栈顶元素1存入位置为3的局部变量中
21: lconst_0 //常量long类型的0入栈
22: lstore 7 // 弹出栈顶元素0存入位置为7和8的局部变量中
24: lconst_1 //常量long类型的1入栈
25: lstore 9 // 弹出栈顶元素0存入位置为9和10的局部变量中
27: lload 7 //从位置为7和8的局部变量中取出元素long类型的0压入栈
29: lload 9 //从位置为9和10的局部变量中取出元素long类型的1压入栈
31: ladd //从栈顶弹出两个元素然后做加法,把结果压入栈
32: lstore 11 //弹出栈顶元素1存入位置为11和12的局部变量中
34: aconst_null //将null对象引用压入栈
35: astore 13 //弹出栈顶元素null存入位置为13的局部变量中
37: return
}
1.2常量值在字节码中跟随在操作码之后:
将byte和short类型常量压入栈
操作码 |
操作数 |
说明 |
---|---|---|
bipush |
一个byte类型的数 |
将byte类型的数转换为int类型的数,然后压入栈 |
sipush |
一个short类型的数 |
将short类型的数转换为int类型的数,然后压入栈 |
1.3从常量池中取出常量
操作码 |
操作数 |
说明 |
---|---|---|
ldc |
无符号8位数indexbyte |
从由indexbyte指向的常量池入口中取出一个字长的值,然后将其压入栈 |
ldc_w |
无符号16位数indexshort |
从由indexshort指向的常量池入口中取出一个字长的值,然后将其压入栈 |
ldc2_w |
无符号16位数indexshort |
从由indexshort指向的常量池入口中取出两个字长的值,然后将其压入栈 |
这三个操作码是从常量池中取出常量,然后将其压入栈,这些操作码的操作码表示常量池索引,Java虚拟机通过给定的索引查找相应的常量池入口,决定这些常量的类型和值,并把它们压入栈。
常量池索引是一个无符号值,ldc和ldc_w是把一个字长的项压入栈,区别在于:ldc的索引只有一个8位,只能指向常量池中1~255范围的位置。ldc_w的索引有16位,可以指向1~65535范围的位置。
例如下面代码:
public class StackTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
byte i = 125;
byte j = -128;
int k = i + j;
short a = 32767;
short b = - 32768;
int c = a + b;
int x = 2147483647;
int y = -2147483648;
int z = x + y;
long I = 2147483648L;
long J = -2147483649L;
long K = I + J;
}
}
用javap工具查看其字节码为:
Compiled from "StackTest.java"
public class StackTest extends java.lang.Object{
public StackTest();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: bipush 125 //将byte类型的255转换成int类型压入栈
2: istore_1 //弹出栈顶元素255存入位置为1的局部变量中
3: bipush -128 //将byte类型的-128转换成int类型压入栈
5: istore_2 //弹出栈顶元素-128存入位置为2的局部变量中
6: iload_1 //取出位置为1的局部变量中的数压入栈
7: iload_2 //取出位置为2的局部变量中的数压入栈
8: iadd //从栈顶弹出两个元素然后做加法,把结果压入栈
9: istore_3 //弹出栈顶元素存入位置为3的局部变量中
10: sipush 32767 //将short类型的32767转换成int类型压入栈
13: istore 4 //弹出栈顶元素32767存入位置为4的局部变量中
15: sipush -32768 /将short类型的-32768转换成int类型压入栈
18: istore 5 //弹出栈顶元素-32768存入位置为5的局部变量中
20: iload 4 //取出位置为4的局部变量中的数压入栈
22: iload 5 //取出位置为5的局部变量中的数压入栈
24: iadd //从栈顶弹出两个元素然后做加法,把结果压入栈
25: istore 6 /弹出栈顶元素存入位置为6的局部变量中
27: ldc #16; //int 2147483647 //从常量池索引16的位置取出2147483647压入栈
29: istore 7 //弹出栈顶元素2147483647存入位置为4的局部变量中
31: ldc #17; //int -2147483648 //从常量池索引17的位置取出-2147483648压入栈
33: istore 8 //弹出栈顶元素-2147483648存入位置为8的局部变量中
35: iload 7 //取出位置为7的局部变量中的数压入栈
37: iload 8 //取出位置为8的局部变量中的数压入栈
39: iadd //从栈顶弹出两个元素然后做加法,把结果压入栈
40: istore 9 //弹出栈顶元素存入位置为9的局部变量中
42: ldc2_w #18; //long 2147483648l //从常量池索引18的位置取出long类型的2147483648L压入栈
45: lstore 10 //弹出栈顶元素2147483648L存入位置为10和11的局部变量中
47: ldc2_w #20; //long -2147483649l //从常量池索引20的位置取出long类型的-2147483649L压入栈
50: lstore 12 //弹出栈顶元素-2147483649L存入位置为12和13的局部变量中
52: lload 10 //取出位置为10和11的局部变量中的数压入栈
54: lload 12 //取出位置为12和13的局部变量中的数压入栈
56: ladd //从栈顶弹出两个元素然后做加法,把结果压入栈
57: lstore 14 //弹出栈顶元素存入位置为14和15的局部变量中
59: return
}
2通用栈操作
操作码 |
操作数 |
说明 |
---|---|---|
nop |
(无) |
不做任何操作 |
pop |
(无) |
从操作数栈弹出栈顶部的一个字 |
pop2 |
(无) |
从操作数栈弹出最顶端的两个字 |
swap |
(无) |
交换栈顶部的两个字 |
dup |
(无) |
复制栈顶部的一个字 |
dup2 |
(无) |
复制栈顶部的两个字 |
dup_x1 |
(无) |
复制栈顶部的一个字,并将复制内容及原来弹出的两个字长的内容压入栈 |
dup_x2 |
(无) |
复制栈顶部的一个字,并将复制内容及原来弹出的三个字长的内容压入栈 |
dup2_x1 |
(无) |
复制栈顶部的两个字,并将复制内容及原来弹出的三个字长的内容压入栈 |
dup2_x2 |
(无) |
复制栈顶部的两个字,并将复制内容及原来弹出的四个字长的内容压入栈 |
1,dup:复制栈顶部的一个字长的内容。
栈:
前:......,word
后:......,word,word
2,dup_x1:复制栈顶部一个字长的内容,然后将复制内容及原来弹出的两个字长的内容压入栈
栈:
前:......,word2,word1
后:......,word1,word2,word1
3,dup_x2:复制栈顶部一个字长的内容,然后将复制内容及原来弹出的三个字长的内容压入栈
栈:
前:.......,word3,word2,word1
后:.......,word1,word3,word2,word1
4,dup2:复制栈顶部长度为两个字长的内容
栈:
前:......,word2,word1
后:......,word2,word1,word2,word1
5,dup2_x1:复制栈顶部两个字长的内容,然后将复制内容及原来弹出的三个字长的内容压入栈
栈:
前:......,word3,word2,word1
后:.......,word2,word1,word3,word2,word1
6,dup2_x2:复制栈顶部两个字长的内容,然后将复制内容及原来弹出的四个字长的内容压入栈
栈:
前:......,word4,word3,word2,word1
后:.......,word2,word1,word4,word3,word2,word1
7,pop:弹出栈顶端一个字长的内容
栈:
前:......,word
后:.......
8,pop2:弹出栈顶端两个字长的内容
栈:
前:......,word2,word1
后:.......
9,swap:交换栈顶端两个字的内容
栈:
前:......,word2,word1
后:.......,word1,word2
例如如下代码:
public class StackTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String a;
String b;
a = new String("aaa");
b = new String("aaa");
}
}
用javap工具查看其字节码为:
Compiled from "StackTest.java"
public class StackTest extends java.lang.Object{
public StackTest();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #16; //class java/lang/String
3: dup
4: ldc #18; //String aaa
6: invokespecial #20; //Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1
10: new #16; //class java/lang/String
13: dup
14: ldc #18; //String aaa
16: invokespecial #20; //Method java/lang/String."<init>":(Ljava/lang/String;)V
19: astore_2
20: return
}
3,把局部变量压入栈
将一个字长的局部变量压入栈
操作码 |
操作数 |
说明 |
---|---|---|
iload |
vindex |
将位置为vindex的int类型的局部变量压入栈 |
iload_0 |
(无) |
将位置为0的int类型的局部变量压入栈 |
iload_1 |
(无) |
将位置为1的int类型的局部变量压入栈 |
iload_2 |
(无) |
将位置为2的int类型的局部变量压入栈 |
iload_3 |
(无) |
将位置为3的int类型的局部变量压入栈 |
fload |
vindex |
将位置为vindex的float类型的局部变量压入栈 |
fload_0 |
(无) |
将位置为0的float类型的局部变量压入栈 |
fload_1 |
(无) |
将位置为1的float类型的局部变量压入栈 |
fload_2 |
(无) |
将位置为2的float类型的局部变量压入栈 |
fload_3 |
(无) |
将位置为3的float类型的局部变量压入栈 |
将两个字长的局部变量压入栈
操作码 |
操作数 |
说明 |
---|---|---|
lload |
vindex |
将位置为vindex和(vindex+1)的long类型的局部变量压入栈 |
lload_0 |
(无) |
将位置为0和1的long类型的局部变量压入栈 |
lload_1 |
(无) |
将位置为1和2的long类型的局部变量压入栈 |
lload_2 |
(无) |
将位置为2和3的long类型的局部变量压入栈 |
lload_3 |
(无) |
将位置为3和4的long类型的局部变量压入栈 |
dload |
vindex |
将位置为vindex和(vindex+1)的double类型的局部变量压入栈 |
dload_0 |
(无) |
将位置为0和1的double类型的局部变量压入栈 |
dload_1 |
(无) |
将位置为1和2的double类型的局部变量压入栈 |
dload_2 |
(无) |
将位置为2和3double类型的局部变量压入栈 |
dload_3 |
(无) |
将位置为3和4double类型的局部变量压入栈 |
将对象引用局部变量压入栈
操作码 |
操作数 |
说明 |
---|---|---|
aload |
vindex |
将位置为vindex的对象引用局部变量压入栈 |
aload_0 |
(无) |
将位置为0的对象引用局部变量压入栈 |
aload_1 |
(无) |
将位置为1的对象引用局部变量压入栈 |
aload_2 |
(无) |
将位置为2的对象引用局部变量压入栈 |
aload_3 |
(无) |
将位置为3的对象引用局部变量压入栈 |
4,弹出栈顶元素,将其赋给局部变量
弹出一个字长的值,将其赋给局部变量
操作码 |
操作数 |
说明 |
---|---|---|
istore |
vindex |
从栈中弹出int类型值,然后将其存到位置为vindex的局部变量中 |
istore_0 |
(无) |
从栈中弹出int类型值,然后将其存到位置为0的局部变量中 |
istore_1 |
(无) |
从栈中弹出int类型值,然后将其存到位置为1的局部变量中 |
istore_2 |
(无) |
从栈中弹出int类型值,然后将其存到位置为2的局部变量中 |
istore_3 |
(无) |
从栈中弹出int类型值,然后将其存到位置为3的局部变量中 |
fstore |
vindex |
从栈中弹出float类型值,然后将其存到位置为vindex的局部变量中 |
fstore_0 |
(无) |
从栈中弹出float类型值,然后将其存到位置为0的局部变量中 |
fstore_1 |
(无) |
从栈中弹出float类型值,然后将其存到位置为1的局部变量中 |
fstore_2 |
(无) |
从栈中弹出float类型值,然后将其存到位置为2的局部变量中 |
fstore_3 |
(无) |
从栈中弹出float类型值,然后将其存到位置为3的局部变量中 |
弹出对象引用,并将其赋值给局部变量
操作码 |
操作数 |
说明 |
---|---|---|
lstore |
vindex |
从栈中弹出long类型值,然后将其存到位置为vindex和(vindex+1)的局部变量中 |
lstore_0 |
(无) |
从栈中弹出long类型值,然后将其存到位置为0和1的局部变量中 |
lstore_1 |
(无) |
从栈中弹出long类型值,然后将其存到位置为1和2的局部变量中 |
lstore_2 |
(无) |
从栈中弹出long类型值,然后将其存到位置为2和3的局部变量中 |
lstore_3 |
(无) |
从栈中弹出long类型值,然后将其存到位置为3和4的局部变量中 |
dstore |
vindex |
从栈中弹出double类型值,然后将其存到位置为vindex和(vindex+1)的局部变量中 |
dstore_0 |
(无) |
从栈中弹出double类型值,然后将其存到位置为0和1的局部变量中 |
dstore_1 |
(无) |
从栈中弹出double类型值,然后将其存到位置为1和2的局部变量中 |
dstore_2 |
(无) |
从栈中弹出double类型值,然后将其存到位置为2和3的局部变量中 |
dstore_3 |
(无) |
从栈中弹出double类型值,然后将其存到位置为3和4的局部变量中 |
操作码 |
操作数 |
说明 |
---|---|---|
astore |
vindex |
从栈中弹出对象引用,然后将其存到位置为vindex的局部变量中 |
astore_0 |
(无) |
从栈中弹出对象引用,然后将其存到位置为0的局部变量中 |
astore_1 |
(无) |
从栈中弹出对象引用,然后将其存到位置为1的局部变量中 |
astore_2 |
(无) |
从栈中弹出对象引用,然后将其存到位置为2的局部变量中 |
astore_3 |
(无) |
从栈中弹出对象引用,然后将其存到位置为3的局部变量中 |
5,wide指令
无符号8位局部变量索引,把方法中局部变量数的限制在256以下。一条单独的wide指令可以将8位的索引再扩展8位,就可以把局部变量数的限制扩展到65536.
操作码 |
操作数 |
说明 |
---|---|---|
wide |
iload,index |
从局部变量位置为index的地方取出int类型值,并将其压入栈 |
wide |
lload ,index |
从局部变量位置为index的地方取出long类型值,并将其压入栈 |
wide |
fload,index |
从局部变量位置为index的地方取出float类型值,并将其压入栈 |
wide |
dload,index |
从局部变量位置为index的地方取出double类型值,并将其压入栈 |
wide |
aload,index |
从局部变量位置为index的地方取出对象引用,并将其压入栈 |
wide |
istore,index |
从栈中弹出int类型值,将其存入位置为index的局部变量中 |
wide |
lstore,index |
从栈中弹出long类型值,将其存入位置为index的局部变量中 |
wide |
fstore,index |
从栈中弹出float类型值,将其存入位置为index的局部变量中 |
wide |
dstore,index |
从栈中弹出double类型值,将其存入位置为index的局部变量中 |
wide |
astore,index |
从栈中弹出对象引用,将其存入位置为index的局部变量中 |
跳转指令并不允许直接跳转到被wide指令修改过的操作码。