自上而下有如下:
-Xbootclasspath
改变其路径。-Djava.ext.dirs
指定目录。ClassLoader.getSystemClassLoader()
,通过-Djava.system.class.loader
修改。什么是双亲委派?
虚拟机使用类加载器加载类,但是如上所示有很多类加载器。当一个类加载器收到了类加载的请求的时候,他不会直接去加载指定的类,而是把这个请求委托给自己的父加载器去加载(比如 BootstrapClassLoader 去加载rt.jar的类)。只有父加载器无法加载这个类的时候,才会由当前这个加载器来负责类的加载。
那么双亲委派是如何实现的?需要从下面方法找答案
public abstract class ClassLoader {
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 { // 从下文可以知道,ExtClassLoader 会走这里
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 从父加载器没加载到,才调用当前加载器的 findClass 加载类
// 具体实现参考 URLClassLoader 的 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);
}
return c;
}
}
}
由源码可以看到,AppClassLoader、ExtClassLoader都是继承于URLClassLoader,和 BootstrapClassLoader 没什么关系。那这里的parent又是指什么呢?
public abstract class ClassLoader {
// 父加载器
private final ClassLoader parent;
public final ClassLoader getParent() {
if (parent ==null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// Check access to the parent class loader
// If the caller's class loader is same as this class loader,
// permission check is performed.
checkClassLoaderPermission(parent, Reflection.getCallerClass());
}
return parent;
}
}
由代码可以知道,这里的父加载器不是继承关系,不是父类,而是当前类加载器的一个属性。
public class Test {
public static void main(String[] args) {
ClassLoader classLoader = Test.class.getClassLoader();
System.out.println(classLoader);
System.out.println(classLoader.getParent());
System.out.println(classLoader.getParent().getParent());
}
}
输出如下:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@7506e922
null
这里为什么获取不到 BootstrapClassLoader ?先看看parent是如何赋值的
public abstract class ClassLoader {
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
......
}
}
Launcher 作为入口,在构造函数中创建了ExtentionClassLoader和AppClassLoader
public class Launcher {
private static Launcher launcher = new Launcher();
private static String bootClassPath =
System.getProperty("sun.boot.class.path"); // -Xbootclasspath
private ClassLoader loader;
public Launcher() {
// Create the extension class loader
ClassLoader extcl;
extcl = ExtClassLoader.getExtClassLoader();
loader = AppClassLoader.getAppClassLoader(extcl);
// Also set the context class loader for the primordial thread.
// 设置AppClassLoader为线程上下文类加载器
Thread.currentThread().setContextClassLoader(loader);
......
}
}
继续看 ExtClassLoader 的创建
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();
}
}
public ExtClassLoader(File[] dirs) throws IOException {
super(getExtURLs(dirs), null, factory);
SharedSecrets.getJavaNetAccess().
getURLClassPath(this).initLookupCache(this);
}
private static File[] getExtDirs() {
// 通过系统变量 java.ext.dirs 获取需要加载的目录
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;
}
}
往上看构造函数
public class URLClassLoader extends SecureClassLoader implements Closeable {
public URLClassLoader(URL[] urls, ClassLoader parent,
URLStreamHandlerFactory factory) {
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
acc = AccessController.getContext();
ucp = new URLClassPath(urls, factory, acc);
}
}
public class SecureClassLoader extends ClassLoader {
protected SecureClassLoader(ClassLoader parent) {
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
initialized = true;
}
}
public abstract class ClassLoader {
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
}
由super(getExtURLs(dirs), null, factory);
可以很明显的看到 ExtClassLoader 给parent传的值就是null,所以也解释了为什么空指针。而且由于 BootstrapClassLoader 是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类。
由下面代码可以知道 AppClassLoader 设置了父加载器为 extcl 也就是 ExtClassLoader。
static class AppClassLoader extends URLClassLoader {
public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException
{
// 加载工作目录
final String s = System.getProperty("java.class.path");
return new AppClassLoader(urls, extcl);
}
}
说了这么多,双亲委派有哪些好处?
- 可以避免类的重复加载,因为都会先去看看父类有没有加载,加载就直接返回了。
- 安全性,比如 java.lang.String 只会由Bootstrap ClassLoader加载,可以防止代码被别人随意改动。
类加载器有几个比较重要的方法
当我们需要自定义ClassLoader的时候,可以先考虑是否要打破双亲委派。
如果要打破则重写其loadClass,修改其先从parent获取class的逻辑。
如果不需要打破双亲委派,则可以重写findClass方法,实现具体逻辑即可。
自定义加载器有很多使用场景,下面就列举部分
类的生命周期包括:加载、链接(验证、准备、解析、初始化)、使用、卸载7个阶段,其中前面4个作为其加载过程。
为类的静态变量(static修饰)分配内存,并初始化默认值(根据类型,null、false、0、0L,而非代码定义的值—因为值不一定已经初始化了)。
如果是static final同时修饰的,在javac编译时生成ConstantValue属性(可以理解为编译的时候已经放入常量池),这个时候直接赋值。
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和符号引用进行。
符号引用:一个java类会变编译为字节码文件,在编译时,java类并不知道所引用类的实际地址(也就是直接引用),所以要用符号引用来代替,在编译完成后,类加载时,在解析这一步来将符号引用来转变为直接引用。
直接引用:就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
对类变量进行初始化。初始化阶段是执行类构造器
方法的过程。
()
方法与类构造函数不一样,不需要显示调用父类构造函数,虚拟机会保证在子类的()
方法执行之前,父类的()
方法已执行完毕。()
主要针对static静态代码块,如果没有则不会生成()
()
对象构造方法是在new对象时候加载的,比如直接使用类的静态属性就不会触发