赞
踩
恢复元气,最后一天学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."<init>":()V
- #2 = Class #4 // Omy
- #3 = NameAndType #5:#6 // "<init>":()V
- #4 = Utf8 Omy
- #5 = Utf8 <init>
- #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."<init>":()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<Integer> 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为例
一个类加载器收到类加载请求,首先不会尝试去加载,而是将请求委派给父类加载器去完成,因此所有的加载请求都会传送到最顶层,只有父类加载器无法加载时,子加载器才会去完成加载,这就是双亲委派模型
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。