恢复元气,最后一天学JVM!学了五天JVM了,不打算学的太深,这几天收获也很多,对很多底层原理有了那么一点了解,以后肯定还会继续加深JVM的学习理解的,暂时先到此为止,接下来是为期一个星期的JUC并发编程学习。
虚拟机+字节码实现了java的平台无关性和语言无关性。
java程序不需要考虑运行在什么操作系统上,JVM也并非只能运行java代码,虚拟机只关心*.class字节码文件,能生成字节码文件的不只有java,还有Jruby、Groovy、Kotlin等。
一个Class文件唯一地对应着一个类或者接口的定义信息。
根据JVM规范,Class文件结构如下:
ClassFile {
u4 magic;
u2 minor_ version;
u2 major_ version;
u2 constant_ pool_ count ;
cp_info constant_ pool[constant_ pool_ count-1];
u2 access_ flags;
u2 this_ class;
u2 super_ class;
u2 interfaces_ count;
u2 interfaces[interfaces_ count];
u2 fields_ count ;
field_info fields[fields_ count];
u2 methods_ count;
method_ info methods [methods_ count ] ;
u2 attributes_ count;
attribute_ info attributes[attributes_ count];
}
Class文件有两种数据类型:
1、无符号数,u1、u2、u4、u8分别代表1个字节、2个字节、4个字节、8个字节。
2、表,由多个无符号数和其他表构成的复合数据类型,命名一般以"_info"结尾,整个Class文件本质上也是一个表。
比如以下java代码。
public class test {
public static void main(String[] args) {
System.out.println("hello");
}
}
编译后的字节码文件对应如下。(注意这是16进制的)
cafe babe 0000 0037 0022 0a00 0600 1409
0015 0016 0800 170a 0018 0019 0700 1a07
001b 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 124c 6f63
616c 5661 7269 6162 6c65 5461 626c 6501
0004 7468 6973 0100 064c 7465 7374 3b01
0004 6d61 696e 0100 1628 5b4c 6a61 7661
2f6c 616e 672f 5374 7269 6e67 3b29 5601
0004 6172 6773 0100 135b 4c6a 6176 612f
6c61 6e67 2f53 7472 696e 673b 0100 0a53
6f75 7263 6546 696c 6501 0009 7465 7374
2e6a 6176 610c 0007 0008 0700 1c0c 001d
001e 0100 0568 656c 6c6f 0700 1f0c 0020
0021 0100 0474 6573 7401 0010 6a61 7661
2f6c 616e 672f 4f62 6a65 6374 0100 106a
6176 612f 6c61 6e67 2f53 7973 7465 6d01
0003 6f75 7401 0015 4c6a 6176 612f 696f
2f50 7269 6e74 5374 7265 616d 3b01 0013
6a61 7661 2f69 6f2f 5072 696e 7453 7472
6561 6d01 0007 7072 696e 746c 6e01 0015
284c 6a61 7661 2f6c 616e 672f 5374 7269
6e67 3b29 5600 2100 0500 0600 0000 0000
0200 0100 0700 0800 0100 0900 0000 2f00
0100 0100 0000 052a b700 01b1 0000 0002
000a 0000 0006 0001 0000 0001 000b 0000
000c 0001 0000 0005 000c 000d 0000 0009
000e 000f 0001 0009 0000 0037 0002 0001
0000 0009 b200 0212 03b6 0004 b100 0000
0200 0a00 0000 0a00 0200 0000 0300 0800
0400 0b00 0000 0c00 0100 0000 0900 1000
1100 0000 0100 1200 0000 0200 13
按照JVM规范,首先是u4 magic,一个字节8位,一个16进制的字符是4位,那么前四个字节就是ca fe ba be,这头四个字节就是魔数,代表这是个java的Class文件。
后面的0000 0037,前面的0000代表次版本号,后面0037代表高版本号。0x0037转成十进制为55,对应表格中的java SE 11
Java字节码文件版本号 | JDK版本 | 产品版本号 |
---|---|---|
· | 1.0.x | Java 1.0.x |
45 | 1.1.x | Java 1.1.x |
46 | 1.2.x | Java 1.2.x |
47 | 1.3.x | Java 1.3.x |
48 | 1.4.x | Java Java 2 Platform, Standard Edition (J2SE) 1.4.x |
49 | 5.x | Java 2 Platform, Standard Edition (J2SE) 5.0 |
50 | 6.x | Java 2 Platform, Standard Edition (J2SE) 6.0 |
51 | 7.x | Java SE 7 |
52 | 8.x | Java SE 8 |
53 | 8.x | Java SE 9 |
54 | 8.x | Java SE 10 |
55 | 8.x | Java SE 11 |
56 | 8.x | Java SE 12 |
57 | 8.x | Java SE 13 |
58 | 8.x | Java SE 14 |
59 | 8.x | Java SE 15 |
60 | 8.x | Java SE 16 |
61 | 8.x | Java SE 17 |
62 | 8.x | Java SE 18 |
紧接着版本号后面就是常量池入口,这是第一个出现的表类型的数据项目,也是class文件中关联最多、占用空间最大的数据项之一。
下面是常量池类型与十进制的对应关系:
在0037的版本号之后,首先是0x0022(34),代表常量池有#1—#33项,#0不计入。
接下来0x0a(10),查表发现10对应是CONSTANT_Methodref,是一个方法引用,那接下来四个字节0x0006和0x0014代表引用了常量池中的对应项获得方法的类名和方法名。
字节码文件就是这样看的,大致了解这些即可,不要太钻细节。
那么,常量池中主要存放的是两种类型的常量:
1、字面量:文本字符串、final常量值等
2、符号引用:类和接口的全限定名、字段的名称和描述符、方法名称和描述符、方法句柄和方法类型等。
常量池结束后的2个字节,代表访问标志,描述了这是类还是接口;是否为public;是否是abstract等。
下表可查到访问标志对应的十六进制。
主要确认该类型的继承关系。类索引、父类索引都是一个u2的数据类型,接口索引是u2类型的集合。(单继承、多接口)
字段表(field_info),描述类或接口声明的变量。
包含方法的作用域、是否是static等等,与字段表类似。
为了方便读取信息,可以用javap反编译查看class文件。
java -verbose -p test.class
可以看到详细信息:
6:minor version: 0 //次版本号7:major version: 61 //主版本号
9: this_class: #8 // test 类索引
10: super_class: #2 // Omy 父类索引
12:Constant pool: 常量池
65—80:字段表属性
82—121:方法表属性
Classfile /C:/Software/something/learning/1web/project/database-test/JVM/src/test.class
Last modified 2024年1月31日; size 943 bytes
SHA-256 checksum b4966d64ed488e664920e7cee11605fde28faa7df79daf01d1c41a48d7022fbb
Compiled from "test.java"
public class test extends Omy
minor version: 0
major version: 61
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #8 // test
super_class: #2 // Omy
interfaces: 0, fields: 4, methods: 2, attributes: 3
Constant pool:
#1 = Methodref #2.#3 // Omy."":()V
#2 = Class #4 // Omy
#3 = NameAndType #5:#6 // "":()V
#4 = Utf8 Omy
#5 = Utf8
#6 = Utf8 ()V
#7 = Fieldref #8.#9 // test.age:I
#8 = Class #10 // test
#9 = NameAndType #11:#12 // age:I
#10 = Utf8 test
#11 = Utf8 age
#12 = Utf8 I
#13 = Fieldref #8.#14 // test.name:Ljava/lang/String;
#14 = NameAndType #15:#16 // name:Ljava/lang/String;
#15 = Utf8 name
#16 = Utf8 Ljava/lang/String;
#17 = Fieldref #8.#18 // test.card:Ljava/lang/String;
#18 = NameAndType #19:#16 // card:Ljava/lang/String;
#19 = Utf8 card
#20 = InvokeDynamic #0:#21 // #0:makeConcatWithConstants:(ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#21 = NameAndType #22:#23 // makeConcatWithConstants:(ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#22 = Utf8 makeConcatWithConstants
#23 = Utf8 (ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#24 = Utf8 sm
#25 = Utf8 ConstantValue
#26 = Integer 18
#27 = Utf8 (ILjava/lang/String;Ljava/lang/String;)V
#28 = Utf8 Code
#29 = Utf8 LineNumberTable
#30 = Utf8 getInfo
#31 = Utf8 ()Ljava/lang/String;
#32 = Utf8 SourceFile
#33 = Utf8 test.java
#34 = Utf8 BootstrapMethods
#35 = MethodHandle 6:#36 // REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Lja
va/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
#36 = Methodref #37.#38 // java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/Me
thodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
#37 = Class #39 // java/lang/invoke/StringConcatFactory
#38 = NameAndType #22:#40 // makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lan
g/Object;)Ljava/lang/invoke/CallSite;
#39 = Utf8 java/lang/invoke/StringConcatFactory
#40 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
#41 = String #42 // \u0001 \u0001 \u0001
#42 = Utf8 \u0001 \u0001 \u0001
#43 = Utf8 InnerClasses
#44 = Class #45 // java/lang/invoke/MethodHandles$Lookup
#45 = Utf8 java/lang/invoke/MethodHandles$Lookup
#46 = Class #47 // java/lang/invoke/MethodHandles
#47 = Utf8 java/lang/invoke/MethodHandles
#48 = Utf8 Lookup
{
private static final int sm;
descriptor: I
flags: (0x001a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL
ConstantValue: int 18
private int age;
descriptor: I
flags: (0x0002) ACC_PRIVATE
private java.lang.String name;
descriptor: Ljava/lang/String;
flags: (0x0002) ACC_PRIVATE
public java.lang.String card;
descriptor: Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
public test(int, java.lang.String, java.lang.String);
descriptor: (ILjava/lang/String;Ljava/lang/String;)V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=4, args_size=4
0: aload_0
1: invokespecial #1 // Method Omy."":()V
4: aload_0
5: iload_1
6: putfield #7 // Field age:I
9: aload_0
10: aload_2
11: putfield #13 // Field name:Ljava/lang/String;
14: aload_0
15: aload_3
16: putfield #17 // Field card:Ljava/lang/String;
19: return
LineNumberTable:
line 8: 0
line 9: 4
line 10: 9
line 11: 14
line 12: 19
public java.lang.String getInfo();
descriptor: ()Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: getfield #7 // Field age:I
4: aload_0
5: getfield #13 // Field name:Ljava/lang/String;
8: aload_0
9: getfield #17 // Field card:Ljava/lang/String;
12: invokedynamic #20, 0 // InvokeDynamic #0:makeConcatWithConstants:(ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;
17: areturn
LineNumberTable:
line 15: 0
}
SourceFile: "test.java"
BootstrapMethods:
0: #35 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang
/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
Method arguments:
#41 \u0001 \u0001 \u0001
InnerClasses:
public static final #48= #44 of #46; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
javac会对我们写的代码进行优化,减轻我们的工作量。举几个例子:
编译器会给加上了默认的无参构造器。
public class test{
public static void main(String[] args) {
}
}
public class test {
public test() {
}
public static void main(String[] args) {
}
}
JDK5开始有的这个特性。
public class test{
public static void main(String[] args) {
Integer a = 10;
int b = a;
}
}
反编译一下:
可以看到调用了valueOf()和intValue()方法
import java.util.ArrayList;
import java.util.List;
public class test{
public static void main(String[] args) {
List list = new ArrayList<>();
list.add(10);
list.add(12);
Integer t = list.get(1);
}
}
调用add方法时,实际还是加入Object类型的数据
get也是
我们的泛型信息被保存在局部变量类型表里
1、通过类的全限定名获取类的二进制字节流
2、将字节流代表的静态存储结构转化为方法去运行时的数据结构。
3、在内存中生成一个代表这个类的Class对象,作为方法去该类的数据访问入口。
连接的第一步,检查是不是Class字节流,并且确保字节流符合要求、安全。
为类的static变量分配内存,并初始化为默认值。
只会给类的变量分配内存,不会给实例变量分配。
将常量池的符号引用替换为直接引用,就是把虚拟的指向换成了内存中的真正地址了。
比如之前看到Constant Pool里的#2 #3之类的,这就是符号引用。
为类的static变量赋予正确的初始值(准备阶段是赋默认值)。
java有两种方式设置初始值。
public static int value = 123
public static int value;
static{
value = 123;
}
类初始化的时机:只有主动使用类的时候,类才会初始化。
初始化的情况例如调用类的静态方法、访问类的静态变量、new关键字创建类的实例、Class.forName获取类。
不会初始化的情况,比如获取类的static变量。
类加载器有一定层级关系,以JDK8为例
一个类加载器收到类加载请求,首先不会尝试去加载,而是将请求委派给父类加载器去完成,因此所有的加载请求都会传送到最顶层,只有父类加载器无法加载时,子加载器才会去完成加载,这就是双亲委派模型