Java ClassLoader

本文深入探讨了Java类加载机制,包括启动类加载器、扩展类加载器、系统类加载器以及用户自定义类加载器的功能。特别介绍了如何通过线程上下文类加载器动态加载类,并解释了类加载过程中的双亲代理机制和类名+加载器的唯一标识原则。此外,文章还通过示例代码展示了不同类加载器之间的交互和作用,以及静态块执行时间的特性。


java文件被编译为class文件,成为jvm机器码。jvm通过classloader将class的字节流加载,解析后变为class类。


  1. Bootstrap ClassLoader/启动类加载器
    主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作。
  2. Extension ClassLoader/扩展类加载器
    主要负责jdk_home/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包装入工作。
  3. System ClassLoader/系统类加载器
    主要负责java -classpath/-Djava.class.path所指的目录下的类与jar包装入工作。
  4. User Custom ClassLoader/用户自定义类加载器(java.lang.ClassLoader的子类)
    在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性。
每个时刻,当前类有三种加载器可选择:

系统类加载器, 通过ClassLoader.getSystemClassLoader()

当前类加载器, 加载当前类的加载器。 Class.forName()会隐式调用当前类的加载器加载

当前线程类加载器, 通过Thread.getCurrentThread().getContextClassLoader()获取


加载classloader类的当前类所在的加载器,将会变成classloader的父加载器。


类加载采用双亲代理机制,加载类时,首先代理到父加载器,查找类的顺序变成从根加载器往叶子加载器向下寻找。好处是防止自定义包中实现String类。


每个加载器维护自身的名字空间,父加载器对子加载器可见,反之不可逆。jvm认定一个类,根据类名+加载器。


因为jdk中包含一些API接口类,这些类使用启动类加载器。而其接口实现类在第三方jar包中,采用系统加载类,会导致两边的类无法互相可见,最终导致ClassNotFound异常。解决方法是线程上下文加载器。


java动态载入class的两种方式:

  • 1) implicit隐式,即利用实例化才载入的特性来动态载入class
    2) explicit显式方式,又分两种方式:
    java.lang.Class的forName()方法
    java.lang.ClassLoader的loadClass()方法
static块在什么时候执行?
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(); } }


// static block execute time

public class Test { public static class A { static { System.out.println("static"); } public A() { System.out.println("Cons A"); } } public static void main(String[] args) throws Exception { Thread.currentThread().getContextClassLoader().loadClass(A.class.getName()); // no static exec System.out.println("load class"); Class.forName(A.class.getName(), false, ClassLoader.getSystemClassLoader()); // no static exec System.out.println("class for name0"); Class.forName(A.class.getName()); // static exec System.out.println("class for name1"); System.out.println("~~~~~~ new A"); new A(); } } ---------------------- result: -------------------

load class
class for name0
static
class for name1
~~~~~~ new A
Cons A




评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值