zoukankan      html  css  js  c++  java
  • Java类加载器详解

    JVM三种类型的类加载器

    • 我们首先看一下JVM预定义的三种类型类加载器,当一个 JVM 启动的时候,Java 缺省开始使用如下三种类型类装入器:
    • 启动类加载器(Bootstrap Class Loader):引导类装入器是用本地代码实现的类装入器,它负责将 <Java_Runtime_Home>/lib 下面的类库加载到内存中。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
    • 标准扩展类加载器(Extensions Class Loader):扩展类加载器是由 Sun 的 ExtClassLoader (sun.misc.Launcher$ExtClassLoader) 实现的。它负责将 < Java_Runtime_Home >/lib/ext 或者由系统变量 java.ext.dir 指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
    • 系统类加载器(System Class Loader ):系统类加载器是由 Sun 的 AppClassLoader (sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(classpath)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。

    类加载双亲委派机制介绍和分析

    为什么是双亲委派机制:java.lang.String类在rt.jar中,在JVM启动时由BootStrap启动类加载器加载;当用户自定义的加载器也需要加载java.lang.String类时,如果自定义的加载器不请求双亲加载(检查双亲是否已经加载),就会有2个java.lang.String类了。所以双亲委派机制解决了这个问题:一个类加载器在接收到类加载请求的时候,首先将其委托给父类加载器,如果父类加载器可以完成类加载任务,就成功返回;如果父类加载器无法完成加载任务时,才去自己加载。

    双亲委派机制很好解决了各个类加载器类加载的统一问题。
    Alt text
    系统类加载器的父类加载器是标准扩展类加载器,标准扩展类加载器的父类加载器是启动类加载器

    public static void main(String[] args) {
    	try {
    		System.out.println(ClassLoader.getSystemClassLoader());
    		System.out.println(ClassLoader.getSystemClassLoader().getParent();
    		System.out.println(ClassLoader.
    			getSystemClassLoader().getParent().getParent());                                                            
    	} catch (Exception e) {
           e.printStackTrace();
       }
    }
    

    通过ClassLoader.getSystemClassLoader()可以直接获取到系统类加载器,系统类加载器父类是扩展类加载器,扩展类加载器的父类则是启动类加载器,在JVM中的体现是null,因为启动类加载器是用native的c++实现的。以上代码结果如下:

    sun.misc.Launcher$AppClassLoader@197d257
    sun.misc.Launcher$ExtClassLoader@7259da
    null
    

    通过以上的代码输出,我们可以判定系统类加载器的父加载器是扩展类加载器,但是我们试图获取启动类加载器的父类加载器时确得到了null,就是说扩展类加载器本身强制设定父类加载器为null。我们还是借助于代码分析一下:

    protected ClassLoader() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        //默认将父类加载器设置为系统类加载器,getSystemClassLoader()获取系统类加载器
        this.parent = getSystemClassLoader();
        initialized = true;
    }
    
    protected ClassLoader(ClassLoader parent) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
           security.checkCreateClassLoader();
        }
        //强制设置父类加载器
        this.parent = parent;
        initialized = true;
    }
    

    线程上下文类加载器(Thread Context Claass Loader,TCCL)

    双亲委派机制解决了类重复加的问题,但是不能解决应用开发中遇到的全部类加载问题。双亲委派机制中,上层父类的加载器不可以使用子类加载的对象。而有些时候程序的确需要父类调用子类对象,这时候就需要线程上下文加载器来处理。

    Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。 以JNDI为例,它的核心接口是由JRE核心类(rt.jar)实现的,由启动类加载器加载。但是在这核心类JNDI的实现是由第三方厂商实现的,由系统类加载器加载。启动类加载器是无法找到第三方实现类,因为它只加载 Java 的核心库。

    但这些核心接口的实现类必须能加载由第三方厂商提供的JNDI实现。这种情况下父类请求子类加载器去完成类加载任务(这个类只有子类加载器可见),双亲委派机制就会失效。解决办法就是让核心JNDI类使用线程上下文类加载器,从而有效的打通类加载器层次结构,逆着代理机制的方向使用类加载器。

    **线程上下文类加载器(Thread Context ClassLoader) **
    线程上下文加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,默认是系统类加载器。

    Thread Context Class Loader(TCCL)

    TCCL is a hack that was added in Java 1.2 to support J2EE. Specifically it was needed to support things like Entity Beans; in a modern world it's used to support technologies like JPA, JAXB, Hibernate and so on.

    先来看个TCCL的例子,自定义类加载器myUrlCl(URLClassLoader)

    public class MyURLClassLoaderTest{
    	public static void main(String[] args) throws Exception {	
    	URL[] baseUrls = { URL("file:D:/java_tools/maven2/m2/repository
    	/com/xxx/common/common-test/1.0-SNAPSHOT/
    	common-test-1.0-SNAPSHOT.jar")};
    	
    	String targetClassName = "com.xxx.common.HelloWorld";
    	// 设置系统类加载器为myUrlCl的父类加载器
    	ClassLoader systemCl = ClassLoader.getSystemClassLoader();
    	// 
    	ClassLoader myUrlCl = new URLClassLoader(baseUrls,systemCl);
    	// 设置TCCL为myUrlCl,
    	Thread.currentThread().setContextClassLoader(myUrlCl);
    	
    	Class targetClass = myUrlCl.loadClass(targetClassName);
    	Object targetObj = targetClass.newInstance();
    	Object res  = targetClass.getMethod("test").invoke(targetObj);
    	System.out.println(res);
    	}
    }
    

    其中com/xxx/common/common-test/1.0-SNAPSHOT 这个jar包只有两个类,分别是:Apple.java,HelloWorld.java;

    public class Apple {
        private String id;
        getter/setter.....
    }
    

    HelloWorld.java 创建Apple的对象用Class.forName指定类加载器为TCCL的类

    public class HelloWorld {
      public String test() throws Exception {
    	StringBuilder sb = new StringBuilder();
        ClassLoader tccl = Thread.currentThread().getContextClassLoader();
        // appleClass指定类加载器为tccl
    	Class appleClass = Class.forName("com.xxx.common.Apple", true, tccl);
    	sb.append("thread_context_classLoader:" + tccl.toString() + "
    ");            
    	sb.append("Apple_Class_classLoader:"
    			+appleClass.getClassLoader().toString()+"
    ");
                
    	if(tccl.getParent()!=null){
    		sb.append("thread_context_classLoader_parent:"+
    			tccl.toString()+"
    ");
    	}else{
    		sb.append("thread_context_classLoader_parent:null
    ");
    	}
    	if(String.class.getClassLoader()!=null){
    		sb.append("String_classLoader:"
    			+String.class.getClassLoader().toString()+"
    ");
    	}else{
    		sb.append("String_classLoader:null
    ");
    	}
    	return sb.toString();
      }
    }
    

    执行MyURLClassLoaderTest.java获取的结果是:加载Apple类的是myUrlCl类加载器,myUrlCl加载器的父类加载器是系统类加载器,和ClassLoader myUrlCl = new URLClassLoader(baseUrls,systemCl)的设置符合;加载String的是启动类加载器。输出的结果如下:

    thread_context_classLoader:java.net.URLClassLoader@1c90a278
    Apple_Class_classLoader:java.net.URLClassLoader@1c90a278
    thread_context_classLoader_parent:sun.misc.Launcher$AppClassLoader@4e7a15b
    String_classLoader:null
    

    如果在HelloWorld.java中有main方法调用test方法,结果:加载Apple的是系统类加载器;加载String的是启动类加载器。输出结果如下:

    thread_context_classLoader:sun.misc.Launcher$AppClassLoader@4e7a15b
    Apple_Class_classLoader:sun.misc.Launcher$AppClassLoader@4e7a15b
    thread_context_classLoader_parent:sun.misc.Launcher$ExtClassLoader@3125fe1
    String_classLoader:null
    

    栗子演示完毕,是不是有点迷惑:为什么不直接加载common-test-1.0-SNAPSHOT 这个jar包的下面的类。 这种类加载设计应用场景是什么?其中一个应用场景是Tomcat的启动类就是应用了这种特性。
    tomcat_classloader
    每一个WebApp Class Loader都会有个统一的启动方法start(或者init),Catalina ClassLoader(类似于上例中的MyURLCl)加载WebAppClassLoader,这样就能做到每个WebApp间接依赖的下游的类,即使类限定名一直也不会发生冲突,这就是类隔离的。

    JVM唯一标识一个类:类加载器+类限定名

    有一个点留给读者思考,如果MyURLClassLoaderTest中注释掉以下代码,执行结果是怎么样的

     Thread.currentThread().setContextClassLoader(myUrlCl);
    
    博主原创,转载请标明出处!
    联系方式: 微信:corolla_zhaojd
    Email: zhaojiandongzju@gmail.com

  • 相关阅读:
    Angular2 表单
    PHP Windows环境部署
    CI 扩展 Service
    Angular2 组件生命周期
    linux 命令笔记
    CI 笔记一
    Angular2 管道
    Angular2 指令
    springboot整合Quartz实现动态配置定时任务
    vue-app开发入门
  • 原文地址:https://www.cnblogs.com/oldtrafford/p/6736667.html
Copyright © 2011-2022 走看看