双亲委派机制:当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
类加载器从上往下依次是:
-
BootstrapClassLoader(启动类加载器)
c++编写,加载java核心库 java.*,无法直接获取到启动类加载器的引用,不允许直接通过引用进行操作。
-
ExtClassLoader (标准扩展类加载器)
java编写,加载扩展库,如classpath中的jre ,javax.*,可以通过引用获取。
-
AppClassLoader(系统类加载器)
应用类加载器,一般是用户自己写的类。
案例分析
date.getClass().getClassLoader()返回的是null,原因是BootstrapClassLoader是c++写的,无法被引用到。
import com.sun.java.accessibility.util.Translator;
import java.util.Date;
public class JVMDemo01 {
public static void main(String[] args) {
//rt.jar下面的类,java.util包下,由BootstrapClassLoader进行加载
Date date = new Date();
System.out.println(date.getClass().getClassLoader());
System.out.println("================================");
//jre lib下ext目录中的类,由ExtClassLoader进行加载
Translator translator = new Translator();
System.out.println(translator.getClass().getClassLoader());
System.out.println("================================");
//自己写的类,由AppClassLoader进行加载
JVMDemo01 jvmDemo01 = new JVMDemo01();
System.out.println(jvmDemo01.getClass().getClassLoader());
System.out.println(jvmDemo01.getClass().getClassLoader().getParent());
System.out.println(jvmDemo01.getClass().getClassLoader().getParent().getParent());
}
}
输出结果:
null
================================
sun.misc.Launcher$ExtClassLoader@330bedb4
================================
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@330bedb4
null
源码分析
ClassLoader是一个抽象类,AppClassLoader和ExtClassLoader继承自URLClassLoader,URLClassLoader又继承自SecureClassLoader,SecureClassLoader继承自ClassLoader。
public abstract class ClassLoader {
// The parent class loader for delegation
// Note: VM hardcoded the offset of this field, thus all new fields
// must be added *after* it.
//不是继承关系,而是组合parent
private final ClassLoader parent;
//
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
//
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 {
//进入递归,这里注意如果parent是BootstrapClassLoader
//则parent无法引用,也就是说parent==null
if (parent != null) {
//有parent,且parent不是BootstrapClassLoader
c = parent.loadClass(name, false);
} else {
//BootstrapClassLoader返回找到的类,或者找不到返回null
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;
}
}
//调用本地方法在BootstrapClassLoader中查找类
private Class<?> findBootstrapClassOrNull(String name)
{
if (!checkName(name)) return null;
return findBootstrapClass(name);
}
// return null if not found
private native Class<?> findBootstrapClass(String name);
}
递归结束也没找到,就调用findClass方法。findClass方法应该被重写:
/**
* Finds the class with the specified <a href="#name">binary name</a>.
* This method should be overridden by class loader implementations that
* follow the delegation model for loading classes, and will be invoked by
* the {@link #loadClass <tt>loadClass</tt>} method after checking the
* parent class loader for the requested class. The default implementation
* throws a <tt>ClassNotFoundException</tt>.
*
* @param name
* The <a href="#name">binary name</a> of the class
*
* @return The resulting <tt>Class</tt> object
*
* @throws ClassNotFoundException
* If the class could not be found
*
* @since 1.2
*/
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
在URLClassLoader找到了重写的findClass方法,找到并加载类,找不到就抛出ClassNotFoundException。
/**
* Finds and loads the class with the specified name from the URL search
* path. Any URLs referring to JAR files are loaded and opened as needed
* until the class is found.
*
* @param name the name of the class
* @return the resulting class
* @exception ClassNotFoundException if the class could not be found,
* or if the loader is closed.
* @exception NullPointerException if {@code name} is {@code null}.
*/
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
双亲委派机制的好处
- 防止重复加载。
- 保证核心class不被篡改。