java文件被编译为class文件,成为jvm机器码。jvm通过classloader将class的字节流加载,解析后变为class类。
-
Bootstrap ClassLoader/启动类加载器
主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作。 -
Extension ClassLoader/扩展类加载器
主要负责jdk_home/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包装入工作。 -
System ClassLoader/系统类加载器
主要负责java -classpath/-Djava.class.path所指的目录下的类与jar包装入工作。 -
User Custom ClassLoader/用户自定义类加载器(java.lang.ClassLoader的子类)
在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性。
系统类加载器, 通过ClassLoader.getSystemClassLoader()
当前类加载器, 加载当前类的加载器。 Class.forName()会隐式调用当前类的加载器加载
当前线程类加载器, 通过Thread.getCurrentThread().getContextClassLoader()获取
类加载采用双亲代理机制,加载类时,首先代理到父加载器,查找类的顺序变成从根加载器往叶子加载器向下寻找。好处是防止自定义包中实现String类。
每个加载器维护自身的名字空间,父加载器对子加载器可见,反之不可逆。jvm认定一个类,根据类名+加载器。
因为jdk中包含一些API接口类,这些类使用启动类加载器。而其接口实现类在第三方jar包中,采用系统加载类,会导致两边的类无法互相可见,最终导致ClassNotFound异常。解决方法是线程上下文加载器。
java动态载入class的两种方式:
-
1) implicit隐式,即利用实例化才载入的特性来动态载入class
2) explicit显式方式,又分两种方式:
java.lang.Class的forName()方法
java.lang.ClassLoader的loadClass()方法
1)当调用forName(String)载入class时执行,如果调用ClassLoader.loadClass并不会执行.forName(String,false,ClassLoader)时也不会执行.
2)如果载入Class时没有执行static块则在第一次实例化时执行.比如new ,Class.newInstance()操作
3)static块仅执行一次
loadClass 为classloader主要方法,传入classname,中间做cache查找、双亲委派、安全控制之类的逻辑,最终返回Class
defineClass , 传入byte字节流,根据jvm的bytecode算法,将其解析为Class
========= tomcat classloader ===========
Bootstrap
|
System
|
Common
/ \
Webapp1 Webapp2 ...
bootstrap: jvm/lib/ext/*.jar
system: CLASSPATH/*.jar
common: tomcathome/lib/*.jar
webappx: webapp/root/WEB-INF/lib/*.jar webapp/root/WEB-INF/classes/
上图为tomcat加载器树状结构。于双亲委派策略不同的是,WebappClassLoader会优先加载本地的类,然后再委派双亲。(除了servlet api这样的系统类会滤过)
tomcat下线程默认上下文类加载器是 webapp class loader。
因此,应用app中,类的查找顺序是:
/WEB-INF/classes/
/WEB-INF/lib/*.jar
bootstrap
CLASSPATH
tomcathome/lib/
==================== example code =============
// invisible to parent classloader
public class Test { public static class A { public A() { System.out.println("Cons A"); } } public static void main(String[] args) throws Exception { ClassLoader cl = Thread.currentThread().getContextClassLoader(); ClassLoader pcl = cl.getParent(); pcl.loadClass(Object.class.getName()); // ok to load root class pcl.loadClass(A.class.getName()); // fail to load child class, throw java.lang.ClassNotFoundException: Test$A } }// thread context classloader public class Test { public static class A { public A() { System.out.println("Cons A"); } } public static void main(String[] args) throws Exception { ClassLoader cl = Thread.currentThread().getContextClassLoader(); ClassLoader pcl = cl.getParent(); Thread t = new Thread(new Runnable() { @Override public void run() { ClassLoader cl = Thread.currentThread().getContextClassLoader(); try { new A(); // ok, 使用当前类加载器加载A Class.forName(A.class.getName()); // ok, 使用当前类加载器加载A ClassLoader.getSystemClassLoader().loadClass(A.class.getName()); // ok, // 使用系统类加载器 cl.loadClass(A.class.getName()); // throw ex, A class not // found } catch (ClassNotFoundException e) { e.printStackTrace(); } } }); t.setContextClassLoader(pcl); t.start(); t.join(); } }
load class
class for name0
static
class for name1
~~~~~~ new A
Cons A
本文深入探讨了Java类加载机制,包括启动类加载器、扩展类加载器、系统类加载器以及用户自定义类加载器的功能。特别介绍了如何通过线程上下文类加载器动态加载类,并解释了类加载过程中的双亲代理机制和类名+加载器的唯一标识原则。此外,文章还通过示例代码展示了不同类加载器之间的交互和作用,以及静态块执行时间的特性。
7230

被折叠的 条评论
为什么被折叠?



