java类加载机制

java类加载体系

BootStrap ClassLoader   >  ExtClassLoader  >  AppClassLoader

每种类加载器都有自己的加载目录

  • Bootstrap ClassLoader
    系统类(rt.ar)的类加载器,采用C++代码加载
  • Extension ClassLoader
    扩展类(ext.jar)的类加载器,采用ExtClassLoader加载
  • Application ClassLoader
    用户类路径(classpath)上类的类加载器,采用AppClassLoader加载
  • 自定义类加载器
    自定义的类加载器,继承ClassLoader即可

java双亲委派

一个java类加载进jvm内存的过程

  • 每个类加载器对他加载过的类,都有一个缓存。

  • 向上委托查找,向下委托加载。


    image.png
  • Bootstrap 类加载器是用 C++ 实现的,是虚拟机自身的一部分,主要负责加载核心的类库。如果获取它的对象,将会返回 null

双亲委派的作用

  1. 避免类的重复加载。当父类加载器加载了该类时,子类加载器就不会再加载一次。
  2. 防止java核心[API]被随意替换。

类加载流程:

  • 当Application ClassLoader 收到一个类加载请求时,他首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器Extension ClassLoader去完成。
  • 当Extension ClassLoader收到一个类加载请求时,他首先也不会自己去尝试加载这个类,而是将请求委派给父类加载器Bootstrap ClassLoader去完成。
  • 如果Bootstrap ClassLoader加载失败(在\lib中未找到所需类),就会让Extension ClassLoader尝试加载。
  • 如果Extension ClassLoader也加载失败,就会使用Application ClassLoader加载。
  • 如果Application ClassLoader也加载失败,就会使用自定义加载器去尝试加载。
  • 如果均加载失败,就会抛出ClassNotFoundException异常。

jdk类加载对象

ClassLoader > SecureClassLoader >  URLClassLoader  > ExtClassLoader,AppClassLoader
image.png

打印类加载器

public class ClassLoaderTest {

    public static void main(String[] args) throws Exception {
        //父子关系:AppClassLoader <- ExtClassLoader  <- BootStrap ClassLoader
        ClassLoader c1 = ClassLoaderTest.class.getClassLoader();
        System.out.println("c1:"+c1); // AppClassLoader
        System.out.println("c1 parent:"+c1.getParent()); //ExtClassLoader
        //BootStrap ClassLoader是由c++开发,是JVM虚拟机的一部分,本身不是java类
        System.out.println("c1 parent parent:"+c1.getParent().getParent()); // null
        
        // String int等基础类由 BootStrap ClassLoader 加载
        ClassLoader c2 = String.class.getClassLoader();
        System.out.println("c2:"+c2); // null
        System.out.println(c1.loadClass("java.util.List").getClass().getClassLoader()); // null
        
        // java指令可以通过增加-verbose:class -verbose:gc 参数在启动时打印出类加载情况
        // BootStrap ClassLoader 加载java基础类,非java实现
        System.out.println("BootStrap ClassLoader加载目录:"+System.getProperty("sun.boot.class.path"));
        // ExtClassLoader加载JAVA_HOME/ext下的jar, 可通过-D java.ext.dirs另行指定目录
        System.out.println("ExtClassLoader加载目录:"+System.getProperty("java.ext.dirs"));
        // AppClassLoader加载CLASSPATH应用下的jar, 可通过-D java.class.path另行指定目录
        System.out.println("AppClassLoader加载目录:"+System.getProperty("java.class.path"));
    }
}
image.png

新建一个工程,导出为jar

我们新建一个工程,导出为jar文件,里面包含一个类

package com.test.say;

public class SayHello {
    StringBuffer sb = new StringBuffer();
    public String say(String hi, int age, String name) {
        return sb.append(hi+"  ").append("我今年"+age+"岁了").append("我叫:"+name).toString();
    }
}
image.png

使用URLClassLoader加载jar

package com.test.load;

import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class JarLoad {

    public static void main(String[] args) throws Exception{
        URL jarPath = new URL("file:D:/jar/say.jar");
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{jarPath});
        Class clz = urlClassLoader.loadClass("com.test.say.SayHello");
        Object obj = clz.newInstance();
        Method method = clz.getMethod("say", String.class, int.class, String.class);
        String ret = (String)method.invoke(obj, "你好", 22, "张三");
        System.out.println(ret);
    }
}

自定义类加载器,直接加载class文件

我们把上面生成的SayHello.class改为SayHello.myclass(这样反编译jdui就无法查看),然后自定义类加载器加载SayHello.myclass


image.png

自定义SayClassLoader

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.security.SecureClassLoader;

public class SayClassLoader extends SecureClassLoader {
    private String clzPath;

    public SayClassLoader(String clzPath) {
        this.clzPath = clzPath;
    }

    /**
     * 需要实现从myClass加载
     */
    @Override
    protected Class findClass(String name) {
        // name: com.test.say.SayHello
        // filePath:D:\eclipse_workspace\SayHello\bin\com\test\say\SayHello.myclass
        String filePath = this.clzPath + name.replace(".", "\\").concat(".myclass");
        byte[] b = fileConvertToByteArray(new File(filePath));
        return this.defineClass(name, b, 0, b.length);
    }
    
    
    /**
     * 把一个文件转化为byte字节数组。
     */
    private byte[] fileConvertToByteArray(File file) {
        byte[] data = null;
 
        try {
            FileInputStream fis = new FileInputStream(file);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
 
            int len;
            byte[] buffer = new byte[1024];
            while ((len = fis.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            data = baos.toByteArray();
            fis.close();
            baos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;
    }
}

使用自定义的类加载器

  SayClassLoader sayClassLoader = new SayClassLoader("D:\\eclipse_workspace\\SayHello\\bin\\");

Class clz = sayClassLoader.loadClass("com.test.say.SayHello");
Object obj = clz.newInstance();
Method method = clz.getMethod("say", String.class, int.class, String.class);
String ret = (String)method.invoke(obj, "hi", 22, "李四");
System.out.println(ret);

自定义类加载器,从jar中加载class

自定义类加载器,从jar中找到对应的class文件,加载到jvm中。

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.SecureClassLoader;

/**
 * 实现从jar包中找到class文件,来加载类
 */
public class JarLoader extends SecureClassLoader{
    private String jarPath; //jar文件的路径
    
    public JarLoader(String jarPath) {
        this.jarPath = jarPath;
    }

    @Override
    protected Class findClass(String name) {
        String classPath = name.replace(".", "/").concat(".class");
        URL fileUrl;
        try {
            String path = "jar:file:\\"+this.jarPath+"!/"+classPath;
            // path:  jar:file:\D:\jar\say.jar!/com/test/say/SayHello.class
            fileUrl = new URL(path);
            byte[] b = fileConvertToByteArray(fileUrl.openStream());
            return this.defineClass(name, b, 0, b.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
     /**
     * 把一个文件转化为byte字节数组。
     */
    private byte[] fileConvertToByteArray(InputStream ins) {
        byte[] data = null;
 
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
 
            int len;
            byte[] buffer = new byte[1024];
            while ((len = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            data = baos.toByteArray();
            ins.close();
            baos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;
    }
}

使用自定的jar加载器

JarLoader jarLoader = new JarLoader("D:\\jar\\say.jar");

Class clz = jarLoader.loadClass("com.test.say.SayHello");
Object obj = clz.newInstance();
Method method = clz.getMethod("say", String.class, int.class, String.class);
String ret = (String)method.invoke(obj, "how are you", 22, "王五");
System.out.println(ret);

到底加载的是哪个类

先看下面场景,假如我的当前工程下面有一个包名类名以及方法声明都一样的java文件,此时使用自定义的jar,会是怎么样的?


image.png

image.png

测试加载jar包里的类

public class JarLoad {

    public static void main(String[] args) throws Exception{
        JarLoader jarLoader = new JarLoader("D:\\jar\\say.jar");
        
        Class clz = jarLoader.loadClass("com.test.say.SayHello");
        Object obj = clz.newInstance();
        Method method = clz.getMethod("say", String.class, int.class, String.class);
        String ret = (String)method.invoke(obj, "how are you", 22, "王五");
        System.out.println(ret);
    }
}

image.png

可以看到,使用的是当前工程的SayHello,而不是预期的jar包里的SayHello,问题就出在双亲委派机制。

打破双亲委派

上述的JarLoader除了重写findClass还需要重写loadClass

@Override
protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class c= null;
        synchronized (getClassLoadingLock(name)) {
            // 1. 先从缓存中取
            c = findLoadedClass(name);
            if (!name.startsWith("com.test.say")) {
                c = this.getParent().loadClass(name);
            } else {
                c = this.findClass(name);
            }
        }
        return c;
    }

你可能感兴趣的:(java类加载机制)