快速导航
1、类文件结构
2、字节码指令
3、编译器处理
4、类加载阶段
5、类加载器
6、运行期优化
1、类文件结构
先编写一个Hello World
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
我们执行Javac HelloWorld.java,可以得到HelloWorld.class如下
cafe babe 0000 0034 001d 0a00 0600 0f09
0010 0011 0800 120a 0013 0014 0700 1507
0016 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 046d 6169
6e01 0016 285b 4c6a 6176 612f 6c61 6e67
2f53 7472 696e 673b 2956 0100 0a53 6f75
7263 6546 696c 6501 000f 4865 6c6c 6f57
6f72 6c64 2e6a 6176 610c 0007 0008 0700
170c 0018 0019 0100 0b48 656c 6c6f 2057
6f72 6c64 0700 1a0c 001b 001c 0100 3763
6f6d 2f7a 616e 676c 696b 756e 2f73 7072
696e 6764 6174 6172 6564 6973 6465 6d6f
2f63 6f6e 7472 6f6c 6c65 722f 4865 6c6c
6f57 6f72 6c64 0100 106a 6176 612f 6c61
6e67 2f4f 626a 6563 7401 0010 6a61 7661
2f6c 616e 672f 5379 7374 656d 0100 036f
7574 0100 154c 6a61 7661 2f69 6f2f 5072
696e 7453 7472 6561 6d3b 0100 136a 6176
612f 696f 2f50 7269 6e74 5374 7265 616d
0100 0770 7269 6e74 6c6e 0100 1528 4c6a
6176 612f 6c61 6e67 2f53 7472 696e 673b
2956 0021 0005 0006 0000 0000 0002 0001
0007 0008 0001 0009 0000 001d 0001 0001
0000 0005 2ab7 0001 b100 0000 0100 0a00
0000 0600 0100 0000 0900 0900 0b00 0c00
0100 0900 0000 2500 0200 0100 0000 09b2
0002 1203 b600 04b1 0000 0001 000a 0000
000a 0002 0000 000b 0008 000c 0001 000d
0000 0002 000e
我们通过编译javap -verbose HelloWorld.class
zanglikun@zanglikundeMacBook-Pro controller % javap -verbose HelloWorld.class
Classfile /Users/zanglikun/IdeaProjects/spring-data-redis-demo/src/main/java/com/zanglikun/springdataredisdemo/controller/HelloWorld.class
Last modified 2022-10-31; size 470 bytes
MD5 checksum 1a3912ad68e7d17694aaa298bb02fe43
Compiled from "HelloWorld.java"
public class com.zanglikun.springdataredisdemo.controller.HelloWorld
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#15 // java/lang/Object."<init>":()V
#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #18 // Hello World
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #21 // com/zanglikun/springdataredisdemo/controller/HelloWorld
#6 = Class #22 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 SourceFile
#14 = Utf8 HelloWorld.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = Class #23 // java/lang/System
#17 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#18 = Utf8 Hello World
#19 = Class #26 // java/io/PrintStream
#20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#21 = Utf8 com/zanglikun/springdataredisdemo/controller/HelloWorld
#22 = Utf8 java/lang/Object
#23 = Utf8 java/lang/System
#24 = Utf8 out
#25 = Utf8 Ljava/io/PrintStream;
#26 = Utf8 java/io/PrintStream
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/String;)V
{
public com.zanglikun.springdataredisdemo.controller.HelloWorld();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 9: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello World
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 11: 0
line 12: 8
}
SourceFile: "HelloWorld.java"
JVM 类文件结构如下:
class-file {
u4 magic; // 魔术数
u2 minor_version; // 小版本号
u2 major_version; // 大版本号
u2 constant_pool_count; // 常量池中常量个数+1
cp_info constant_pool[constant_pool_count-1]; // 常量池
u2 access_flags; // 类的访问控制符标识(public,static,final,abstract等)
u2 this_class; // 该类的描述(值为对常量池的引用,引用的值为CONSTANT_Class_info)
u2 super_class; // 父类的描述(值为对常量池的引用,引用的值为CONSTANT_Class_info)
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; // 属性数,包括InnerClasses,EnclosingMethod,SourceFile等
attribute_info attributes[attributes_count]; // 属性表集合
}
类加载器
JDK8主要有3个类加载器
- Bootstrap ClassLoader 加载JAVA_HOME/jre/lib C++实现,无法访问,null
- Extension ClassLoader 加载JAVA_HOME/jre/lib/ext
- Applicaiton ClassLoader 加载classpath
一个类的加载顺序是从Bootstrap先加载,如果Bootstrap没加载则由Extension去加载,如果Extension没有加载,就有Applicaiton去加载。如果还没有,就会出现ClassNotFoundException
不同类加载器加载的内容,不可相互访问。
双亲委派模式
先了解一下双亲委派流程
双亲委派类加载的规则
先判断类有没有被当前类加载器加载过,如果没有被加载,就会去调用上级类加载器去执行加载任务,直到上级是Bootstrap ClassLoader,尝试加载类,如果加载失败,就会由下级尝试去加载,知道加载成功,如果还不成功,最后一次加载,就会抛出ClassNotFoundException!
本案例
请求上级类加载器Applicaiton类加载器去加载,如果没有被Applicaiton加载器加载就会去让Extension类加载去去加载,判断类有没有被Extension类加载器加载过,如果没有被加载,就会去找Bootstrap类加载去去加载,如果没有被加载,就回去使用上级类加载器Bootstrap类加载去加载,如果没有被加载,就会去尝试加载,如果加载失败会走异常,然后返回给下级Extension类加载去,让其自己加载。如果加载失败,就会去走Applicaiton类加载器去加载,知道最后一个类加载器没有找到,就会真正抛出ClassNotFoundException!
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//看,这里有锁
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//去看看类是否被加载过,如果被加载过,就立即返回
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//这里通过是否有parent来区分启动类加载器和其他2个类加载器
if (parent != null) {
//先尝试请求父类加载器去加载类,父类加载器加载不到,再去尝试自己加载类
c = parent.loadClass(name, false);
} else {
//启动类加载器加载类,本质是调用c++的方法
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//如果父类加载器加载不到类,子类加载器再尝试自己加载
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//加载类
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
打破双亲委派 线程上下文类加载器
为什么要打破双亲委派模型?
某些场景是:Bootstrap ClassLoadr加载的类,需要去调用Application ClassLoader加载的类。如java.jdbc.Driver是一个接口,不同数据厂商去实现自己的DB实现,就出现了Bootstrap ClassLoader加载的java.jdbc.Driver,取调用Applicaiton ClassLoader的实现,肯定行不通!
如何打破双亲委派模型?
自定义类加载器:继承ClassLoader类,重写LoadClass方法
使用线程上下文类加载器使用
ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); Class.forName(XXX.getClass().getName(), true, classLoader);
第三方平台不会及时更新本文最新内容。如果发现本文资料不全,可访问本人的Java博客搜索:标题关键字。以获取最新全部资料 ❤