众所周知. 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
vscode launch.jsps: 一定要通过第三个类(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
forName0
然后通过JVM_FindClassFromCaller 函数从指定的classloader中查找指定的类 此时类名应该位 com/huangxunyi/Son这样 splash形式
然后来到 src/hotspot/share/prims/jvm.cpp的 JVM_FindClassFromCaller
首先从符号表里面去查找,如果其不存在. 就添加到符号表中
符号表(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.执行查找以查看类是否已存在以及保护域是否具有正确的访问权限
检查是否已经加载该类
2.获取对象锁 并 将要加载的类名hash加入placeholders 防止多线程重复加载
3. 再次确认该类未加载
4. 从placeholder中判断是否该类正在被加载.
5. 添加此class正在被加载的记录到placeholder . (复杂)
6. 一切验证都通过后 并确保该类未加载后 真正的加载类
k = load_instance_class(name, class_loader, THREAD);
7. 清理placeholder , 并在LOAD_INSTANCE里标记该类加载成功或失败
其中第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的情况. 最终完成加载 . 并且转换为我们需要类
call_virtual****************************************************************************
其大致调用栈如下 (摘自类加载与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 ClassforName(String name, boolean initialize,
ClassLoader loader)
跟进去后来到src/hotspot/share/oops/instanceKlass.cpp的 void InstanceKlass::initialize_impl(TRAPS)函数
initialize_impl 加锁检查.防止重复初始化
2. 如果 处于 being_initialized状态 并且 正在被*其它线程*初始化,则执行waitUninterruptibly等待其他线程完成后通知 .
3. 如果 处于 being_initialized状态 并且 正在被*当前线程*初始化,则直接返回
4. 如果类已经初始化过了(fully_initialized), 直接返回 ( 每个类只初始化一次)
5. 如果初始化失败, 处于initialization_error 状态则抛出异常
6. 设置初始化状态为being_initialized 和初始化线程
7. 如果当前 类 不是接口类型,且父类不为空同时父类且还未初始化,则执行父类的初始化。
(这里发生了套娃)
此时父类的静态变量和静态代码块已经打印
8. 通过 call_class_initializer 执行静态代码. 在此之前 寻找这个类的aot编译方法,包括类初始值设定项 , 此时子类静态代码块 和子类静态变量被打印
9. 如果未出现异常则表示初始化成功, 那么设置 当前类 状态为fully_initialized, 并通知其它线程初始化已经完成
10/11. 如果出现异常.则设置当前类的状态为 initialization_error,并通知其它线程初始化发生异常。并且对Java TI 进行一些处理
类的状态
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() 的指定其不初始化呢
什么也没输出.
因此可以得出结论. Class.forName() 默认会对第一次加载的类初始化. 而 .class不会, 至于getClass() 你都能拿到对象了...可肯定已经初始化过了..
所以在 早期 JDBC连接数据库的使用,需要使用Class.forName("com.mysql.jdbc.Driver") 加载驱动
因为其内部有一个静态代码块将JDBC驱动加载至DriverManager中
注意是 早期 现在的JDBC早就不需要了 Class.forName() 了...
原因就是 ServiceLoader. 即 SPI
services
暂无评论数据