类加载相关知识

1、类加载过程

类加载的过程是将.class文件加载到JVM内存当中,生成对应的class对象

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);
                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

流程:

  • 查看类有无被当前类加载器加载过,如果加载过,直接返回;
  • 如果没有加载过,查看有无父类加载器,有父类加载器,则查看父类加载器有无加载过(调用parent.loadClass从而又进行同样的流程);无父类加载器,查看启动类加载器有无加载过;
  • 如果父类加载器和启动类加载器都没有加载过,则调用findClass(通常由子类来实现)来加载类;

可以看出类加载的过程是一个递归的过程,也叫作双亲委派模式。

2、类加载器的分类

2.1、应用程序类加载器(AppClassLoader)

应用程序类加载器可通过下面的方式获取:

ClassLoader classLoader = Launcher.getLauncher().getClassLoader();
System.out.println(classLoader);

或者这种方式

ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
System.out.println(contextClassLoader);

在创建Launcher的时候会创建应用程序类加载器,如在Launcher的构造方法中的第九行this.loader = Launcher.AppClassLoader.getAppClassLoader(var1)创建应用程序类加载器,并将其设置为上下文类加载器Thread.currentThread().setContextClassLoader(this.loader)

public Launcher() {
    Launcher.ExtClassLoader var1;
    try {
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
        throw new InternalError("Could not create extension class loader", var10);
    }
    try {
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }
    Thread.currentThread().setContextClassLoader(this.loader);
    String var2 = System.getProperty("java.security.manager");
    if (var2 != null) {
        SecurityManager var3 = null;
        if (!"".equals(var2) && !"default".equals(var2)) {
            try {
                var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
            } catch (IllegalAccessException var5) {
            } catch (InstantiationException var6) {
            } catch (ClassNotFoundException var7) {
            } catch (ClassCastException var8) {
            }
        } else {
            var3 = new SecurityManager();
        }
        if (var3 == null) {
            throw new InternalError("Could not create SecurityManager: " + var2);
        }
        System.setSecurityManager(var3);
    }
}

走进getAppClassLoader方法,可以看到应用程序类加载器的创建过程,主要是获取加载路径,然后构建对象:

public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
    final String var1 = System.getProperty("java.class.path");
    final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
    return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
        public Launcher.AppClassLoader run() {
            URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
            return new Launcher.AppClassLoader(var1x, var0);
        }
    });
}

应用程序类加载器主要用来加载java.class.path下的类,即我们自定义的类。

String property = System.getProperty("java.class.path");
StringTokenizer stringTokenizer = new StringTokenizer(property, ";");
while (stringTokenizer.hasMoreTokens()) {
    String s = stringTokenizer.nextToken();
    System.out.println(s);
}

2.2、拓展类加载器(ExtClassLoader)

在上面Launcher的构造方法中,我们还看到了var1 = Launcher.ExtClassLoader.getExtClassLoader(),没错,这就是在创建拓展类加载器,下面我们看一下getExtClassLoader方法:

public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
    final File[] var0 = getExtDirs();
    try {
        return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
            public Launcher.ExtClassLoader run() throws IOException {
                int var1 = var0.length;
                for(int var2 = 0; var2 < var1; ++var2) {
                    MetaIndex.registerDirectory(var0[var2]);
                }
                return new Launcher.ExtClassLoader(var0);
            }
        });
    } catch (PrivilegedActionException var2) {
        throw (IOException)var2.getException();
    }
}

也是获取加载路径,然后创建对象,只不过路径变为了getExtDirs方法的返回值:

private static File[] getExtDirs() {
    String var0 = System.getProperty("java.ext.dirs");
    File[] var1;
    if (var0 != null) {
        StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
        int var3 = var2.countTokens();
        var1 = new File[var3];
        for(int var4 = 0; var4 < var3; ++var4) {
            var1[var4] = new File(var2.nextToken());
        }
    } else {
        var1 = new File[0];
    }
    return var1;
}

可以看到拓展类加载器主要加载java.ext.dirs目录下的类。
另外,var1 = Launcher.ExtClassLoader.getExtClassLoader()执行之后,var1被作为参数传递给了应用程序类加载器,追踪代码之后,可以看出拓展类加载被作为了应用程序类加载器的父加载器。

2.3、启动类加载器(BootstrapClassLoader)

启动类加载器由C++实现,它不继承ClassLoader类。

URLClassPath bootstrapClassPath = Launcher.getBootstrapClassPath();
for (URL url : bootstrapClassPath.getURLs()) {
    System.out.println(url.getPath());
}

打印结果:

/D:/jdk8/jre/lib/resources.jar
/D:/jdk8/jre/lib/rt.jar
/D:/jdk8/jre/lib/sunrsasign.jar
/D:/jdk8/jre/lib/jsse.jar
/D:/jdk8/jre/lib/jce.jar
/D:/jdk8/jre/lib/charsets.jar
/D:/jdk8/jre/lib/jfr.jar
/D:/jdk8/jre/classes

可以看出启动类加载器主要加载$JAVA_HOME$/jre/lib下面的类。比如String类位于rt.jar,获取String类的类加载器则为null,因为启动类加载器不是java实现的,所以为null。

ClassLoader classLoader = String.class.getClassLoader();
System.out.println(classLoader); //null

3、自定义类加载器

流程:

  • 创建一个类继承自ClassLoader;
  • 重写findClass;
  • 读取字节码文件的字节流,调用defineClass,得到Class对象;
public class CustomClassLoader extends ClassLoader {

    private final String filePath;

    public CustomClassLoader(String filePath) {
        this.filePath = filePath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        BufferedInputStream bis = null;
        ByteArrayOutputStream baos = null;
        try {
            bis = new BufferedInputStream(new FileInputStream(filePath));
            baos = new ByteArrayOutputStream();
            byte[] arr = new byte[1024];
            int len;
            while ((len = bis.read(arr, 0, arr.length)) > 0) {
                baos.write(arr, 0, len);
            }
            byte[] b = baos.toByteArray();
            return defineClass(name, b, 0, b.length);
        } catch (Exception e) {
            throw new ClassNotFoundException();
        } finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (baos != null) {
                try {
                    baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws Exception {
        CustomClassLoader customClassLoader = new CustomClassLoader("C://Users//86157//Desktop//com//oamha//test//Test.class");
        Class<?> aClass = customClassLoader.loadClass("com.oamha.test.Test");
        Class<?> aClass1 = customClassLoader.loadClass("com.oamha.test.Test");
        System.out.println(aClass == aClass1); //true

        CustomClassLoader customClassLoader1 = new CustomClassLoader("C://Users//86157//Desktop//com//oamha//test//Test.class");
        Class<?> aClass2 = customClassLoader1.loadClass("com.oamha.test.Test");
        System.out.println(aClass == aClass2); //false
    }
}

不同类加载器加载同一字节码文件,得到的字节码对象是不同的,因为一个类是由其二进制名和其定义类加载器共同确定的。

posted @   世间很大  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示