众所周知. 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
分类: 游戏攻略 标签: 暂无标签

评论

暂无评论数据

暂无评论数据

目录