![fda2a1d78a7065750563f510c6425d86.png](https://i-blog.csdnimg.cn/blog_migrate/fe4c605c350c5334ee454508d0e74d63.jpeg)
众所周知. Java中获取Class
有三种方式
- 方式一 :通过对象的
getClass()
方法
Class<?> clazz1 =str.getClass();
- 方式二:通过类的
.class
属性
Class<?> clazz2 =String.class;
- 方式三:通过
Class
类的静态方法forName(String className)
Class<?> clazz3 =Class.forName("java.lang.String");
那么引入一个问题.
在 早期 JDBC连接数据库的使用,需要使用Class.forName("com.mysql.jdbc.Driver")
加载驱动.但是这个句话与其他两句有什么区别呢. 为什么只能使用 Class.forName
而不能通过另外两种方法呢 ?
写一段源码验证一下
- Parent.java
package com.huangxunyi;
public class Parent {
private static String name = getName();
static {
System.out.println("父类静态代码块");
}
{
System.out.println("父类代码块");
}
public Parent() {
System.out.println("父类构造函数");
}
private static String getName() {
System.out.println("父类静态变量");
return null;
}
}
- Son.java
package com.huangxunyi;
public class Son extends Parent {
{
System.out.println("子类代码块");
}
static {
System.out.println("子类静态代码块");
}
private static String name = getName1();
public Son() {
System.out.println("子类构造方法");
}
private static String getName1() {
System.out.println("子类静态变量");
return null;
}
}
- Test.java
package com.huangxunyi;
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("com.huangxunyi.Son");
}
}
运行结果
Class.forName | Class.forName().newInstance() | Son.class |
-----------------------------------------------|-----------------
父类静态变量 | 父类静态变量 | <no output>
父类静态代码块 | 父类静态代码块 |
子类静态代码块 | 子类静态代码块 |
子类静态变量 | 子类静态变量 |
| 父类代码块 |
| 父类构造函数 |
| 子类代码块 |
| 子类构造方法 |
众所周知. IDE只是字符串拼接器. 因此运行上述代码可以通过
$ javac -d . Parent.java Son.java Test.java
生成目录结构如下
.
├── com
│ └── huangxunyi
│ ├── Parent.class
│ ├── Son.class
│ └── Test.class
├── Parent.java
├── Son.java
└── Test.java
然后通过如下命令即可运行
$ java com.huangxunyi.Test
![252b2dbcff01c6f29ca7c55f995271b7.png](https://i-blog.csdnimg.cn/blog_migrate/8d6cd664462f8290a40301e05db698eb.jpeg)
ps: 一定要通过第三个类(Test.java) 去重写
main
方法. 因为如果main方法写在Parent/Son中在执行main方法的时候 Parent/Son 就已经被加载了.
然后就可以愉快地开始调试源码了.
在Class.java
中 . forName(String name)
最终会调用native
的方法 forName0(String name, boolean initialize,ClassLoader loader,Class<?> caller)
那么就从这个native
方法入手
位于jdk/src/java.base/share/native/libjava/Class.c
![7be886a3f8989d184d6a9b7b91024a57.png](https://i-blog.csdnimg.cn/blog_migrate/92fdaff81306c9569677b765bd2faf16.jpeg)
然后通过JVM_FindClassFromCaller
函数从指定的classloader
中查找指定的类 此时类名应该位 com/huangxunyi/Son
这样 splash
形式
![b57c7de2ce3a951ca7096afdc26a1499.png](https://i-blog.csdnimg.cn/blog_migrate/26ab9d420bdd295bf56934f2538557d1.jpeg)
然后来到 src/hotspot/share/prims/jvm.cpp
的 JVM_FindClassFromCaller
![a5cc6d92db5ec468b756235d1cd05328.png](https://i-blog.csdnimg.cn/blog_migrate/cd026e0e6127f9186a99a213e218a6be.jpeg)
首先从符号表里面去查找,如果其不存在. 就添加到符号表中
![c45a5c80d9e35d2317efa98f661df018.png](https://i-blog.csdnimg.cn/blog_migrate/32486319a9cd2d8c1e8365a8c0771881.jpeg)
符号表(SymbolTable) 是由一组符号地址和符号信息构成的表格,最简单的可以理解为哈希表的K-V值对的形式。
然后来到 find_class_from_class_loader
函数 即: 从指定的Classloader
中找Class
核心逻辑位于src/hotspot/share/classfile/systemDictionary.cpp
Klass* SystemDictionary::resolve_or_null(Symbol* class_name, Handle class_loader, Handle protection_domain, TRAPS) {
if (Signature::is_array(class_name)) {
return resolve_array_class_or_null(class_name, class_loader, protection_domain, THREAD);
} else {
return resolve_instance_class_or_null_helper(class_name, class_loader, protection_domain, THREAD);
}
}
// name may be in the form of "java/lang/Object" or "Ljava/lang/Object;"
InstanceKlass* SystemDictionary::resolve_instance_class_or_null_helper(Symbol* class_name,
Handle class_loader,
Handle protection_domain,
TRAPS) {
assert(class_name != NULL && !Signature::is_array(class_name), "must be");
if (Signature::has_envelope(class_name)) {
ResourceMark rm(THREAD);
// Ignore wrapping L and ;.
TempNewSymbol name = SymbolTable::new_symbol(class_name->as_C_string() + 1,
class_name->utf8_length() - 2);
return resolve_instance_class_or_null(name, class_loader, protection_domain, THREAD);
} else {
return resolve_instance_class_or_null(class_name, class_loader, protection_domain, THREAD);
}
}
然后来到一个非常核心的代码非常长的函数
src/hotspot/share/classfile/systemDictionary.cpp#resolve_instance_class_or_null
他主要完成了如下行为
1.执行查找以查看类是否已存在以及保护域是否具有正确的访问权限
![6b3c5bf137e76bc5d71de8be37ea32bc.png](https://i-blog.csdnimg.cn/blog_migrate/24f160c915186648bafea6ee14e4add7.jpeg)
2.获取对象锁 并 将要加载的类名hash加入placeholders 防止多线程重复加载
![82fcc471de404b3a2065a8543ce793e0.png](https://i-blog.csdnimg.cn/blog_migrate/e6db8d3203da23853e99330e9d53ff45.jpeg)
3. 再次确认该类未加载
![1d3c0474017bd425417c8140abf6bee1.png](https://i-blog.csdnimg.cn/blog_migrate/203bdc3c0e2c16c7a92f39456ffd2706.jpeg)
4. 从placeholder中判断是否该类正在被加载.
![8d7d23c841ffa2dcef62e26095ad3212.png](https://i-blog.csdnimg.cn/blog_migrate/77190ac1aa67f69975352819abd4b0e8.jpeg)
5. 添加此class正在被加载的记录到placeholder . (复杂)
6. 一切验证都通过后 并确保该类未加载后 真正的加载类
k = load_instance_class(name, class_loader, THREAD);
![43e2e3c4ff5e81526b51a9bc91285dc8.png](https://i-blog.csdnimg.cn/blog_migrate/1bb100e0672c7d0d1d2957454cc0485d.jpeg)
7. 清理placeholder , 并在LOAD_INSTANCE里标记该类加载成功或失败
![6f7f67c3e7b5a39c5b5d5a34289dc19d.png](https://i-blog.csdnimg.cn/blog_migrate/0aa7adf2fa0c3c4004d80e5a91efdd33.jpeg)
其中第6步 load_instance_class
主要逻辑为 .
if (class_loader.is_null()) {
// 通过 boot classloader 加载 ..
}else{
// 通过指定的classloader加载
// Use user specified class loader to load class.
// Call loadClass operation on class_loader.
}
如果class_loader.is_null()
为true
, 通过src/hotspot/share/classfile/classLoader.cpp
的InstanceKlass* ClassLoader::load_class(Symbol* name, bool search_append_only, TRAPS)
方法中 KlassFactory::create_from_stream
从字节流中加载
InstanceKlass* result = KlassFactory::create_from_stream(stream,
name,
loader_data,
protection_domain,
NULL, // unsafe_anonymous_host
NULL, // cp_patches
THREAD);
如果class_loader.is_null()
为false
(本文Demo为false) , 通过JavaCalls::call_virtual
方法生成jobject
.
****************************************************************************
此方法最终会调用掉ClassLoader.loadClass
(双亲委派) , 因此会进入到class_loader.is_null()为true
的情况. 最终完成加载 . 并且转换为我们需要类
![6a621b5870ee0f83327dab8670982a78.png](https://i-blog.csdnimg.cn/blog_migrate/d2d1baea7c420ef8585877155ad27949.jpeg)
****************************************************************************
其大致调用栈如下 (摘自类加载与Java主类加载机制解析 - 博文视点)
jvm.cpp::find_class_from_class_loader():执行klassOop klass = SystemDictionary::resolve_or_fail(name, loader, protection_domain, throwError != 0, CHECK_NULL)
SystemDictionary::resolve_or_fail()
SystemDictionary::resolve_or_null()
SystemDictionary::resolve_instance_class_or_null():执行k = load_instance_class(name, class_loader, THREAD)(Do actual loading)
SystemDictionary::load_instance_class()
JavaCalls::call_virtual();
java.lang.ClassLoader.loadClass(String)
sun.misc.AppClassLoader.loadClass(String, boolean)
java.lang.ClassLoader.loadClass(String, boolean)
java.net.URLClassLoader.findClass(final String)
在find_class_from_class_loader
中执行完resolve_or_fail
函数后. 此时已经完成了JVM类加载机制的 连接
接下来变通过Java层传入的参数 initialize
判断是否需要 初始化.
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
![64ca6c03272e1966c57a05d723c31ad6.png](https://i-blog.csdnimg.cn/blog_migrate/7d1523b15cb28c78f86855634beff7b9.jpeg)
跟进去后来到src/hotspot/share/oops/instanceKlass.cpp
的 void InstanceKlass::initialize_impl(TRAPS)
函数
initialize_impl
- 加锁检查.防止重复初始化
![975529b3cde71e20123572b1ef9ac019.png](https://i-blog.csdnimg.cn/blog_migrate/68b3a95bda9081e495e16533e12d58c7.jpeg)
2. 如果 处于 being_initialized
状态 并且 正在被*其它线程*初始化
,则执行waitUninterruptibly
等待其他线程完成后通知 .
![318e827b949aada004360699b4f5c33a.png](https://i-blog.csdnimg.cn/blog_migrate/24d545ac6c4af5c130d5a9bf4177faf2.jpeg)
3. 如果 处于 being_initialized
状态 并且 正在被*当前线程*初始化
,则直接返回
![2c1e18269361b0b02b7d41be71fad785.png](https://i-blog.csdnimg.cn/blog_migrate/e5080a87af85186f9b6a518fdb1c1608.png)
4. 如果类已经初始化过了(fully_initialized), 直接返回 ( 每个类只初始化一次)
![e354c2edb2973753700506aa392564dd.png](https://i-blog.csdnimg.cn/blog_migrate/f049d9cd4ca242cfdbf2ad5dd7376b41.png)
5. 如果初始化失败, 处于initialization_error
状态则抛出异常
![fbcb76ce5d186281b91a644d635cdc0f.png](https://i-blog.csdnimg.cn/blog_migrate/c944d650111bcf2030a47efe37deafd3.jpeg)
6. 设置初始化状态为being_initialized
和初始化线程
![49fe6b818425fbf464e87feb81c0bc41.png](https://i-blog.csdnimg.cn/blog_migrate/25a31d81d36b055b790e1a5113e08650.png)
7. 如果当前 类 不是接口类型,且父类不为空同时父类且还未初始化,则执行父类的初始化。
(这里发生了套娃)
此时父类的静态变量
和静态代码块
已经打印
![adbe2d353b101fe02b938b778e9f0caa.png](https://i-blog.csdnimg.cn/blog_migrate/86550669947ed4469c47a5680cf26f47.jpeg)
8. 通过 call_class_initializer
执行静态代码. 在此之前 寻找这个类的aot编译方法,包括类初始值设定项 , 此时子类静态代码块
和子类静态变量
被打印
![ed3ed3a3a743959fba5c641f63eeee5e.png](https://i-blog.csdnimg.cn/blog_migrate/7f63476111e8337b5be606d276791579.jpeg)
9. 如果未出现异常则表示初始化成功, 那么设置 当前类 状态为fully_initialized, 并通知其它线程初始化已经完成
![c203d9898eabf1d8a34f26cb72b2deb4.png](https://i-blog.csdnimg.cn/blog_migrate/848fce340d4b15c706914d8936b988aa.jpeg)
10/11. 如果出现异常.则设置当前类的状态为 initialization_error,并通知其它线程初始化发生异常。并且对Java TI 进行一些处理
![c4e51b4f9d90f3bc89932bcd1198d96e.png](https://i-blog.csdnimg.cn/blog_migrate/c3196fdb30b5c5c03a8c2fbde283c63e.jpeg)
类的状态
enum ClassState {
allocated, // allocated (but not yet linked)
loaded, // loaded and inserted in class hierarchy (but not linked yet)
linked, // successfully linked/verified (but not initialized yet)
being_initialized, // currently running class initializer
fully_initialized, // initialized (successfull final state)
initialization_error // error happened during initialization
};
这就是native
方法forName0
的主要流程了
回到我们的Demo
如果我们在调用 Class.forName()
的指定其不初始化呢
![33c181f688226ceb31f3a4436adcea78.png](https://i-blog.csdnimg.cn/blog_migrate/ed05f1e0830f5af8cb93f4c3e24e3e13.jpeg)
什么也没输出.
因此可以得出结论. Class.forName() 默认会对第一次加载的类初始化. 而 .class
不会, 至于getClass()
你都能拿到对象了...可肯定已经初始化过了..
所以在 早期 JDBC连接数据库的使用,需要使用Class.forName("com.mysql.jdbc.Driver")
加载驱动
因为其内部有一个静态代码块将JDBC驱动加载至DriverManager
中
![81eeda3eae3effe69d93082499c3ec68.png](https://i-blog.csdnimg.cn/blog_migrate/9137d03e04d12b08b5dc5f4ab5a19acc.jpeg)
注意是 早期 现在的JDBC早就不需要了 Class.forName() 了...
原因就是 ServiceLoader
. 即 SPI
![9ff78bd144545ccce2f97bdc68157be9.png](https://i-blog.csdnimg.cn/blog_migrate/4f0e7a9ed3bd74f2adfc6a8322073470.jpeg)