怎么可以错过(class.forname.newinstance的作用)class.forname classnotfound,Class.forName 发生了什么,classforname,java,
众所周知. 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运行结果
众所周知. IDE只是字符串拼接器. 因此运行上述代码可以通过
生成目录结构如下
然后通过如下命令即可运行
然后就可以愉快地开始调试源码了.
在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
然后来到一个非常核心的代码非常长的函数
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 主要逻辑为 .
如果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 从字节流中加载
如果class_loader.is_null()为false(本文Demo为false) , 通过JavaCalls::call_virtual方法生成jobject.
****************************************************************************此方法最终会调用掉ClassLoader.loadClass(双亲委派) , 因此会进入到class_loader.is_null()为true的情况. 最终完成加载 . 并且转换为我们需要类
call_virtual****************************************************************************其大致调用栈如下 (摘自类加载与Java主类加载机制解析 - 博文视点)
在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 进行一些处理
类的状态这就是native方法forName0的主要流程了
回到我们的Demo如果我们在调用 Class.forName() 的指定其不初始化呢
什么也没输出.
因此可以得出结论. Class.forName() 默认会对第一次加载的类初始化. 而 .class不会, 至于getClass() 你都能拿到对象了...可肯定已经初始化过了..
所以在 早期 JDBC连接数据库的使用,需要使用Class.forName("com.mysql.jdbc.Driver") 加载驱动
因为其内部有一个静态代码块将JDBC驱动加载至DriverManager中
注意是 早期 现在的JDBC早就不需要了 Class.forName() 了...
原因就是 ServiceLoader. 即 SPI
services本文系作者 @河马 原创发布在河马博客站点。未经许可,禁止转载。
暂无评论数据