探究Java之ClassLoader

什么是ClassLoader

当我们写好一个Java程序之后,不是管是CS还是BS应用,都是由若干个.class文件组织而成的一个完整的Java应用程序,当程序在运行时,即会调用该程序的一个入口函数来调用系统的相关功能,而这些功能都被封装在不同的class文件当中,所以经常要从这个class文件中要调用另外一个class文件中的方法,如果另外一个文件不存在的,则会引发系统异常。而程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它class所引用。所以ClassLoader就是用来动态加载class文件到内存当中用的。

简单的说,就是java 的JVM怎么把.class加载到内存中,就是ClassLoader 这哥们负责的

加载时机

  1. 调用类构造器
  2. 调用类中静态的(static)变量或者静态方法

JVM自带的ClassLoader

     系统加载器AppClassLoader

     瞅瞅这哥们源码,在哪呢,/sun/misc/Launcher.java 

package sun.misc;
public class Launcher {

 .......
  static class AppClassLoader extends URLClassLoader {
  .....
  public static ClassLoader getAppClassLoader(final ClassLoader extcl)
            throws IOException
        {
            final String s = System.getProperty("java.class.path");
            final File[] path = (s == null) ? new File[0] : getClassPath(s);

            // Note: on bugid 4256530
            // Prior implementations of this doPrivileged() block supplied
            // a rather restrictive ACC via a call to the private method
            // AppClassLoader.getContext(). This proved overly restrictive
            // when loading  classes. Specifically it prevent
            // accessClassInPackage.sun.* grants from being honored.
            //
            return AccessController.doPrivileged(
                new PrivilegedAction<AppClassLoader>() {
                    public AppClassLoader run() {
                    URL[] urls =
                        (s == null) ? new URL[0] : pathToURLs(path);
                    return new AppClassLoader(urls, extcl);
                }
            });
        }
}

看源码得知,AppClassLoader 加载的是系统属性"java.class.path"配置下类文件,也就是环境变量CLASS_PATH配置的路径。因此

我们可以知道,AppClassLoader是面向用户的加载器,我们平时写的代码和引用的第三方jar包,都是用它来加载的。

不信?走几步,瞧瞧,

public class TestClass {

	public static void main(String[] args) {
		ClassLoader cl = TestClass.class.getClassLoader();
		System.out.println("===cl is=====>" + cl);

		ClassLoader parent = cl.getParent();
		System.out.println("===parent is=====>" + parent);

		ClassLoader bootStrap = parent.getParent();
		System.out.println("====bootStrap is ====>" + bootStrap);
	}

}

来,走两步,看结果

===cl is=====>sun.misc.Launcher$AppClassLoader@4e25154f
===parent is=====>sun.misc.Launcher$ExtClassLoader@33909752
====bootStrap is ====>null

惊不惊喜意不意外,我们看到TestClass是被AppClassLoader加载进来的,而AppClassLoader的parent是ExtClassLoader。ExtClassLoader 的parent却为null。那我们看看,ExtClassLoader是个什么鬼

      扩展类加载器ExtClassLoader

源码,还是上面的路径/sun/misc/Launcher.java 

package sun.misc;
public class Launcher {
..........
static class ExtClassLoader extends URLClassLoader {

...........

        /**
         * create an ExtClassLoader. The ExtClassLoader is created
         * within a context that limits which files it can read
         */
        public static ExtClassLoader getExtClassLoader() throws IOException
        {
            final File[] dirs = getExtDirs();

            try {
                // Prior implementations of this doPrivileged() block supplied
                // aa synthesized ACC via a call to the private method
                // ExtClassLoader.getContext().

                return AccessController.doPrivileged(
                    new PrivilegedExceptionAction<ExtClassLoader>() {
                        public ExtClassLoader run() throws IOException {
                            int len = dirs.length;
                            for (int i = 0; i < len; i++) {
                                MetaIndex.registerDirectory(dirs[i]);
                            }
                            return new ExtClassLoader(dirs);
                        }
                    });
            } catch (java.security.PrivilegedActionException e) {
                throw (IOException) e.getException();
            }
        }

  /*
         * Creates a new ExtClassLoader for the specified directories.
         */
        public ExtClassLoader(File[] dirs) throws IOException {
            super(getExtURLs(dirs), null, factory);
        }

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

.........
}

可以看到ExtClassLoader加载系统属性为"java.ext.dirs"下的文件,我们打印一下,看这属性下有哪些文件:

System.out.println("====dirs files ====>" + System.getProperty("java.ext.dirs"));

结果为:

====dirs files ====>/Users/hello/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java

启动类加载器BootStrapClassLoader

BootstrapClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用,JVM启动时通过Bootstrap类加载器加载rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加载。然后呢,我们前面已经分析了,JVM初始化sun.misc.Launcher并创建ExtClassLoader和AppClassLoader实例。并将ExtClassLoader设置为AppClassLoader的父加载器。Bootstrap没有父加载器,但是它却可以作用一个ClassLoader的父加载器。比如ExtClassLoader。这也可以解释之前通过ExtClassLoader的getParent方法获取为Null的现象

双亲委派模式

作为富家子弟,手里有三大宝贝,打妖怪的时候,JVM是如何知道使用哪个宝贝呢?

所谓双亲委派模式就是,当类加载器收到加载类或资源的请求时,通常都是先委托给父类加载器加载,也就是说,只有当父类加载器找不到指定类或资源时,自身才会执行实际的类加载过程。

上阵父子兵,先父亲上,父亲搞不定,儿子再上(典型的坑爹类型),上源码

package java.lang;
 
public abstract class ClassLoader {

    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;
        }
    }
}

我们可以看到第10行,判断是否加载完成,如果加载完成,直接返回c,如果首次加载,那c肯定为null,14行,判断parent是否为null,不为null,parent去加载。

有点乱哈,我们来理一理:

TestClass tc =  new TestClass();

第一步:先AppClassLoader执行loadClass,走它的parent ExtClassLoader上,结果ExtClassLoader执行loadClass的时候,一想,哦,我自己虽然是AppClassLoader的parent,但是我还有我的parent BootStrapClassLoader.让他去执行。

第二步:BootStrapClassLoader执行的目录下找不到TestClass,又返回到ExtClassLoader自己

第三步:ExtClassLoader执行的目录下也没有找到TestClass,所以返回到AppClassLoader

第四步:爹坑不动,还是自己上,28行,执行 c = findClass(name);并加载到内存中

自定义ClassLoader

JVM自带的三个ClassLoader只能加载特定目录下的文件,如果想加载其他路径下的文件,比如磁盘下保存的.class文件(动态加载.class这就是热修复的基础)

1.新建一个类UserInfo

public class UserInfo {
	public void printUserInfo(String name, Integer age) {
		System.out.println("用户名:" + name + ";年龄:" + age);
	}
}

2.新建一个类继承ClassLoader,重写findClass方法,并调用defineClass创建Class.

package com.whm.core;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class DiskClassLoader extends ClassLoader {
	private String fileName;

	public DiskClassLoader(String fileName) {
		this.fileName = fileName;
	}

	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		String newFileName = fileName + name + ".class";
		byte[] classBytes = null;
		Path path;
		try {
			path = Paths.get(new File(newFileName).toURI());
			classBytes = Files.readAllBytes(path);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return defineClass(name, classBytes, 0, classBytes.length);
	}
}

3.TestClass类

package com.whm.core;

import java.lang.reflect.Method;

public class TestClass {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		DiskClassLoader classLoader = new DiskClassLoader(
				"/Users/hello/Desktop/eclipseProject/workspace/JavaProject/src/com/whm/core/");
		try {
			Class clazz = classLoader.loadClass("com.whm.core.UserInfo");
			Method method = clazz.getDeclaredMethod("printUserInfo", String.class, Integer.class);
			Object obj = clazz.newInstance();
			method.invoke(obj, "张三", 20);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

文件路径、包名+类名、方法名、反射执行,看结果: 

用户名:张三;年龄:20

搞定!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值