1. 字节码文件
源代码经过编译器编译之后便会生成一个字节码文件,字节码是一种二进制的类文件,它的内容是JVM
指令,而不像C
、C++
经由编译器直接生成机器码。
2. 字节码指令
Java
虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码以及跟随其后的零至多个代表此操作所需参数的操作数所构成。虚拟机中许多指令并不包含操作数,只有一个操作码。
3. 包装类缓存对象
@Test
public void test7() {
Integer i1 = 10;
Integer i2 = 10;
System.out.println(i1 == i2);// true
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4);// false
Boolean b1 = true;
Boolean b2 = true;
System.out.println(b1 == b2);// true
Integer x = 128;
int y = 128;
System.out.println(x == y);//true
}
对于
Integer
包装类值i1
,i2
,i3
,i4
查看字节码可以看到都调用了valueOf
方法,查看valueOf
方法源码可知,其所允许的缓存值为-128~127,因此i1
和i2
指向同一个地址,i1==i2
结果为true
,i3
和i4
都是经new
实例化出来的,i3==i4
结果为false
,Boolean
类同样使用了valueOf
方法,此方法返回的true
和false
是一个常量,所以b1 == b2
结果为true
。包装类型x
和基本类型y
比较,查看字节码可知执行了intValue
方法,进行了拆箱,两个值相等,故而输出true
。Integer
类valueOf
方法及其调用值源码如下:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
Boolean
类valueOf
方法及其调用值源码如下:
public static Boolean valueOf(String s) {
return parseBoolean(s) ? TRUE : FALSE;
}
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
4. 字节码文件结构组成
- 魔数
Magic Number
(魔数):字节码文件的标志。每个字节码文件开头的4个字节的无符号整数称为魔数; 它的唯一作用是确定这个文件是否为一个能被虚拟机接受的有效合法的字节码文件。即:魔数是字节码文件的标识符;魔数值固定为0xCAFEBABE
,不会更改;使用魔数而不是扩展名来进行识别主要是基于安全方面的考虑,因为文件扩展名可以随意地改动。
Class
文件版本
字节码文件版本号,占用4个字节。其中第5和第6个字节代表编译的副版本号
minor_version
,第7和第8个字节代表编译的主版本号major_version
。不同版本的Java
编译器编译的Class
文件对应的版本是不一样的。高版本的Java
虚拟机可以执行由低版本编译器生成的字节码文件,但是低版本的Java
虚拟机不能执行由高版本编译器生成的字节码文件。否则JVM
会抛出java.lang.UnsupportedClassVersionError
异常。在实际开发中,需注意生产环境和开发环境的JDK
版本是否一致。
- 常量池
常量池是
Class
文件中内容最为丰富的区域之一。常量池对于Class
文件中的字段和方法解析也有着至关重要的作用;常量池是Class文件之中的资源仓库,它是Class
文件结构中与其他项目关联最多的数据类型,也是占用Class
文件空间最大的数据项目之一;常量池表中,用于存放编译时期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
- 访问标识(或标志)
访问标记使用两个字节表示,用于识别类或者接口层次的访问信息,比如
Class
是类还是接口、是否定义为public
类型、是否定义为abstract
类型、如果是类是否声明为fianl
等。
- 类索引,父类索引,接口索引集合
指定该类的类别、父类类别以及实现的接口。其中类索引确定这个类的全限定名,父类索引用于确定这个类的父类全限定名。
- 字段表集合
用于描述接口或类中声明的变量。字段(
field
)包括类级变量以及实例级变量,但是不包括方法内部、代码块内部声明的局部变量。(local variables
)字段叫什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。它指向常量池索引集合,它描述了每个字段的完整信息。比如字段的标识符、访问修饰符(public
、private
或protected
)、是类变量还是实例变量(static
修饰符)、是否是常量(final
修饰符)等。
- 方法表集合
指向常量池索引集合,它完整描述了每个方法的签名。
- 属性表集合
class文件所携带的辅助信息
5. 字节码指令
- 加载和存储指令
加载和存储指令用于将数据从栈帧的局部变量表和操作数栈之间来回传递。
- 算术指令
用于对两个操作数栈上的值进行某种特定运算,并把结果重新压入操作数栈。
- 类型转换指令
将两种不同的数值类型进行相互转换。
- 对象的创建与访问指令
指令专门用于对象操作,可进一步细分为创建指令、字段访问指令、数组操作指令、类型检查指令。
- 方法调用与返回指令
- 操作数栈管理指令
JVM提供的操作数栈管理指令,可以用于直接操作操作数栈的指令。
- 控制转移指令
程序流程离不开条件控制,为了支持条件跳转,虚拟机提供了大量字节码指令,大体上可以分为 比较指令、条件跳转指令、比较条件跳转指令、多条件分支跳转指令、无条件跳转指令等。
- 异常处理指令
- 同步控制指令
方法级的同步、方法内指定指令序列的同步
6. Java虚拟机数据类型
Java
虚拟机通过数据类型来执行计算,数据类型可分为基本数据类型和引用数据类型,基本数据类型的变量持有原始值,引用类型的变量持有引用值。Java语言中的所有基本数据类型也是Java
虚拟机中的基本数据类型,其中Java
虚拟机中把boolean
值看成基本数据类型,由于指令集对boolean
只有很有限的支持,当编译器把Java
源代码编译为字节码时,会用int
和byte
表示boolean
。在Java
虚拟机中,false
即整数0,true
即所有非零整数。boolean
数组是当byte
数组来访问的。Java
虚拟机还有一个只在内部使用的基本类型:returnAddress
,Java
程序员不能使用这个类型,这个基本类型被用来实现Java
程序中的finally
子句。returnAddress
类型不是简单意义上的数值,不属于任何一种基本类型,并且它的值是不能被运行中的程序所修改的。Java
虚拟机的引用类型被统称为“引用”,有三种引用类型:类类型、接口类型、以及数组类型,它们的值都是对动态创建对象的引用。类类型的值是对类实例的引用;数组类型的值是对数组对象的引用,在Java
虚拟机中,数组是个真正的对象;而接口类型的值,则是对实现了该接口的某个类实例的引用。还有一种特殊的引用值是null,它表示该引用变量没有引用任何对象。
- 基本类型为何不放在堆中
堆比栈要大,但是栈比堆的运算速度要快。将复杂数据类型放在堆中的目的是为了不影响栈的效率,而是通过引用的方式去堆中查找。(八大基本类型的大小创建时候已经确立大小。三大引用类型创建时候无法确定大小)简单数据类型比较稳定,并且它只占据很小的内存,将它放在空间小、运算速度快的栈中,能够提高效率。