任何一台计算机的指令系统一般都包含有几十条到上百条指令,下面按一般计算机的功能把指令划分以下几种类型.
(1)算术运算指令
计算机指令系统一般都设有二进制数加\减\比较和求补等最基本的指令,此外还设置了乘\除法运算指令\浮点运算指令以有十进制动算指令等.
(2)逻辑运算指令
一般计算机都具有与\或\非(求反)\异或(按位加)和测试等逻辑运算指令.
(3)数据传送指令.
这是一种常用的指令,用以实现寄存器与寄存器,寄存器与存储单元以及存储器单元与存储器单元之间的数据传送,对于存储器来说,数据传送包括对数据的读(相当于取数指令)和写(相当于存数指令)操作.
(4)移位操作指令
移位操作指令分为算术移位\逻辑移位和循环移位三种,可以实现对操作数左移或右移一位或若干位.
(5)堆栈及堆栈操作指令.
堆栈是由若干个连续存储单元组成的先进后出(FILO)存储区,第一个送入堆栈中的数据存放在栈底,最后送入堆栈中的数据存放在栈顶.栈底是固定不变的,而栈顶却是随着数据的入栈和出栈在不断变化.
(6)字符串处理指令.
字符串处理指令就是一种非数值处理指令,一般包括字符串传送,字符串转换(把一种编码的字符串转换成另一种编码的字符串),字符串比较,字符串查找(查找字符串中某一子串),字符串匹配,字符串的抽取(提取某一子串)和替换(把某一字符串用另一字符串替换)等.
(7)输入输出(I/O)指令.
计算机本身公是数据处理和管理机构,不能产生原始数把,也不能长期保存数据.所处理的一切原始数据均来自输入设备,所得的处理结果必须通过外总设备输出.
(8)其它指令.
特权指令----具有特殊权限的指令,在多服务用户\多任务的计算机系统中,特权指令是不可少的.
陷阱与陷阱指令---陷阱实际上是一种意外事故中断,中断的目的不是为请求CPU的正常处理,面是为了通知CPU所出现的故障,并根据故障情况,转入相就的故障处理程序.
转移指令---用来控制程序的执行方向,实现程序的分支.
子程序调用指令---在骗写程序过程中,常常需要编写一些经常使用的\能够独立完成的某一特定功能的程序段,在需要时能随时调用,而不必重复编写,以便节省存储空间和简化程序设计.
https://www.cnblogs.com/jentleTao/p/12699894.html
《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指令集》
1、栈和局部变量操作
1.1 将常量压入栈的指令 19
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类型的项压入栈(使用宽索引)
1.2 从栈中的局部变量中装载值的指令 33
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类型值
1.3 将栈中的值存入局部变量的指令 33
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类型值存入数组中
2、类型转换 15
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类型
3、整数运算 13
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类型的局部变量上
4、逻辑运算
4.1 位移操作
1
2
3
4
5
6
ishl 执行int类型的向左移位操作
lshl 执行long类型的向左移位操作
ishr 执行int类型的向右移位操作
lshr 执行long类型的向右移位操作
iushr 执行int类型的向右逻辑移位操作
lushr 执行long类型的向右逻辑移位操作
4.2 按位布尔运算
1
2
3
4
5
6
iand 对int类型值进行“逻辑与”操作
land 对long类型值进行“逻辑与”操作
ior 对int类型值进行“逻辑或”操作
lor 对long类型值进行“逻辑或”操作
ixor 对int类型值进行“逻辑异或”操作
lxor 对long类型值进行“逻辑异或”操作
4.3 浮点运算 12个
fadd 执行float类型的加法
dadd 执行double类型的加法
fsub 执行float类型的减法
dsub 执行double类型的减法
fmul 执行float类型的乘法
dmul 执行double类型的乘法
fdiv 执行float类型的除法
ddiv 执行double类型的除法
frem 计算float类型除法的余数
drem 计算double类型除法的余数
fneg 将一个float类型的数值取反
dneg 将一个double类型的数值取反
5、对象和数组
5.1 对象操作指令 7
new 创建一个新对象
checkcast 确定对象为所给定的类型
getfield 从对象中获取字段
putfield 设置对象中字段的值
getstatic 从类中获取静态字段
putstatic 设置类中静态字段的值
instanceof 判断对象是否为给定的类型
5.2 数组操作指令4
newarray 分配数据成员类型为基本上数据类型的新数组
anewarray 分配数据成员类型为引用类型的新数组
arraylength 获取数组长度
multianewarray 分配新的多维数组
6、控制流
6.1 条件分支指令 16
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 如果两个对象引用不相等,则跳转
7、比较指令 5
lcmp 比较long类型值
fcmpl 比较float类型值(当遇到NaN时,返回-1)
fcmpg 比较float类型值(当遇到NaN时,返回1)
dcmpl 比较double类型值(当遇到NaN时,返回-1)
dcmpg 比较double类型值(当遇到NaN时,返回1)
8、无条件转移指令 2
goto 无条件跳转
goto_w 无条件跳转(宽索引)
9、表跳转指令
1
2
tableswitch 通过索引访问跳转表,并跳转
lookupswitch 通过键值匹配访问跳转表,并执行跳转操作
10、异常5
athrow 抛出异常或错误
finally子句
jsr 跳转到子例程
jsr_w 跳转到子例程(宽索引)
rct 从子例程返回
11、方法调用与返回
11.1 方法调用指令 4
invokcvirtual 运行时按照对象的类来调用实例方法
invokespecial 根据编译时类型来调用实例方法
invokestatic 调用类(静态)方法
invokcinterface 调用接口方法
11.2 方法返回指令 6
ireturn 从方法中返回int类型的数据
lreturn 从方法中返回long类型的数据
freturn 从方法中返回float类型的数据
dreturn 从方法中返回double类型的数据
areturn 从方法中返回引用类型的数据
return 从方法中返回,返回值为void
12、线程同步
1
2
montiorenter 进入并获取对象监视器
monitorexit 释放并退出对象监视器
jvm 指令 invokevirtual indexbyte1 indexbyte2 方法索引位置 (indexbyte1 << 8) | indexbyte2
1、Java语言规范中如何定义boolean?
boolean的取值只有两种:true或者false
然而这两个符号无法被虚拟机直接使用
2、JVM规范是如何定义boolean类型的?
JVM中boolean类型被映射为int类型
true映射为整数1
false映射为整数0
————————————————
https://blog.csdn.net/zhoufanyang_china/article/details/86750351 获取java对象的内存地址
https://blog.csdn.net/u013928208/article/details/106758062 jdk c 源码 jvm启动分析较好.
先简单介绍一下instanceof的字节码操作:确定对象是否为给定的类型指令格式:instanceof|indexbyte1|indexbyte2指令执行前后的栈顶状态:
……,objectref=> 从栈顶弹出
……,result 把结果压入栈顶
描述:indexbyte1和indexbyte2用于构造对当前类的常量池的索引,objectref为reference类型,可以是某个类,数组的实例或者是接口。基本的实现过程:对indexbyte1和indexbyte2构造的常量池索引进行解析,然后根据java规范判断解析的类是不是objectref的一个实例,最后在栈顶写入结果。大体是这么个过程,没有详细列举细节
jvm 常量池排列算法. : 添加class 首先看有没有对应的utf8 没有 则建立后再加入class
这样引用就不会出错. 但asm 项目 49 [1.5] 显示出来的是 正序. jdk 1.8 显示反序
int addConstantNameAndType(final String name, final String descriptor) {
final int tag = Symbol.CONSTANT_NAME_AND_TYPE_TAG;
int hashCode = hash(tag, name, descriptor);
Entry entry = get(hashCode);
while (entry != null) {
if (entry.tag == tag
&& entry.hashCode == hashCode
&& entry.name.equals(name)
&& entry.value.equals(descriptor)) {
return entry.index;
}
entry = entry.next;
}
//此处嵌套添加. 要想 添加 addConstantNameAndType 则必须先添加 utf8... good!! 晚安 小姑姑. 南无阿弥陀佛 南无地藏王菩萨
constantPool.put122(tag, addConstantUtf8(name), addConstantUtf8(descriptor));
return put(new Entry(constantPoolCount++, tag, name, descriptor, hashCode)).index;
}
jvm包括两种数据类型,基本类型和引用类型。
基本类型包括,数值类型,boolean类型,和returnAddress类型。
数值类型包括,整型,浮点型,和char类型。
boolean类型同样只有true和false。
returnAddress类型是一个指针,指向jvm指令的操作码,在Java中没有与之对应的类型。
boolean类型的操作会被转化为int类型的操作进行,boolean数组会当成byte数组去操作。1表示true,0表示false。
引用类型包括三种,类类型,数组类型,和接口类型。
有三种reference
类型:类类型,数组类型和接口类型。它们的值分别引用动态创建的类实例,数组或实现接口的类实例或数组。
数组类型由具有单个维度的组件类型组成 (其长度未由类型指定)。数组类型的组件类型本身可以是数组类型。如果从任何数组类型开始,先考虑其组件类型,然后再考虑(如果也是数组类型)该类型的组件类型,依此类推,则最终必须达到不是数组类型的组件类型;这称为数组类型的元素类型。数组类型的元素类型必须是原始类型,类类型或接口类型。
甲reference
值也可以是专用空引用的,没有对象的引用,这将在这里通过来表示null
。该null
引用最初没有运行时类型,但可以强制转换为任何类型。reference
类型的默认值是null
。
本规范不要求进行具体的值编码null
。
jvm 指令与相对类型 匹配
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.11.1
数据类型
在 JVM 中,数据分为两大类:primitive types (原生类型)和 reference types(引用类型)。
引用类型,让 JVM 能更好的支持于面向对象语言的设计,引用类型的值用来指向内存中分配的类实例或者数组。JVM 规范中并没有详细规定引用类型的实现细节,比如引用应该通过何种方式去定位、访问堆中的对象,具体的对象访问方式取决于虚拟机的具体实现,比如 HotSpot 有其自己的实现方案。
目前主流的访问方式有使用句柄和直接指针两种:
其中使用直接指针访问的方式,类似于 C++ 中的虚表(虚表就是指向对象类型数据的指针)。这两种对象访问方式各有优劣,使用句柄访问的最大好处就是 reference 中存储的是稳定的句柄地址,在对象被移动(比如垃圾回收时,整理内存空间,会移动对象的存储位置)时只会改变句柄中示例数据的指针,而 reference 本身不需要修改。
使用直接指针访问的最大好处就是速度更快,节省了一次内存寻址的时间开销。
原生数据类型包括:numeric types, boolean type, returnAddress type。其中 returnAddress 数据只存在于字节码层面,与编程语言无关,也就是说,我们在 Java 语言中是不会直接与 returnAddress 类型的数据打交道的。
returnAddress 类型的值是指向字节码的指针,不管是物理机还是虚拟机,运行时内存中的数据总归可分为两类:代码,数据。对于冯诺依曼结构的计算机,指令数据和数值数据都存储在内存中,而哈弗结构的计算机,将程序指令与数据分开存储。
对于 JVM 来说,程序就是存储在方法区的字节码指令,而 returnAddress 类型的值就是指向特定指令内存地址的指针。
JVM支持多线程,每个线程有自己的程序计数器(pc register),而 pc 中的值就是当前指令所在的内存地址,即 returnAddress 类型的数据,当线程执行 native 方法时,pc 中的值为 undefined。
栈帧
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,栈帧中存储了方法的局部变量表,操作数栈,动态连接,和方法返回地址等信息。在程序编译时,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写在方法表的 Code 属性中。
当一个方法开始执行后,只有两种方式可以退出,第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这种方式称为正常完成出口;另外一种退出方式是,在方法执行过程中遇到异常,且该异常没有被被捕获,称为异常完成出口。
无论是哪种退出方式,在方法退出后,都需要返回到该方法被调用的位置(地址),让程序继续执行。一般来说,方法执行前,会保存调用者当前的 PC 计数器中的值,当方法正常退出时,将该 PC 计数器的值会作为返回地址,返回给调用者。在方法异常退出时,返回地址是通过异常处理器表来确定的。
方法退出的过程实际上就等于把当前栈帧出栈,一般过程为:
恢复上层方法的局部变量表和操作数栈
把返回值压入调用者栈帧的操作数栈中
调整 PC 计数器的值,以指向方法调用指令后面的一条指令
《Thinking in Java》中写到:如果对char、byte 或者 short类型的数值进行移位处理,那么在移位进行之前,它们会被转换为int类型,并且得到的结果也是一个int类型。只有数值右端的低5位才有用。这样可以防止我们移位超过int型值所具有的位数。
byte、short、int、char类型的数据经过移位运算后结果都为int型。
long经过移位运算后结果为long型。
https://blog.csdn.net/chattie/article/details/4029922
例如第一个byte 为1 则 左移 8位则为 256
https://www.cnblogs.com/Yee-Q/p/14250375.html
可以发现,大部分指令都没有支持 byte、char、short、boolean,编译器会在编译期或运行期将 byte 和 short 类型的数据带符号扩展为相应的 int 类型数据,将 boolean 和 char 类型数据零位扩展为相应的 int 类型数据,然后使用对应 int 类型的字节码指令来处理。因此,大多数对于 boolean、byte、short 和 char 类型数据的操作,实际上都是转换成 int 类型再进行操作
https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.4
官方文档(最好的jvm教程)
由于限制了Java虚拟机操作码的长度为一个字节(即0~255),这意味着指令集的操作码总数不可能超过256条。
jvm的opcode都是占一个字节,所以jvm虚拟机最多可有256条opcode。目前jvm规范8中列出的opcode有0x000xc9(0201),加上3个保留opcode 0xca,0xfe,0xff(202,254,255),共205条。
三个保留操作码:
254(0xfe) impdep1 后门
255(0xff) impdep2 陷进
202(0xca) breakpoint 断点
其中前203条的编码都是连续的,很有规律。
其中,操作码值分别为 254(0xfe)和 255(0xff),助记符分别为 impdep1 和 impdep2的两个操作码是作为“后门”和“陷阱”出现,目的是在某些硬件和软件中提供一些与实现相关的功能。第三个操作码值分别为 202(0xca)、助记符为 breakpoint 的操作码是用于调试器实现断点功能。
你开发的系统是 裸奔 的吗?深夜被老板 Diss
一套系统是否稳定运行,取决于它的运行健康度,而这包括; 调用量 、 可用率 、 响应时长 以及服务器
性能等各项指标的一个综合值。并且在系统出现异常问题时,可以抓取整个业务方法执行链路并输出;
当时的入参、出参、异常信息等等。当然还包括一些JVM、Redis、Mysql的各项性能指标,以用于快速
定位并解决问题。
那么要做到这样的事情有什么监控方案呢,这里面的做法比较多。比如;
- 最简单粗暴的可能就是硬编码在方法中,收取执行耗时以及出入参和异常信息。但这样的成本实在
太大,而且有一些不可预估的风险。 - 可以选择切面方式做一套统一监控的组件,相对来说还是好一些的。但也需要硬编码,同时维护成
本不低。 - 市面上对于这样的监控其实是有整套的非入侵监控方案的,比如; Google Dapper 、 Zipkin 等
都可以实现,他们都是基于探针技术非入侵的采用字节码增强的方式进行监控。
好,那么这样非入侵的探针方式是怎么实现的呢?如何去做方法的 字节码增强 ?
在字节码增强方面有三个框架; ASM 、 Javassist 、 ByteCode ,各有优缺点按需选择。这在我们之前
的字节码编程文章里也有所提到。
reference
reference是一个对象实例的引用
作用:
从此引用中直接或间接地查找到对象在Java堆中的数据存放的起始地址索引
从此引用中直接或间接地查找到对象所属数据类型在方法区中的存储的类型对象(因为类信息在方法区中存储)
java 编译的官方 good
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-3.html
package com.rumenz;
public class rumenz{
public Integer id=10;
public String name="入门";
public final int age=100;
public void setId(Integer id){
this.id=id;
}
}
javap 后的
> javac rumenz.java
> javap -v rumenz
Classfile /code/rumenz.class
Last modified 2020-10-17; size 542 bytes
MD5 checksum 6a8a73fb6327c1a64e9ad54e53e94afd
Compiled from "rumenz.java"
public class com.rumenz.rumenz
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #9.#24 // java/lang/Object."":()V
#2 = Fieldref #8.#25 // com/rumenz/rumenz.id:I
#3 = String #26 // 入门
#4 = Fieldref #8.#27 // com/rumenz/rumenz.name:Ljava/lang/String;
#5 = Methodref #28.#29 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#6 = Fieldref #8.#30 // com/rumenz/rumenz.age:Ljava/lang/Integer;
#7 = Methodref #28.#31 // java/lang/Integer.intValue:()I
#8 = Class #32 // com/rumenz/rumenz
#9 = Class #33 // java/lang/Object
#10 = Utf8 id
#11 = Utf8 I
#12 = Utf8 name
#13 = Utf8 Ljava/lang/String;
#14 = Utf8 age
#15 = Utf8 Ljava/lang/Integer;
#16 = Utf8
#17 = Utf8 ()V
#18 = Utf8 Code
#19 = Utf8 LineNumberTable
#20 = Utf8 setId
#21 = Utf8 (Ljava/lang/Integer;)V
#22 = Utf8 SourceFile
#23 = Utf8 rumenz.java
#24 = NameAndType #16:#17 // "":()V
#25 = NameAndType #10:#11 // id:I
#26 = Utf8 入门
#27 = NameAndType #12:#13 // name:Ljava/lang/String;
#28 = Class #34 // java/lang/Integer
#29 = NameAndType #35:#36 // valueOf:(I)Ljava/lang/Integer;
#30 = NameAndType #14:#15 // age:Ljava/lang/Integer;
#31 = NameAndType #37:#38 // intValue:()I
#32 = Utf8 com/rumenz/rumenz
#33 = Utf8 java/lang/Object
#34 = Utf8 java/lang/Integer
#35 = Utf8 valueOf
#36 = Utf8 (I)Ljava/lang/Integer;
#37 = Utf8 intValue
#38 = Utf8 ()I
{
public int id;
descriptor: I
flags: ACC_PUBLIC
public java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC
public final java.lang.Integer age;
descriptor: Ljava/lang/Integer;
flags: ACC_PUBLIC, ACC_FINAL
public com.rumenz.rumenz();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: aload_0
5: bipush 10
7: putfield #2 // Field id:I
10: aload_0
11: ldc #3 // String 入门
13: putfield #4 // Field name:Ljava/lang/String;
16: aload_0
17: bipush 100
19: invokestatic #5 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
22: putfield #6 // Field age:Ljava/lang/Integer;
25: return
LineNumberTable:
line 11: 0
line 12: 4
line 13: 10
line 14: 16
public void setId(java.lang.Integer);
descriptor: (Ljava/lang/Integer;)V
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=2
0: aload_0
1: iconst_3
2: aload_1
3: invokevirtual #7 // Method java/lang/Integer.intValue:()I
6: iadd
7: putfield #2 // Field id:I
10: return
LineNumberTable:
line 17: 0
line 18: 10
}
SourceFile: "rumenz.java"
jvm 常量池数据结构
https://blog.csdn.net/weixin_33796177/article/details/91700668
hotspot 分析
https://www.cnblogs.com/dennyzhangdd/p/6734933.html
JVM中 int 类型数值,根据 取值范围将 入栈的 字节码指令 就分为4类:
取值 -1~5 采用 iconst 指令;
取值 -128~127 采用 bipush 指令;
取值 -32768~32767 采用 sipush指令;
取值 -2147483648~2147483647 采用 ldc 指令。
注意,上图中ldc指令是从常量池中取值的,ldc指令用于将int、float、String类型常量从常量池中压到栈顶,在这段范围(-2147483648~2147483647)内的int值存储在常量池中。
jvm指令大部分1个字节
少部分 带2个操作数
MULTIANEWARRAY 3个操作数
.只有JSR_W GOTO_W invokeinterface invokedynamic 是4字节)
iconst、bipush、sipush、ldc指令的区别
java 虚拟机的命令由一个操作码(Opcode)以及零或多个操作数(Operands)组成。其中,操作码是一个字节长度的数字,不同的数字代表不同的操作码。对于操作数而言,一个字节码的数字代表一个操作数。由于 java 虚拟机采用面向操作数栈而不是寄存器的架构,因此,大多数的命令都不包含操作数,只有一个操作码。
在 java 虚拟机的命令集中,大多数的命令都包含了其操作所对应的数据类型信息,在这些命令中,操作码中第一个字母表明了其数据类型:i 代表 int,l 代表 long,s 代表 short,b 代表 byte,c 代表 char,f 代表 float,d 代表 double,a 代表 reference。
由于操作码只占一个字节,所以最多只有 256 个命令,这也决定了 java 虚拟机中只对一些常用的操作提供了有限的类型相关识别命令,对于剩下的部分,java 虚拟机通过类型转换将其他类型转成虚拟机命令支持的类型然而再进行操作。
由于虚拟机命令很多,而且部分命令功能类似,所以本文只介绍部分常用的命令。
加载命令
加载命令是指将数据在栈帧中的局部变量表加载到操作数栈中。其主要的命令包括两种:将一个局部变量加载到操作数栈和将一个常量加载到操作数栈。
加载局部变量
部分常见的加载局部变量的命令如下所示
助记符 | 操作数 | 含义 |
---|---|---|
iload | 一个,i | 加载第 i 个 int 类型的局部变量,i 从 0 开始计数 |
iload_0 | 0 | 加载第 0 个 int 类型的局部变量,与 iload 0 命令相同 |
iload_1 | 无 | 加载第 1 个 int 类型的局部变量,与 iload 1 命令相同 |
iload_2 | 无 | 加载第 2 个 int 类型的局部变量,与 iload 2 命令相同 |
iload_3 | 无 | 加载第 3 个 int 类型的局部变量,与 iload 3 命令相同 |
lload | 一个,i | 加载第 i 个 long 类型的局部变量,i 从 0 开始计数 |
lload_0 | 无 | 加载第 0 个 long 类型的局部变量,与 lload 0 命令相同 |
lload_1 | 无 | 加载第 1 个 long 类型的局部变量,与 lload 1 命令相同 |
lload_2 | 无 | 加载第 2 个 long 类型的局部变量,与 lload 2 命令相同 |
lload_3 | 无 | 加载第 3 个 long 类型的局部变量,与 lload 3 命令相同 |
fload | 一个,i | 加载第 i 个 float 类型的局部变量,i 从 0 开始计数 |
fload_0 | 无 | 加载第 0 个 float 类型的局部变量,与 fload 0 命令相同 |
fload_1 | 无 | 加载第 1 个 float 类型的局部变量,与 fload 1 命令相同 |
fload_2 | 无 | 加载第 2 个 float 类型的局部变量,与 fload 2 命令相同 |
fload_3 | 无 | 加载第 3 个 float 类型的局部变量,与 fload 3 命令相同 |
dload | 一个,i | 加载第 i 个 double 类型的局部变量,i 从 0 开始计数 |
dload_0 | 无 | 加载第 0 个 double 类型的局部变量,与 dload 0 命令相同 |
dload_1 | 无 | 加载第 1 个 double 类型的局部变量,与 dload 1 命令相同 |
dload_2 | 无 | 加载第 2 个 double 类型的局部变量,与 dload 2 命令相同 |
dload_3 | 无 | 加载第 3 个 double 类型的局部变量,与 dload 3 命令相同 |
aload | 一个,i | 加载第 i 个 引用类型的局部变量,i 从 0 开始计数 |
aload_0 | 无 | 加载第 0 个 引用类型的局部变量,与 aload 0 命令相同 |
aload_1 | 无 | 加载第 1 个 引用类型的局部变量,与 aload 1 命令相同 |
aload_2 | 无 | 加载第 2 个 引用类型的局部变量,与 aload 2 命令相同 |
aload_3 | 无 | 加载第 3 个 引用类型的局部变量,与 aload 3 命令相同 |
加载常量
部分常见的加载常量的命令如下所示
操作码 | 操作数 | 含义 | |
---|---|---|---|
bipush | 一个,i | 将单字节的常量值 i (-128 ~ 127)推送到栈顶 | |
sipush | 两个,byte1, byte2 | 将 byte1 和 byte2 进行 ((byte1 << 8) | byte2) 运算,得到 short 类型(-32768 ~ 32767),并将其推送到栈顶 |
ldc | 一个,i | 与 ldc_w 类似,将常量池中第 i 个(从 1 开始计数)常量的值取出推送到栈顶,类型可以是 int、float、String。当常量池中常量数量不超过 255 个时,使用此命令,否则使用 ldc_w | |
ldc_w | 两个,indexbyte1,indexbyte2 | 与 ldc 类似,将常量池中第 ((indexbyte1 << 8) | indexbyte2) 个(从 1 开始计数)常量的值取出推送到栈顶,类型可以是 int、float、String。当常量池中常量数量超过 255 个时,使用此命令,否则使用 ldc |
ldc2_w | 两个,indexbyte1,indexbyte2 | 将常量池中第 ((indexbyte1 << 8) | indexbyte2) 个(从 1 开始计数)常量的值取出推送到栈顶,类型可以是 long 或 double |
aconst_null | 无 | 将一个 null 推送到栈顶 | |
iconst_m1 | 无 | 将数字 -1(int 类型)推送到栈顶 | |
iconst_0 | 无 | 将数字 0(int 类型)推送到栈顶 | |
iconst_1 | 无 | 将数字 1(int 类型)推送到栈顶 | |
iconst_2 | 无 | 将数字 2(int 类型)推送到栈顶 | |
iconst_3 | 无 | 将数字 3(int 类型)推送到栈顶 | |
iconst_4 | 无 | 将数字 4(int 类型)推送到栈顶 | |
iconst_5 | 无 | 将数字 5(int 类型)推送到栈顶 | |
lconst_0 | 无 | 将数字 0(long 类型)推送到栈顶 | |
lconst_1 | 无 | 将数字 1(long 类型)推送到栈顶 | |
fconst_0 | 无 | 将数字 0(float 类型)推送到栈顶 | |
fconst_1 | 无 | 将数字 1(float 类型)推送到栈顶 | |
fconst_2 | 无 | 将数字 2(float 类型)推送到栈顶 | |
dconst_0 | 无 | 将数字 0(double 类型)推送到栈顶 | |
dconst_1 | 无 | 将数字 1(double 类型)推送到栈顶 |
存储命令
存储命令主要是将一个数值从操作数栈存储到局部变量表中,常见的命令如下
助记符 | 操作数 | 含义 |
---|---|---|
istore | 一个,i | 将栈顶 int 类型数值存入第 i 个(从 0 开始计数)本地局部变量 |
istore_0 | 无 | 将栈顶 int 类型的数值存入第 0 个本地局部变量,与 istore 0 命令相同 |
istore_1 | 无 | 将栈顶 int 类型的数值存入第 1 个本地局部变量,与 istore 1 命令相同 |
istore_2 | 无 | 将栈顶 int 类型的数值存入第 2 个本地局部变量,与 istore 2 命令相同 |
istore_3 | 无 | 将栈顶 int 类型的数值存入第 3 个本地局部变量,与 istore 3 命令相同 |
lstore | 一个,i | 将栈顶 long 类型数值存入第 i 个(从 0 开始计数)本地局部变量 |
lstore_0 | 无 | 将栈顶 long 类型的数值存入第 0 个本地局部变量,与 lstore 0 命令相同 |
lstore_1 | 无 | 将栈顶 long 类型的数值存入第 1 个本地局部变量,与 lstore 1 命令相同 |
lstore_2 | 无 | 将栈顶 long 类型的数值存入第 2 个本地局部变量,与 lstore 2 命令相同 |
lstore_3 | 无 | 将栈顶 long 类型的数值存入第 3 个本地局部变量,与 lstore 3 命令相同 |
fstore | 一个,i | 将栈顶 float 类型数值存入第 i 个(从 0 开始计数)本地局部变量 |
fstore_0 | 无 | 将栈顶 float 类型的数值存入第 0 个本地局部变量,与 fstore 0 命令相同 |
fstore_1 | 无 | 将栈顶 float 类型的数值存入第 1 个本地局部变量,与 fstore 1 命令相同 |
fstore_2 | 无 | 将栈顶 float 类型的数值存入第 2 个本地局部变量,与 fstore 2 命令相同 |
fstore_3 | 无 | 将栈顶 float 类型的数值存入第 3 个本地局部变量,与 fstore 3 命令相同 |
dstore | 一个,i | 将栈顶 double 类型数值存入第 i 个(从 0 开始计数)本地局部变量 |
dstore_0 | 无 | 将栈顶 double 类型的数值存入第 0 个本地局部变量,与 dstore 0 命令相同 |
dstore_1 | 无 | 将栈顶 double 类型的数值存入第 1 个本地局部变量,与 dstore 1 命令相同 |
dstore_2 | 无 | 将栈顶 double 类型的数值存入第 2 个本地局部变量,与 dstore 2 命令相同 |
dstore_3 | 无 | 将栈顶 double 类型的数值存入第 3 个本地局部变量,与 dstore 3 命令相同 |
astore | 一个,i | 将栈顶引用类型数值存入第 i 个(从 0 开始计数)本地局部变量 |
astore_0 | 无 | 将栈顶引用类型的数值存入第 0 个本地局部变量,与 astore 0 命令相同 |
astore_1 | 无 | 将栈顶引用类型的数值存入第 1 个本地局部变量,与 astore 1 命令相同 |
astore_2 | 无 | 将栈顶引用类型的数值存入第 2 个本地局部变量,与 astore 2 命令相同 |
astore_3 | 无 | 将栈顶引用类型的数值存入第 3 个本地局部变量,与 astore 3 命令相同 |
运算命令
运算命令用于对两个操作数栈上的数值进行某种特定的运算,并将结果重新存入到操作数栈中。运算命令包括:加法命令、减法命令、乘法命令、除法命令、求余命令、取反命令、位移命令、按位或命令、按位与命令、按位异或命令、局部变量自增命令、比较命令。
加法命令
助记符 | 操作数 | 含义 |
---|---|---|
iadd | 无 | 将栈顶两个 int 类型的数值出栈,对其求和,并将结果压入栈中 |
ladd | 无 | 将栈顶两个 long 类型的数值出栈,对其求和,并将结果压入栈中 |
fadd | 无 | 将栈顶两个 float 类型的数值出栈,对其求和,并将结果压入栈中 |
dadd | 无 | 将栈顶两个 double 类型的数值出栈,对其求和,并将结果压入栈中 |
减法命令
助记符 | 操作数 | 含义 |
---|---|---|
isub | 无 | 将栈顶两个 int 类型的数值出栈,以第一个栈顶元素为减数,第二个栈顶元素为被减数,对其求差,并将结果压入栈中 |
lsub | 无 | 将栈顶两个 long 类型的数值出栈,以第一个栈顶元素为减数,第二个栈顶元素为被减数,对其求差,并将结果压入栈中 |
fsub | 无 | 将栈顶两个 float 类型的数值出栈,以第一个栈顶元素为减数,第二个栈顶元素为被减数,对其求差,并将结果压入栈中 |
dsub | 无 | 将栈顶两个 double 类型的数值出栈,以第一个栈顶元素为减数,第二个栈顶元素为被减数,对其求差,并将结果压入栈中 |
乘法命令
助记符 | 操作数 | 含义 |
---|---|---|
imul | 无 | 将栈顶两个 int 类型的数值出栈,对其求积,并将结果压入栈中 |
lmul | 无 | 将栈顶两个 long 类型的数值出栈,对其求积,并将结果压入栈中 |
fmul | 无 | 将栈顶两个 float 类型的数值出栈,对其求积,并将结果压入栈中 |
dmul | 无 | 将栈顶两个 double 类型的数值出栈,对其求积,并将结果压入栈中 |
除法命令
助记符 | 操作数 | 含义 |
---|---|---|
idiv | 无 | 将栈顶两个 int 类型的数值出栈,以第一个栈顶元素为除数,第二个栈顶元素为被除数,对其求商,并将结果压入栈中 |
ldiv | 无 | 将栈顶两个 long 类型的数值出栈,以第一个栈顶元素为除数,第二个栈顶元素为被除数,对其求商,并将结果压入栈中 |
fdiv | 无 | 将栈顶两个 float 类型的数值出栈,以第一个栈顶元素为除数,第二个栈顶元素为被除数,对其求商,并将结果压入栈中 |
ddiv | 无 | 将栈顶两个 double 类型的数值出栈,以第一个栈顶元素为除数,第二个栈顶元素为被除数,对其求商,并将结果压入栈中 |
求余命令
助记符 | 操作数 | 含义 |
---|---|---|
irem | 无 | 将栈顶两个 int 类型的数值出栈,以第一个栈顶元素为除数,第二个栈顶元素为被除数,对其求余,并将结果压入栈中 |
lrem | 无 | 将栈顶两个 long 类型的数值出栈,以第一个栈顶元素为除数,第二个栈顶元素为被除数,对其求余,并将结果压入栈中 |
frem | 无 | 将栈顶两个 float 类型的数值出栈,以第一个栈顶元素为除数,第二个栈顶元素为被除数,对其求余,并将结果压入栈中 |
drem | 无 | 将栈顶两个 double 类型的数值出栈,以第一个栈顶元素为除数,第二个栈顶元素为被除数,对其求余,并将结果压入栈中 |
取反命令
助记符 | 操作数 | 含义 |
---|---|---|
ineg | 无 | 将栈顶 int 类型的数值出栈,取反,并将结果压入栈中 |
lneg | 无 | 将栈顶 long 类型的数值出栈,取反,并将结果压入栈中 |
fneg | 无 | 将栈顶 float 类型的数值出栈,取反,并将结果压入栈中 |
dneg | 无 | 将栈顶 double 类型的数值出栈,取反,并将结果压入栈中 |
位移命令
助记符 | 操作数 | 含义 |
---|---|---|
ishl | 无 | 将栈顶两个 int 类型的数值出栈,以第一个栈顶元素为移动位数,第二个栈顶元素为位移数,进行左位移操作,并将结果压入栈中 |
lshl | 无 | 将栈顶两个 long 类型的数值出栈,以第一个栈顶元素为移动位数,第二个栈顶元素为位移数,进行左位移操作,并将结果压入栈中 |
ishr | 无 | 将栈顶两个 int 类型的数值出栈,以第一个栈顶元素为移动位数,第二个栈顶元素为位移数,进行带符号右位移操作,并将结果压入栈中 |
lshr | 无 | 将栈顶两个 long 类型的数值出栈,以第一个栈顶元素为移动位数,第二个栈顶元素为位移数,进行带符号右位移操作,并将结果压入栈中 |
iushr | 无 | 将栈顶两个 int 类型的数值出栈,以第一个栈顶元素为移动位数,第二个栈顶元素为位移数,进行无符号右位移操作,并将结果压入栈中 |
lushr | 无 | 将栈顶两个 long 类型的数值出栈,以第一个栈顶元素为移动位数,第二个栈顶元素为位移数,进行无符号右位移操作,并将结果压入栈中 |
按位或命令
助记符 | 操作数 | 含义 |
---|---|---|
ior | 无 | 将栈顶两个 int 类型的数值出栈,对其进行按位或运算,并将结果压入栈中 |
lor | 无 | 将栈顶两个 long 类型的数值出栈,对其进行按位或运算,并将结果压入栈中 |
按位与命令
助记符 | 操作数 | 含义 |
---|---|---|
iand | 无 | 将栈顶两个 int 类型的数值出栈,对其进行按位与运算,并将结果压入栈中 |
land | 无 | 将栈顶两个 long 类型的数值出栈,对其进行按位与运算,并将结果压入栈中 |
按位异或命令
助记符 | 操作数 | 含义 |
---|---|---|
ixor | 无 | 将栈顶两个 int 类型的数值出栈,对其进行按位异或运算,并将结果压入栈中 |
lxor | 无 | 将栈顶两个 long 类型的数值出栈,对其进行按位异或运算,并将结果压入栈中 |
局部变量自增命令
助记符 | 操作数 | 含义 |
---|---|---|
iinc | 两个,index 和 const | 将第 index 个(index 从 0 开始计算)局部变量的数值加上 const 常量数值得到结果 |
比较命令
助记符 | 操作数 | 含义 |
---|---|---|
lcmp | 无 | 将栈顶两个 long 类型的数值出栈,以第一个栈顶元素为 value2,第二个栈顶元素为value1,如果 value1 - value2 > 0,则将 1 压入栈中,如果 value1 - value2 = 0,则将 0 压入栈中,如果 value1 - value2 < 0,则将 -1 压入栈中 |
fcmpl | 无 | 将栈顶两个 float 类型的数值出栈,以第一个栈顶元素为 value2,第二个栈顶元素为value1,如果 value1 和 value2 中有一个值为 NaN,则将 -1 压如栈中;如果 value1 - value2 > 0,则将 1 压入栈中,如果 value1 - value2 = 0,则将 0 压入栈中,如果 value1 - value2 < 0,则将 -1 压入栈中 |
fcmpg | 无 | 将栈顶两个 float 类型的数值出栈,以第一个栈顶元素为 value2,第二个栈顶元素为value1,如果 value1 和 value2 中有一个值为 NaN,则将 1 压如栈中;如果 value1 - value2 > 0,则将 1 压入栈中,如果 value1 - value2 = 0,则将 0 压入栈中,如果 value1 - value2 < 0,则将 -1 压入栈中 |
dcmpl | 无 | 将栈顶两个 double 类型的数值出栈,以第一个栈顶元素为 value2,第二个栈顶元素为value1,如果 value1 和 value2 中有一个值为 NaN,则将 -1 压如栈中;如果 value1 - value2 > 0,则将 1 压入栈中,如果 value1 - value2 = 0,则将 0 压入栈中,如果 value1 - value2 < 0,则将 -1 压入栈中 |
dcmpg | 无 | 将栈顶两个 double 类型的数值出栈,以第一个栈顶元素为 value2,第二个栈顶元素为value1,如果 value1 和 value2 中有一个值为 NaN,则将 1 压如栈中;如果 value1 - value2 > 0,则将 1 压入栈中,如果 value1 - value2 = 0,则将 0 压入栈中,如果 value1 - value2 < 0,则将 -1 压入栈中 |
类型转换命令
java 虚拟机直接支持数值类型的宽化类型转换命令(即小范围类型向大范围类型的安全转换),这些转换无需显示的转换命令,当然,也可以用转换命令来要求其进行显示的转换。其主要包括以下三种类型
- int 类型转换到 long、float 或者 double 类型
- long 类型转换到 float、double 类型
- float 类型转换到 double 类型
对于窄转换类型(即大范围类型向小范围类型的安全转换),必须显示使用转换命令来完成,主要包括以操作
助记符 | 操作数 | 含义 |
---|---|---|
i2b | 无 | 将栈顶 int 类型数值出栈,将其带符号转换成 byte 类型数据,并将其结果压入操作数栈。注意:此过程可能导致精度丢失,甚至可能导致转换结果与原先数值有不同的正负号 |
i2c | 无 | 将栈顶 int 类型数值出栈,将其带符号转换成 char 类型数据,并将其结果压入操作数栈。注意:此过程可能导致精度丢失,甚至可能导致转换结果与原先数值有不同的正负号 |
i2s | 无 | 将栈顶 int 类型数值出栈,将其带符号转换成 short 类型数据,并将其结果压入操作数栈。注意:此过程可能导致精度丢失,甚至可能导致转换结果与原先数值有不同的正负号 |
l2i | 无 | 将栈顶 long 类型数值出栈,将其带符号转换成 int 类型数据,并将其结果压入操作数栈。注意:此过程可能导致精度丢失,甚至可能导致转换结果与原先数值有不同的正负号 |
l2f | 无 | 将栈顶 long 类型数值出栈,将其带符号转换成 int 类型数据,并将其结果压入操作数栈。注意:此过程可能导致精度丢失,甚至可能导致转换结果与原先数值有不同的正负号 |
l2d | 无 | 将栈顶 long 类型数值出栈,将其带符号转换成 double 类型数据,并将其结果压入操作数栈。注意:此过程可能导致精度丢失,甚至可能导致转换结果与原先数值有不同的正负号 |
f2i | 无 | 将栈顶 float 类型数值出栈,将其带符号转换成 int 类型数据,并将其结果压入操作数栈。注意:此过程可能导致精度丢失,甚至可能导致转换结果与原先数值有不同的正负号 |
f2l | 无 | 将栈顶 float 类型数值出栈,将其带符号转换成 long 类型数据,并将其结果压入操作数栈。注意:此过程可能导致精度丢失,甚至可能导致转换结果与原先数值有不同的正负号 |
f2d | 无 | 将栈顶 float 类型数值出栈,将其带符号转换成 double 类型数据,并将其结果压入操作数栈。注意:此过程可能导致精度丢失,甚至可能导致转换结果与原先数值有不同的正负号 |
d2i | 无 | 将栈顶 double 类型数值出栈,将其带符号转换成 int 类型数据,并将其结果压入操作数栈。注意:此过程可能导致精度丢失,甚至可能导致转换结果与原先数值有不同的正负号 |
d2l | 无 | 将栈顶 double 类型数值出栈,将其带符号转换成 long 类型数据,并将其结果压入操作数栈。注意:此过程可能导致精度丢失,甚至可能导致转换结果与原先数值有不同的正负号 |
d2f | 无 | 将栈顶 double 类型数值出栈,将其带符号转换成 float 类型数据,并将其结果压入操作数栈。注意:此过程可能导致精度丢失,甚至可能导致转换结果与原先数值有不同的正负号 |
对象创建与访问命令
助记符 | 操作数 | 含义 | |
---|---|---|---|
new | 两个,indexbyte1, indexbyte2 | 根据常量池中第 ((indexbyte1 << 8) | indexbyte2) 个(从 1 开始计数)常量索引指向的类创建该类实例,并将类引用压入栈顶 |
newarray | 一个,atype | 根据 atype 确定要创建的基本数据类型,其中 atype的值表示如下:4 表示 boolean,5 表示 char,6 表示 float,7 表示 double,8 表示 byte,9 表示 short,10 表示 int,11 表示 long。然后将栈顶 int 类型的数值出栈,表示创建数组的大小,创建数组完毕后,将数组的引用值压入操作数栈中 | |
anewarray | 两个,indexbyte1, indexbyte2 | 根据常量池中第 ((indexbyte1 << 8) | indexbyte2) 个(从 1 开始计数)常量索引指向的类确定创建数组的类型。然后将栈顶 int 类型的数值出栈,表示创建数组的大小,创建数组完毕后,将数组的引用值压入操作数栈中 |
getfield | 两个,indexbyte1, indexbyte2 | 根据常量池中第 ((indexbyte1 << 8) | indexbyte2) 个(从 1 开始计数)常量索引指向的字段确定具体的字段描述信息,然后将栈顶的对象引用数值出栈,根据对象引用和字段描述信息获取该字段的具体值,然后压入栈顶 |
putfield | 两个,indexbyte1, indexbyte2 | 根据常量池中第 ((indexbyte1 << 8) | indexbyte2) 个(从 1 开始计数)常量索引指向的字段确定具体的字段描述信息,然后将栈顶的数值 value 出栈,再将栈顶的对象引用 objectref 出栈,根据对象引用和字段描述信息获取该字段并将其设置为 value 值 |
getstatic | 两个,indexbyte1, indexbyte2 | 根据常量池中第 ((indexbyte1 << 8) | indexbyte2) 个(从 1 开始计数)常量索引指向的字段获取该字段的具体值,然后压入栈顶 |
putstatic | 两个,indexbyte1, indexbyte2 | 根据常量池中第 ((indexbyte1 << 8) | indexbyte2) 个(从 1 开始计数)常量索引指向的字段确定具体的字段描述信息,然后将栈顶的数值 value 出栈,根据字段描述信息获取该字段并将其设置为 value 值 |
baload | 无 | 将栈顶的 int 类型数值 index 出栈(表示数组的下标),再将栈顶的 boolean 或 byte 类型的数组引用类型 arrayref 出栈,根据 arrayref 和 index 定位到具体的数组元素,将其具体值压入栈顶 | |
caload | 无 | 将栈顶的 int 类型数值 index 出栈(表示数组的下标),再将栈顶的 char 类型的数组引用类型 arrayref 出栈,根据 arrayref 和 index 定位到具体的数组元素,将其具体值压入栈顶 | |
iaload | 无 | 将栈顶的 int 类型数值 index 出栈(表示数组的下标),再将栈顶的 int 类型的数组引用类型 arrayref 出栈,根据 arrayref 和 index 定位到具体的数组元素,将其具体值压入栈顶 | |
daload | 无 | 将栈顶的 int 类型数值 index 出栈(表示数组的下标),再将栈顶的 double 类型的数组引用类型 arrayref 出栈,根据 arrayref 和 index 定位到具体的数组元素,将其具体值压入栈顶 | |
aaload | 无 | 将栈顶的 int 类型数值 index 出栈(表示数组的下标),再将栈顶的对象类型的数组引用类型 arrayref 出栈,根据 arrayref 和 index 定位到具体的数组元素,将其具体值压入栈顶 | |
bastore | 无 | 将栈顶的 value 数值出栈,再将栈顶的 int 类型数值 index 出栈(表示数组的下标),再将栈顶的 boolean 或 byte 类型的数组引用类型 arrayref 出栈,根据 arrayref 和 index 定位到具体的数组元素,将该元素的值赋为 value 值 | |
castore | 无 | 将栈顶的 value 数值出栈,再将栈顶的 int 类型数值 index 出栈(表示数组的下标),再将栈顶的 char 类型的数组引用类型 arrayref 出栈,根据 arrayref 和 index 定位到具体的数组元素,将该元素的值赋为 value 值 | |
iastore | 无 | 将栈顶的 value 数值出栈,再将栈顶的 int 类型数值 index 出栈(表示数组的下标),再将栈顶的 int 类型的数组引用类型 arrayref 出栈,根据 arrayref 和 index 定位到具体的数组元素,将该元素的值赋为 value 值 | |
dastore | 无 | 将栈顶的 value 数值出栈,再将栈顶的 int 类型数值 index 出栈(表示数组的下标),再将栈顶的 double 类型的数组引用类型 arrayref 出栈,根据 arrayref 和 index 定位到具体的数组元素,将该元素的值赋为 value 值 | |
aastore | 无 | 将栈顶的 value 数值出栈,再将栈顶的 int 类型数值 index 出栈(表示数组的下标),再将栈顶的对象类型的数组引用类型 arrayref 出栈,根据 arrayref 和 index 定位到具体的数组元素,将该元素的值赋为 value 值 | |
arraylength | 无 | 将栈顶的数组引用值出栈,获取数组的长度,并将其压入栈顶 | |
instanceof | 两个,indexbyte1, indexbyte2 | 根据常量池中第 ((indexbyte1 << 8) | indexbyte2) 个(从 1 开始计数)常量索引指向的类确定具体的类描述信息 class,然后将栈顶元素 objectref 出栈,检查 objectref 是否是指定的类 class,如果是,则将 1 压入栈顶,否则将 0 压入栈顶 |
checkcast | 两个,indexbyte1, indexbyte2 | 根据常量池中第 ((indexbyte1 << 8) | indexbyte2) 个(从 1 开始计数)常量索引指向的类确定具体的类描述信息 class,然后将栈顶元素 objectref 出栈,检查 objectref 是否可以转换成指定的类 class,如果可以,则将 objectref 压入栈顶,否则抛出 ClassCastException 异常 |
操作数栈管理命令
助记符 | 操作数 | 含义 |
---|---|---|
pop | 无 | 将操作数栈顶元素出栈 |
pop2 | 无 | 将操作数栈顶两个元素出栈 |
dup | 无 | 将操作数栈顶元素进行复制,然后将复制值压入栈顶 |
dup2 | 无 | 将操作数栈顶两个元素进行复制,然后按顺序将复制值压入栈顶。具体操作过程如下:将操作数栈顶元素 value2 出栈,再将栈顶元素 value1 出栈,对 value1 进行复制得到 value1_1,对 value2 复制得到 value2_1,然后将 value1 入栈,再将 value2 入栈,再将 value1_1 入栈,再将 value2_1 入栈 |
dup_x1 | 无 | 复制操作数栈栈顶的值,并插入到栈顶以下 2 个值之后。具体操作过程如下:将栈顶元素 value2 出栈,复制 value2 得到 value2_1,再将栈顶 value1 出栈,然后将 value2_1 入栈,再将 value1 入栈,再将 value2 入栈 |
dup_x2 | 无 | 复制操作数栈栈顶的值,并插入到栈顶以下 2 个(次栈顶元素是 long 或 double 类型)或 3 个(次栈顶元素不是 long 或 double 类型)值之后。具体操作过程如下(以次栈顶元素不是 long 或 double 类型为例):将栈顶元素 value3 出栈,复制 value3 得到 value3_1,再将栈顶 value2 出栈,再将栈顶 value1 出栈,然后将 value3_1 入栈,再将 value1 入栈,再将 value2 入栈,再将 value3 入栈 |
dup2_x1 | 无 | dup_x1 的双倍版本,即复制两个栈顶元素 |
dup2_x2 | 无 | dup_x2 的双倍版本,即复制两个栈顶元素 |
swap | 无 | 将操作数栈顶的两个元素互换 |
控制转移命令
助记符 | 操作数 | 含义 | |||
---|---|---|---|---|---|
ifeq | 两个,branchbyte1, branchbyte2 | 将栈顶元素出栈,将这个值与 0 进行比较,如果等于 0,则跳转到 ((branchbyte1 << 8) | branchbyte2) 位置处执行命令,否则继续执行下一条命令 | ||
iflt | 两个,branchbyte1, branchbyte2 | 将栈顶元素出栈,将这个值与 0 进行比较,如果小于 0,则跳转到 ((branchbyte1 << 8) | branchbyte2) 位置处执行命令,否则继续执行下一条命令 | ||
ifne | 两个,branchbyte1, branchbyte2 | 将栈顶元素出栈,将这个值与 0 进行比较,如果不等于 0,则跳转到 ((branchbyte1 << 8) | branchbyte2) 位置处执行命令,否则继续执行下一条命令 | ||
ifge | 两个,branchbyte1, branchbyte2 | 将栈顶元素出栈,将这个值与 0 进行比较,如果大于或等于 0,则跳转到 ((branchbyte1 << 8) | branchbyte2) 位置处执行命令,否则继续执行下一条命令 | ||
ifgt | 两个,branchbyte1, branchbyte2 | 将栈顶元素出栈,将这个值与 0 进行比较,如果大于 0,则跳转到 ((branchbyte1 << 8) | branchbyte2) 位置处执行命令,否则继续执行下一条命令 | ||
ifle | 两个,branchbyte1, branchbyte2 | 将栈顶元素出栈,将这个值与 0 进行比较,如果小于或等于 0,则跳转到 ((branchbyte1 << 8) | branchbyte2) 位置处执行命令,否则继续执行下一条命令 | ||
ifnull | 两个,branchbyte1, branchbyte2 | 将栈顶元素出栈,判断这个值是否是 null,如果是 null, 则跳转到 ((branchbyte1 << 8) | branchbyte2) 位置处执行命令,否则继续执行下一条命令 | ||
ifnonnull | 两个,branchbyte1, branchbyte2 | 将栈顶元素出栈,判断这个值是否是 null,如果不是 null, 则跳转到 ((branchbyte1 << 8) | branchbyte2) 位置处执行命令,否则继续执行下一条命令 | ||
if_icmpeq | 两个,branchbyte1, branchbyte2 | 将栈顶元素 value2 出栈,再将栈顶元素 value1 出栈,如果 value1 == values, 则跳转到 ((branchbyte1 << 8) | branchbyte2) 位置处执行命令,否则继续执行下一条命令 | ||
if_icmpne | 两个,branchbyte1, branchbyte2 | 将栈顶元素 value2 出栈,再将栈顶元素 value1 出栈,如果 value1 != values, 则跳转到 ((branchbyte1 << 8) | branchbyte2) 位置处执行命令,否则继续执行下一条命令 | ||
if_icmplt | 两个,branchbyte1, branchbyte2 | 将栈顶元素 value2 出栈,再将栈顶元素 value1 出栈,如果 value1 < values, 则跳转到 ((branchbyte1 << 8) | branchbyte2) 位置处执行命令,否则继续执行下一条命令 | ||
if_icmpge | 两个,branchbyte1, branchbyte2 | 将栈顶元素 value2 出栈,再将栈顶元素 value1 出栈,如果 value1 >= values, 则跳转到 ((branchbyte1 << 8) | branchbyte2) 位置处执行命令,否则继续执行下一条命令 | ||
if_icmpgt | 两个,branchbyte1, branchbyte2 | 将栈顶元素 value2 出栈,再将栈顶元素 value1 出栈,如果 value1 > values, 则跳转到 ((branchbyte1 << 8) | branchbyte2) 位置处执行命令,否则继续执行下一条命令 | ||
if_icmple | 两个,branchbyte1, branchbyte2 | 将栈顶元素 value2 出栈,再将栈顶元素 value1 出栈,如果 value1 <= values, 则跳转到 ((branchbyte1 << 8) | branchbyte2) 位置处执行命令,否则继续执行下一条命令 | ||
if_acmpeq | 两个,branchbyte1, branchbyte2 | 将栈顶对象引用类型元素 value2 出栈,再将栈顶对象引用类型元素 value1 出栈,如果 value1 == values, 则跳转到 ((branchbyte1 << 8) | branchbyte2) 位置处执行命令,否则继续执行下一条命令 | ||
if_acmpne | 两个,branchbyte1, branchbyte2 | 将栈顶对象引用类型元素 value2 出栈,再将栈顶对象引用类型元素 value1 出栈,如果 value1 != values, 则跳转到 ((branchbyte1 << 8) | branchbyte2) 位置处执行命令,否则继续执行下一条命令 | ||
goto | 两个,branchbyte1, branchbyte2 | 无条件跳转到 ((branchbyte1 << 8) | branchbyte2) 位置处执行命令 | ||
goto_w | 四个,branchbyte1, branchbyte2, branchbyte3, branchbyte4 | 无条件跳转到 ((branchbyte1 << 24) | (branchbyte2 << 16) | (branchbyte3 << 8) | branchbyte4) 位置处执行命令 |
jsr | 两个,branchbyte1, branchbyte2 | 无条件跳转到 ((branchbyte1 << 8) | branchbyte2) 位置处执行命令,并将 jsr 的下一条命令地址压如栈顶 | ||
jsr_w | 四个,branchbyte1, branchbyte2, branchbyte3, branchbyte4 | 无条件跳转到 ((branchbyte1 << 24) | (branchbyte2 << 16) | (branchbyte3 << 8) | branchbyte4) 位置处执行命令,并将 jsr 的下一条命令地址压如栈顶 |
ret | 一个,index | 返回本地变量指定的 index 的命令位置(一般与 jsr 或 jsr_w 联合使用)。index是一个 0 ~ 255 之间的无符号数,它代表一个当前栈帧的局部变量表的索引值,在该索引位置应为一个returnAddress类型的局部变量,命令执行后,将该局部变量的值更新到Java虚拟机的 PC 寄存器中,令进程从修改后的位置继续执行 |
方法调用命令
助记符 | 操作数 | 含义 | |
---|---|---|---|
invokevirtual | 两个,indexbyte1, indexbyte2 | 调用实例方法。具体操作是:取出常量池中第 ((indexbyte1 << 8) | indexbyte2) 个(从 1 开始计数)常量的索引对应的方法描述符,依次弹出操作数栈中的函数个数,再将栈顶的实例引用弹出,根据这些信息调用具体实例的函数 |
invokespecial | 两个,indexbyte1, indexbyte2 | 调用超类的构造方法,实例初始化方法,私有方法。具体操作是:取出常量池中第 ((indexbyte1 << 8) | indexbyte2) 个(从 1 开始计数)常量的索引对应的方法描述符,依次弹出操作数栈中的函数个数,再将栈顶的实例引用弹出,根据这些信息调用具体函数 |
invokestatic | 两个,indexbyte1, indexbyte2 | 调用静态方法。具体操作是:取出常量池中第 ((indexbyte1 << 8) | indexbyte2) 个(从 1 开始计数)常量的索引对应的方法描述符,依次弹出操作数栈中的函数个数,根据这些信息调用具体函数 |
invokeinterface | 四个,indexbyte1, indexbyte2, count, 0(第四个操作数恒为 0) | 调用接口方法。具体操作是:取出常量池中第 ((indexbyte1 << 8) | indexbyte2) 个(从 1 开始计数)常量的索引对应的方法描述符,根据操作数 count 依次弹出操作数栈中的函数个数,根据这些信息调用具体函数 |
invokedynamic | 四个,indexbyte1, indexbyte2, 0, 0(第三个和第四个操作数恒为 0) | 调用动态方法 |
返回命令
助记符 | 操作数 | 含义 |
---|---|---|
ireturn | 无 | 从当前方法返回 int 类型数值,可用于函数返回类型是 boolean、byte、char、short、int 的函数返回命令中 |
lreturn | 无 | 从当前方法返回 long 类型数值 |
freturn | 无 | 从当前方法返回 float 类型数值 |
dreturn | 无 | 从当前方法返回 double 类型数值 |
areturn | 无 | 从当前方法返回对象引用 |
return | 无 | 从当前方法返回 void |
异常处理命令
助记符 | 操作数 | 含义 |
---|---|---|
athrow | 无 | 将栈顶的异常抛出 |
同步命令
助记符 | 操作数 | 含义 |
---|---|---|
monitorenter | 无 | 将栈顶的引用对象出栈,获取该对象的锁,用于进入同步块 |
monitorexit | 无 | 将栈顶的引用对象出栈,释放该对象的锁,用于退出同步块 |
invokeinterface 这个是比较难的一个指令 最后一个字节(操作数第四个字节必须为0 ??)
Operation
Invoke interface method
Format
invokeinterface
indexbyte1
indexbyte2
count
0
Forms
invokeinterface = 185 (0xb9)
Operand Stack
..., objectref, [arg1, [arg2 ...]] →
...
Description
The unsigned indexbyte1 and indexbyte2 are used to construct an index into the run-time constant pool of the current class (§2.6), where the value of the index is (indexbyte1 <<
8) | indexbyte2. The run-time constant pool item at that index must be a symbolic reference to an interface method (§5.1), which gives the name and descriptor (§4.3.3) of the interface method as well as a symbolic reference to the interface in which the interface method is to be found. The named interface method is resolved (§5.4.3.4).
The resolved interface method must not be an instance initialization method, or the class or interface initialization method (§2.9).
The count operand is an unsigned byte that must not be zero. The objectref must be of type reference
and must be followed on the operand stack by nargs argument values, where the number, type, and order of the values must be consistent with the descriptor of the resolved interface method. The value of the fourth operand byte must always be zero.
Let C be the class of objectref. The actual method to be invoked is selected by the following lookup procedure:
If C contains a declaration for an instance method with the same name and descriptor as the resolved method, then it is the method to be invoked.
Otherwise, if C has a superclass, a search for a declaration of an instance method with the same name and descriptor as the resolved method is performed, starting with the direct superclass of C and continuing with the direct superclass of that class, and so forth, until a match is found or no further superclasses exist. If a match is found, then it is the method to be invoked.
Otherwise, if there is exactly one maximally-specific method (§5.4.3.3) in the superinterfaces of C that matches the resolved method's name and descriptor and is not
abstract
, then it is the method to be invoked.
If the method is synchronized
, the monitor associated with objectref is entered or reentered as if by execution of a monitorenter instruction (§monitorenter) in the current thread.
If the method is not native
, the nargs argument values and objectref are popped from the operand stack. A new frame is created on the Java Virtual Machine stack for the method being invoked. The objectref and the argument values are consecutively made the values of local variables of the new frame, with objectref in local variable 0, arg1 in local variable 1 (or, if arg1 is of type long
or double
, in local variables 1 and 2), and so on. Any argument value that is of a floating-point type undergoes value set conversion (§2.8.3) prior to being stored in a local variable. The new frame is then made current, and the Java Virtual Machine pc
is set to the opcode of the first instruction of the method to be invoked. Execution continues with the first instruction of the method.
If the method is native
and the platform-dependent code that implements it has not yet been bound (§5.6) into the Java Virtual Machine, that is done. The nargs argument values and objectref are popped from the operand stack and are passed as parameters to the code that implements the method. Any argument value that is of a floating-point type undergoes value set conversion (§2.8.3) prior to being passed as a parameter. The parameters are passed and the code is invoked in an implementation-dependent manner. When the platform-dependent code returns:
If the
native
method issynchronized
, the monitor associated with objectref is updated and possibly exited as if by execution of a monitorexit instruction (§monitorexit) in the current thread.If the
native
method returns a value, the return value of the platform-dependent code is converted in an implementation-dependent way to the return type of thenative
method and pushed onto the operand stack.
Linking Exceptions
During resolution of the symbolic reference to the interface method, any of the exceptions pertaining to interface method resolution (§5.4.3.4) can be thrown.
Otherwise, if the resolved method is static
or private
, the invokeinterface instruction throws an IncompatibleClassChangeError
.
Run-time Exceptions
Otherwise, if objectref is null
, the invokeinterface instruction throws a NullPointerException
.
Otherwise, if the class of objectref does not implement the resolved interface, invokeinterface throws an IncompatibleClassChangeError
.
Otherwise, if step 1 or step 2 of the lookup procedure selects a method that is not public
, invokeinterface throws an IllegalAccessError
.
Otherwise, if step 1 or step 2 of the lookup procedure selects an abstract
method, invokeinterface throws an AbstractMethodError
.
Otherwise, if step 1 or step 2 of the lookup procedure selects a native
method and the code that implements the method cannot be bound, invokeinterface throws an UnsatisfiedLinkError
.
Otherwise, if step 3 of the lookup procedure determines there are multiple maximally-specific methods in the superinterfaces of C that match the resolved method's name and descriptor and are not abstract
, invokeinterface throws an IncompatibleClassChangeError
Otherwise, if step 3 of the lookup procedure determines there are zero maximally-specific methods in the superinterfaces of C that match the resolved method's name and descriptor and are not abstract
, invokeinterface throws an AbstractMethodError
.
Notes
The count operand of the invokeinterface instruction records a measure of the number of argument values, where an argument value of type long
or type double
contributes two units to the count value and an argument of any other type contributes one unit. This information can also be derived from the descriptor of the selected method. The redundancy is historical.
The fourth operand byte exists to reserve space for an additional operand used in certain of Oracle's Java Virtual Machine implementations, which replace the invokeinterface instruction by a specialized pseudo-instruction at run time. It must be retained for backwards compatibility.
The nargs argument values and objectref are not one-to-one with the first nargs+1 local variables. Argument values of types long
and double
must be stored in two consecutive local variables, thus more than nargs local variables may be required to pass nargs argument values to the invoked method.
The selection logic allows a non-abstract
method declared in a superinterface to be selected. Methods in interfaces are only considered if there is no matching method in the class hierarchy. In the event that there are two non-abstract
methods in the superinterface hierarchy, with neither more specific than the other, an error occurs; there is no attempt to disambiguate (for example, one may be the referenced method and one may be unrelated, but we do not prefer the referenced method). On the other hand, if there are many abstract
methods but only one non-abstract
method, the non-abstract
method is selected (unless an abstract
method is more specific).
备注
count记录了参数的个数,一个long或者double类型的参数记2,其余的参数记1,这个信息也可以从解析出的方法描述符中得到,放在这里是由于历史原因。
第四个运算字节(必须为0的)的存在是为额外的运算元预留空间,这些运算元用于某些oracle的java虚拟机,用来在运行时用某些专用的伪指令替换invokeinterface指令。因此必须保留以达到后项兼容性。
nargs个参数并不一定就对应nargs个局部变量,对于long或double类型的参数值就需要连续两个局部变量的位置来存储,这时,nargs个参数的传递就需要多于nargs个的局部变量位置。
在2006年的JavaOne大会上,Sun公司宣布最终会把Java开源,并在随后的一年,陆续将JDK的各个部分(其中当然也包括了HotSpot VM)在GPL协议下公开了源码, 并在此基础上建立了OpenJDK。这样,HotSpot VM便成为了Sun JDK和OpenJDK两个实现极度接近的JDK项目的共同虚拟机。
在2008年和2009年,Oracle公司分别收购了BEA公司和Sun公司,这样Oracle就同时拥有了两款优秀的Java虚拟机:JRockit VM和HotSpot VM。 Oracle公司宣布在不久的将来(大约应在发布JDK 8的时候)会完成这两款虚拟机的整合工作,使之优势互补。 整合的方式大致上是在HotSpot的基础上,移植JRockit的优秀特性,譬如使用JRockit的垃圾回收器与MissionControl服务, 使用HotSpot的JIT编译器与混合的运行时系统。
JDK中rt.jar、tools.jar和dt.jar作用
dt.jar和tools.jar位于:{Java_Home}/lib/下,而rt.jar位于:{Java_Home}/jre/lib/下,其中:
rt.jar是JAVA基础类库,也就是你在java doc里面看到的所有的类的class文件
dt.jar是关于运行环境的类库
tools.jar是工具类库,编译和运行需要的都是toos.jar里面的类分别是sun.tools.java.; sun.tols.javac.;
在Classpath设置这几个变量,是为了方便在程序中 import;Web系统都用到tool.jar。
1. rt.jar
rt.jar 默认就在Root Classloader的加载路径里面的,而在Claspath配置该变量是不需要的;同时jre/lib目录下的
其他jar:jce.jar、jsse.jar、charsets.jar、resources.jar都在Root Classloader中
2. tools.jar
tools.jar 是系统用来编译一个类的时候用到的,即执行javac的时候用到
javac XXX.java
实际上就是运行
java -Calsspath=%JAVA_HOME%\lib\tools.jar xx.xxx.Main XXX.java
javac就是对上面命令的封装 所以tools.jar 也不用加到classpath里面
3. dt.jar
dt.jar是关于运行环境的类库,主要是swing的包 在用到swing时最好加上。