经过前面的实例演示,我们弄清楚了以下几点:
java 类加载分为
1、启动类加载器,也叫根类加载器(BootStrapClassLoader),负责加载java核心api,如rt.jar等。需要清除的是它不是java.lang.ClassLoader的子类,它是JVM自身内部由C/C++实现的,并不是Java实现的。
2、扩展类加载器(Launcher$ExtClassLoader),负责加载扩展目录(%JAVA_HOME%/jre/lib/ext)下的jar包,我们可以自己打包放到此目录下,扩展java核心api以外的功能。ExtClassLoader的父类加载器是启动类加载器,特别注意的是它与启动类加载器并不是子类与父类的关系。
3、系统类加载器或称为应用程序类加载器(Launcher$AppClassLoader),是加载CLASSPATH环境变量所指定的jar包与类路径。一般来说,我们自定义的类就是由APP ClassLoader加载的。同样它的父加载器是扩展类加载器,但并不是扩展类加载器的子类。
4、用户自定义加载器,用于加载上面三种路径以外的jar或者class。父类加载器为应用类加载。
java 类加载机制
双亲委托加载机制,即当一个类加载收到加载类请求时,它不会马上去加载类,而是先委托自己的父类加载器去加载,一致委托到启动类加载,然后开始从启动类加载器去加载这个类,如果启动类加载器加载失败,再反馈给子类加载器,直到加载完成,如果回到类加载发起的加载器时还未加载成功,则抛出classNotFound异常。
双亲委托的作用:
1、避免重复加载,如果父类加载器加载了该类,子类便不会再去加载。
2、安全,避免用户修改java核心api,如String类。
现在已经知其然,当然就要知其所以然,那就得看源码。在之前的实例中,我们获取加载器都是通过Class的getClassLoader()方法,class主要源码如下:
/**
* Returns the class loader for the class. Some implementations may use
* null to represent the bootstrap class loader. This method will return
* null in such implementations if this class was loaded by the bootstrap
* class loader.
*
* <p> If a security manager is present, and the caller's class loader is
* not null and the caller's class loader is not the same as or an ancestor of
* the class loader for the class whose class loader is requested, then
* this method calls the security manager's {@code checkPermission}
* method with a {@code RuntimePermission("getClassLoader")}
* permission to ensure it's ok to access the class loader for the class.
*
* <p>If this object
* represents a primitive type or void, null is returned.
*
* @return the class loader that loaded the class or interface
* represented by this object.
* @throws SecurityException
* if a security manager exists and its
* {@code checkPermission} method denies
* access to the class loader for the class.
* @see java.lang.ClassLoader
* @see SecurityManager#checkPermission
* @see java.lang.RuntimePermission
*/
@CallerSensitive
public ClassLoader getClassLoader() {
ClassLoader cl = getClassLoader0();
if (cl == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
}
return cl;
}
// Package-private to allow ClassLoader access
ClassLoader getClassLoader0() { return classLoader; }
// Initialized in JVM not by private constructor
// This field is filtered from reflection access, i.e. getDeclaredField
// will throw NoSuchFieldException
private final ClassLoader classLoader;
我们可以很清楚的看到,在getClassLoader方法的注释是这样说的:
返回这个类的加载器,有些实现可能会用null来代表启动类加载器(BootStrap ClassLoader)。如果这个类是由启动类加载器(BootStrap ClassLoader)加载,则这个方法将返回null。
其次我们根据代码可以看出,类的加载器是通过 不可变变量 classLoader 绑定,注释中说它是由JVM初始化,并不是有class的构造器初始化的,而且它也被反射方法getDeclaredField过滤掉了,getDeclaredField方法不能访问到它。所以这里ClassLoader的初始化,我们这里暂不做讨论(主要是自己太菜,不会玩JVM)。
我们再来看ClassLoader类本身,附上部分代码
public abstract class ClassLoader {
/**
*Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
* #findClass(String)}, rather than this method. </p>
*ClassLoader的子类建议重写findClass方法,而不是loadClass,除了重写,此方法还
*/
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 先检查此类是否已经加载
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) {
//如果在非启动类的父类加载器中没有找到,抛出异常
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
//如果仍就没有找到该类,再执行findClass 方法去加载该类
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);//解析类,属于类加载的link阶段,类的加载过程必须link
}
return c;
}
}
//可知在这里,findClass方法直接抛出异常,方法的实现需要ClassLoader的子类去实现。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
}
通过上面的代码,我们也就明白自定义加载器中只重写了findClass方法。
因为启动类加载器是再jvm中由C++实现,我们能看源码的就只有扩展类加载器以及系统类加载器,其实细心的同学应该在
ClassLoader 深入解析学习笔记(一)中的第一个实例,控制台输入的结果中看到,ExtClassLoader 以及AppClassLoader都是Launcher的内部类。
sun.misc.Launcher$AppClassLoader@6d06d69c
sun.misc.Launcher$ExtClassLoader@2503dbd3
null
所以我们来看Launcher的源码:
public class Launcher {
private ClassLoader loader;
public Launcher() {
//创建扩展类加载器
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader", e);
}
// 创建应用类加载器即系统类加载器
try {
loader = AppClassLoader.getAppClassLoader(extcl);//把扩展类加载器作为AppClassLoader的父类加载器
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader", e);
}
// 为原始线程创建上下文类加载器,这里系统默认的上下文加载器就是系统类加载器。
Thread.currentThread().setContextClassLoader(loader);
// Finally, install a security manager if requested
String s = System.getProperty("java.security.manager");
if (s != null) {
SecurityManager sm = null;
if ("".equals(s) || "default".equals(s)) {
sm = new java.lang.SecurityManager();
} else {
try {
sm = (SecurityManager)loader.loadClass(s).newInstance();
} catch (IllegalAccessException e) {
} catch (InstantiationException e) {
} catch (ClassNotFoundException e) {
} catch (ClassCastException e) {
}
}
if (sm != null) {
System.setSecurityManager(sm);
} else {
throw new InternalError(
"Could not create SecurityManager: " + s);
}
}
}
/*
* 用于加载扩展类
*/
static class ExtClassLoader extends URLClassLoader {
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();
}
}
private static File[] getExtDirs() {
//扩展类加载 加载文件路径,系统配置文件中java.ext.dirs
String s = System.getProperty("java.ext.dirs");
//运行System.out.println(System.getProperty("java.ext.dirs"));
//输出结果 C:\Program Files\Java\jdk1.8.0_171\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
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;
}
.........
//应用类加载器
static class AppClassLoader extends URLClassLoader {
public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException
{
final String s = System.getProperty("java.class.path");
//打印s 得到
//D:\workspace\classLoaderSourceCodeStudy\bin;D:\Mye2017\plugins\org.junit_4.12.0.v201504281640\junit.jar;
//d:\Mye2017\plugins\org.hamcrest.core_1.3.0.v201303031735.jar;
//D:/Mye2017/configuration/org.eclipse.osgi/852/0/.cp/;/D:/Mye2017/configuration/org.eclipse.osgi/851/0/.cp/
//其实就是当前工程的目录,里面存放在class文件。
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);
}
});
}
}
}
即Launcher 构造器中先给扩展器加载初始化,然后再扩展加载器作为应用类加载器的父类加载器并初始化。最后再设置上下文加载器,每个线程都有一个ContextClassLoader。
通过源码我们知道了AppClassLoder以及ExtClassloader 都是Launcher的内部类,都继承自URLClassLoader,还有就是这里多出了个上下文加载器,不是说加载器只有三种吗?答案是,我们去看Thread源码发现,其实是ClassLoader作为Thread的一个成员变量,并不是新的加载器,而是一种概念,其默认值为AppClassLoader(Launcher源码中初始化时,用AppClassLoader传值)。
最后附上看源码网址,真心不错
http://www.grepcode.com/。
Launcher的源码就是在里面看到的,我也是找了许久才找到。