ClassLoader的原理

1 ClassLoader工作机制

1.1 ClassLoader作用

寻找类字节码文件并构造出类在JVM内部表示的组件.负责运行时查找和装入Class字节码文件

1.2 装载步骤

1.2.1 装载

查找装载class字节码文件

1.2.2 链接

执行校验,准备和解析步骤,其中解析步骤时可选的

1.2.2.1 校验

检查装载Class文件的正确性

1.2.2.2 准备

给类的静态变量分配存储空间

1.2.2.3 解析

将符号引用转换为直接引用

1.2.3 初始化

对类的静态变量,方法,代码块执行初始化操作

2 JVM中提供的ClassLoader

2.1 Bootstrap ClassLoader(根加载器)

最顶层的装载器,它不是ClassLoader的子类,采用C++编写,因此在JAVA中不可见。主要负责装载JRE核心类库。可以通过jvm启动参数-Xbootclasspath改变该加载器加载的路径

2.2 Extention ClassLoader(扩展加载器)

主要负责加载JRE扩展目录ext下的包,可以通过-D java.ext.dirs选项指定目录

2.3 App ClassLoader(应用加载器/系统加载器)

负责加载当前工程目录下,classpath下的包或者class文件

其中Extention ClassLoader & AppClassLoaderClassLoader的子类,根加载器是扩展加载器的父加载器,扩展加载器是应用加载器的父加载器。在默认情况下使用应用加载器

3 JVM加载机制

类加载采用“全盘负责委托机制”。

“全盘负责”:在类加载时指定一个ClassLoader,除非显示声明其他的加载器,否则该类所依赖的类以及引用的类都由该加载器加载。

“委托机制”:类加载时优先委托父加载器寻找并加载目标类,只有在父加载器没有找到的情况下,才从自己的classpath路径下查找并加载目标类。该点主要是出于安全的考虑,在classpath路径下定义JDK中已经存在的类,由于该机制,JDK中的类都由父加载器开始查找并加载.每个加载器都有缓存,在委托时优先查找缓存,如果缓存中存在,那么直接返回,否者才执行查找和加载

类实例,类描述对象以及ClassLoader关系

类文件被加载后,在JVM内部对应拥有一个java.lang.Class类描述对象,类的每个实例拥有类描述对象的引用,类描述对象拥有类加载器的引用

启动顺序

Bootstrap ClassLoader-->Extention ClassLoader-->AppClass Loader即Bootstrap ClassLoader最先启动,接着是Extenion ClassLoader,最后是AppClass Loader

源码分析

6.1 JVM入口应用是sun.misc.Launcher

1.定义Bootstrap ClassLoader加载路径(sun.boot.class.path)

2.创建Extention ClassCloader

3.创建App ClassLoader并设置App ClassLoader的父类:Extention ClassCloader

  1. public class Launcher {

  2. //Bootstrap ClassLoader加载路径

  3. private static String bootClassPath = System.getProperty("sun.boot.class.path");

  4.  

  5.     //该处定义的App ClassLoader

  6. private ClassLoader loader;

  7.  

  8. public Launcher() {

  9. // Create the extension class loader

  10. ClassLoader extcl;

  11. try {

  12.     //创建Extention ClassLoader

  13. extcl = ExtClassLoader.getExtClassLoader();

  14. } catch (IOException e) {......}

  15.  

  16. // Now create the class loader to use to launch the application

  17. try {

  18.     //创建AppClassLoader并传入父加载器,由此可看AppClassLoader父加载器是Extention ClassLoader

  19. loader = AppClassLoader.getAppClassLoader(extcl);

  20. } catch (IOException e) {......}

  21.  

  22. //设置AppClassLoader为当前线程上下问类加载器

  23. // Also set the context class loader for the primordial thread.

  24. Thread.currentThread().setContextClassLoader(loader);

  25. }

  26.  

  27. public ClassLoader getClassLoader() {

  28. return loader;

  29. }

  30.  

  31. static class ExtClassLoader extends URLClassLoader {}

  32.  

  33. static class AppClassLoader extends URLClassLoader {}

  34. }

该类中定义了Bootstrap ClassLoader加载路径,同时创建了Extention ClassLoader以及AppClassLoader,如下是Bootstrap ClassLoader加载路径信息

  1. /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/resources.jar

  2. /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/rt.jar

  3. /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/sunrsasign.jar

  4. /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/jsse.jar

  5. /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/jce.jar

  6. /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/charsets.jar

  7. /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/jfr.jar

  8. /usr/lib/jvm/java-8-openjdk-amd64/jre/classes

从输出路径上看Bootstrap ClassLoader主要加载的是jre/lib目录下的资源.JVM Runtime核心库

6.2 ExtClassLoader源码

1.根据查找路径获得路径(jav.ext.dirs)下文件

2.将文件路径转换为URL

3.调用父类构造函数,创建ExtClassLoader并设置父加载器

  1. static class ExtClassLoader extends URLClassLoader {

  2. public static ExtClassLoader getExtClassLoader() throws IOException

  3. {

  4. final File[] dirs = getExtDirs();//获得查找路径下的所有文件

  5. try {

  6. return AccessController.doPrivileged(

  7. new PrivilegedExceptionAction<ExtClassLoader>() {

  8. public ExtClassLoader run() throws IOException {

  9. ......

  10.     //创建Extention ClassLoader

  11. return new ExtClassLoader(dirs);

  12. }

  13. });

  14. } catch (java.security.PrivilegedActionException e) {......}

  15. }

  1. public ExtClassLoader(File[] dirs) throws IOException {

  2.     //调用父类URLClassLoader的构造函数并传入父加载器,此时为null

  3. super(getExtURLs(dirs), null, factory);

  4. }

  1. private static File[] getExtDirs() {

  2.     //Extention ClassLoader加载路径

  3. String s = System.getProperty("java.ext.dirs");

  4. File[] dirs;

  5. if (s != null) {

  6. StringTokenizer st = new StringTokenizer(s, File.pathSeparator);

  7. int count = st.countTokens();

  8. dirs = new File[count];

  9. for (int i = 0; i < count; i++) {

  10. dirs[i] = new File(st.nextToken());

  11. }

  12. } else {

  13. dirs = new File[0];

  14. }

  15. return dirs;

  16. }

  17. }

  1. private static URL[] getExtURLs(File[] dirs) throws IOException {

  2. Vector<URL> urls = new Vector<URL>();

  3. for (int i = 0; i < dirs.length; i++) {

  4. String[] files = dirs[i].list();

  5. if (files != null) {

  6. for (int j = 0; j < files.length; j++) {

  7. if (!files[j].equals("meta-index")) {

  8. File f = new File(dirs[i], files[j]);

  9. urls.add(getFileURL(f));

  10. }

  11. }

  12. }

  13. }

  14. URL[] ua = new URL[urls.size()];

  15. urls.copyInto(ua);

  16. return ua;

  17. }

  1. static URL getFileURL(File file) {

  2. try {

  3. file = file.getCanonicalFile();

  4. } catch (IOException e) {}

  5.  

  6. try {

  7. return ParseUtil.fileToEncodedURL(file);

  8. } catch (MalformedURLException e) {

  9. // Should never happen since we specify the protocol...

  10. throw new InternalError(e);

  11. }

  12. }

如下是java.ext.dirs的输出信息

  1. /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/ext

  2. /usr/java/packages/lib/ext

6.3 AppClassLoader源码

1.获得java.classs.path路径下的所有文件

2.解析文件路径为URL

3.调用父类构造函数创建AppClassLoader并设置父类

  1. static class AppClassLoader extends URLClassLoader {

  2. public static ClassLoader getAppClassLoader(final ClassLoader extcl)

  3. throws IOException

  4. {

  5.     //AppClassLoader加载路径

  6. final String s = System.getProperty("java.class.path");

  7. final File[] path = (s == null) ? new File[0] : getClassPath(s);

  8.  

  9. return AccessController.doPrivileged(

  10. new PrivilegedAction<AppClassLoader>() {

  11. public AppClassLoader run() {

  12. URL[] urls =

  13. (s == null) ? new URL[0] : pathToURLs(path);

  14. return new AppClassLoader(urls, extcl);

  15. }

  16. });

  17. }

  18.  

  19. AppClassLoader(URL[] urls, ClassLoader parent) {

  20. super(urls, parent, factory);

  21. }

  22. }

  23. }

  1. private static URL[] pathToURLs(File[] path) {

  2. URL[] urls = new URL[path.length];

  3. for (int i = 0; i < path.length; i++) {

  4. urls[i] = getFileURL(path[i]);

  5. }

  6. // DEBUG

  7. //for (int i = 0; i < urls.length; i++) {

  8. // System.out.println("urls[" + i + "] = " + '"' + urls[i] + '"');

  9. //}

  10. return urls;

  11. }

Java.class.path输出信息

/work/new_workspace/aaa/bin

7 ClassLoader继承关系

java.lang.Object

    java.lang.ClassLoader

        java.security.SecureClassLoader

            java.net.URLClassLoader

                ExtClassLoader

                AppClassLoader

获得加载器的父加载器

可以通过getParent()获得当前加载器的父加载器

  1. //Main为自定义Class

  2. System.out.println(Main.class.getClassLoader());

  3. System.out.println(Main.class.getClassLoader().getParent());

  4.  

  5. System.out.println(Main.class.getClassLoader().getParent().getParent());

  6. System.out.println(Boolean.class.getClassLoader());

  1. sun.misc.Launcher$AppClassLoader@330bedb4

  2. sun.misc.Launcher$ExtClassLoader@5cad8086

  3.  

  4. null

  5. null

为什么获得ExtClassLoader的父加载器为null?这和每个加载器都有一个父加载器违背.这个属于正常现象.具体原因如下:

1.从ExtClassLoader构造函数super(getExtURLs(dirs), null, factory);可以看出此时传入的就是null

2.Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,不是一个JAVA类,无法在java代码中获取它的引用,凡是sun.boot.class.path路径下的包以及类都是由它加载。JVM初始化sun.misc.Launcher创建Extension ClassLoader和AppClassLoader实例。并将ExtClassLoader设置为AppClassLoader的父加载器。BootstrapClassCloader没有父加载器,但是它却可以作为一个ClassLoader的父加载器。比如ExtClassLoader。这也可以解释之前通过ExtClassLoader的getParent方法获取为Null的现象.我们可以分析getParent()的源码

父加载器可以直接由外界指定,如果外界不指定,那么采用AppClassLoader作为父加载器

//在创建ExtClassLoader时,采用super(getExtURLs(dirs), null, factory).所以获得的是null

  1. public URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {

  2. super(parent);

  3. }

  1. public abstract class ClassLoader {

  2. //父加载器直接由外部指定

  3. protected ClassLoader(ClassLoader parent) {

  4. this(checkCreateClassLoader(), parent);

  5. }

  6. }

  1. //没有指定父加载器的情况下将系统加载器设置为父加载器即AppClassLoader

  2. protected ClassLoader() {

  3. this(checkCreateClassLoader(), getSystemClassLoader());

  4. }

  1. private ClassLoader(Void unused, ClassLoader parent) {

  2. this.parent = parent;

  3. ...

  4. }

  1. public final ClassLoader getParent() {

  2. if (parent == null)

  3. return null;

  4. ..........

  5. return parent;

  6. }

  1. public static ClassLoader getSystemClassLoader() {

  2. initSystemClassLoader();

  3. ......

  4. return scl;

  5. }

  1. private static synchronized void initSystemClassLoader() {

  2. if (!sclSet) {

  3. //通过Launcher获取ClassLoader,其实就是AppClassLoader

  4. sun.misc.Launcher l = sun.misc.Launcher.getLauncher();

  5.     scl = l.getClassLoader();

  6. }

重要方法

9.1 URL getResource(String name)

查找给定名称的资源。资源的名称是一个/分隔的路径名称标识资源,优先查找父加载器,如果找不到在从Bootstrap ClassLoader路径中查找并加载

  1. public URL getResource(String name) {

  2. URL url;

  3. if (parent != null) {

  4. url = parent.getResource(name);

  5. } else {

  6. url = getBootstrapResource(name);

  7. }

  8. if (url == null) {

  9. url = findResource(name);

  10. }

  11. return url;

  12. }

9.2 Enumeration<URL> getResources(String name)

查找给定名称的所有资源。资源的名称是一个/分隔的路径名称标识资源,优先查找父加载器,如果找不到在从Bootstrap ClassLoader路径中查找并加载,该模式下支持正则匹配

  1. public Enumeration<URL> getResources(String name) throws IOException {

  2. @SuppressWarnings("unchecked")

  3. Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];

  4. if (parent != null) {

  5. tmp[0] = parent.getResources(name);

  6. } else {

  7. tmp[0] = getBootstrapResources(name);

  8. }

  9. tmp[1] = findResources(name);

  10.  

  11. return new CompoundEnumeration<>(tmp);

  12. }

9.3 URL getSystemResource(String name)

采用AppClassLoader加载给定名称的资源

  1. public static URL getSystemResource(String name) {

  2. ClassLoader system = getSystemClassLoader();

  3. if (system == null) {

  4. return getBootstrapResource(name);

  5. }

  6. return system.getResource(name);

  7. }

9.4 Enumeration<URL> getSystemResources(String name)

采用AppClassLoader加载给定名称的所有资源

  1. public static Enumeration<URL> getSystemResources(String name)

  2. throws IOException

  3. {

  4. ClassLoader system = getSystemClassLoader();

  5. if (system == null) {

  6. return getBootstrapResources(name);

  7. }

  8. return system.getResources(name);

  9. }

9.5 URL findResource(String name)

查找具有给定名称的资源。类加载器实现应覆盖此方法以指定在哪里查找资源

  1. protected URL findResource(String name) {

  2. return null;

  3. }

9.6 Enumeration<URL> findResources(String name)

查找具有给定名称的所有资源。类加载器实现应覆盖此方法以指定在哪里查找资源

  1. protected Enumeration<URL> findResources(String name) throws IOException {

  2. return java.util.Collections.emptyEnumeration();

  3. }

9.7 loadClass

loadClass(String name) & loadClass(String name, boolean resolve)name指定了类装载器的名字,必须使用全限定名。resolve告诉装载器是否需要解析该类。在初始化之前,应该考虑进行类解析工作。并不是所有的类都需要解析的。如果JVM只需要知道该类是否存在或者找出该类的超类,那么就不需要解析该类

  1. public abstract class ClassLoader {

  2. protected Class<?> loadClass(String name, boolean resolve)

  3. throws ClassNotFoundException

  4. {

  5. synchronized (getClassLoadingLock(name)) {

  6. // First, check if the class has already been loaded

  7. //是否已经加载

  8. Class<?> c = findLoadedClass(name);

  9. if (c == null) {

  10. try {

  11. if (parent != null) {

  12. //递归调用,查找父加载器

  13. c = parent.loadClass(name, false);

  14. } else {

  15. //调用Bootstrap Classloader

  16. c = findBootstrapClassOrNull(name);

  17. }

  18. } catch (ClassNotFoundException e) {......}

  19.  

  20. if (c == null) {

  21. // If still not found, then invoke findClass in order

  22. // to find the class.

  23.     //调用findClass

  24. c = findClass(name);

  25. ....

  26. }

  27. }

  28. if (resolve) {

  29. resolveClass(c);

  30. }

  31. return c;

  32. }

  33. }

  34. }

  1. protected final Class<?> findLoadedClass(String name) {

  2. if (!checkName(name))

  3. return null;

  4. return findLoadedClass0(name);

  5. }

  6.  

  7. private native final Class<?> findLoadedClass0(String name);

  1. private Class<?> findBootstrapClassOrNull(String name)

  2. {

  3. if (!checkName(name)) return null;

  4. return findBootstrapClass(name);

  5. }

  6.  

  7. private native Class<?> findBootstrapClass(String name);

  1. protected Class<?> findClass(String name) throws ClassNotFoundException {

  2. throw new ClassNotFoundException(name);

  3. }

9.8 defineClass(String name,byte[] b, int off,int len)

将类文件字节码数组装换成JVM内部的java.lang.Class对象,字节数组可以从本地系统,远程网络获取

  1. protected final Class<?> defineClass(String name, byte[] b, int off, int len)

  2. throws ClassFormatError

  3. {

  4. return defineClass(name, b, off, len, null);

  5. }

  1. protected final Class<?> defineClass(String name, byte[] b, int off, int len,

  2. ProtectionDomain protectionDomain)

  3. throws ClassFormatError

  4. {

  5. protectionDomain = preDefineClass(name, protectionDomain);

  6. String source = defineClassSourceLocation(protectionDomain);

  7. Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);

  8. postDefineClass(c, protectionDomain);

  9. return c;

  10. }

  1. private native Class<?> defineClass1(String name, byte[] b, int off, int len,

  2. ProtectionDomain pd, String source);

9.9 findSystemClass(String)

从本地文件系统载入Class文件,如果本地文件系统中不存在该Class文件,抛出ClassNotFoundError。该方法是JVM默认使用的装载器

  1. protected final Class<?> findSystemClass(String name)

  2. throws ClassNotFoundException

  3. {

  4. ClassLoader system = getSystemClassLoader();

  5. if (system == null) {

  6. if (!checkName(name))

  7. throw new ClassNotFoundException(name);

  8. Class<?> cls = findBootstrapClass(name);

  9. if (cls == null) {

  10. throw new ClassNotFoundException(name);

  11. }

  12. return cls;

  13. }

  14. return system.loadClass(name);

  15. }

9.10 findLoadedClass(String name)

该方法查看ClassLoader是否已经加载了某个类,如果载入就返回Class对象,否则返回null,如果强行载入已经载入的类会抛出异常

  1. protected final Class<?> findLoadedClass(String name) {

  2. if (!checkName(name))

  3. return null;

  4. return findLoadedClass0(name);

  5. }

  6.  

  7. private native final Class<?> findLoadedClass0(String name);

10 如何自定义ClassLoader

1.编写一个类继承ClassLoader

2.重写findClass 方法

3.在findClass中调用defineClass即可

 

11 Context ClassLoader 线程上下文类加载器

 

  1. public class Thread implements Runnable {

  2.  

  3. /* The context ClassLoader for this thread */

  4. private ClassLoader contextClassLoader;

  5.  

  6. public void setContextClassLoader(ClassLoader cl) {

  7. SecurityManager sm = System.getSecurityManager();

  8. if (sm != null) {

  9. sm.checkPermission(new RuntimePermission("setContextClassLoader"));

  10. }

  11. contextClassLoader = cl;

  12. }

  13.  

  14. public ClassLoader getContextClassLoader() {

  15. if (contextClassLoader == null)

  16. return null;

  17. SecurityManager sm = System.getSecurityManager();

  18. if (sm != null) {

  19. ClassLoader.checkClassLoaderPermission(contextClassLoader,

  20. Reflection.getCallerClass());

  21. }

  22. return contextClassLoader;

  23. }

  24. }

contextClassLoader只是一个成员变量,通过setContextClassLoader()方法设置,通过getContextClassLoader()获得。 

每个Thread都有一个相关联的ClassLoader,默认是AppClassLoader。并且子线程默认使用父线程的ClassLoader除非子线程特别设置

12 自定义ClassLoader的作用

常见的用法是将Class文件按照某种加密手段进行加密,然后按照规则编写自定义的ClassLoader进行解密,这样我们就可以在程序中加载特定了类,并且这个类只能被我们自定义的加载器进行加载,提高了程序的安全性

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值