Java的类加载器在sun.misc.Launcher中初始化。
public Launcher() {
ExtClassLoader localExtClassLoader;
try {
localExtClassLoader = ExtClassLoader.getExtClassLoader();
}
catch(IOException localIOException1) {
throw new InternalError("Could not create extension class loader", localIOException1);
}
try {
this.loader = AppClassLoader.getAppClassLoader(localExtClassLoader);
}
catch(IOException localIOException2) {
throw new InternalError("Could not create application class loader", localIOException2);
}
Thread.currentThread().setContextClassLoader(this.loader);
String str = System.getProperty("java.security.manager");
if(str != null) {
SecurityManager localSecurityManager = null;
if(( "".equals(str) ) || ( "default".equals(str) ))
localSecurityManager = new SecurityManager();
else
try {
localSecurityManager = (SecurityManager) this.loader.loadClass(str).newInstance();
}
catch(IllegalAccessException localIllegalAccessException) {}
catch(InstantiationException localInstantiationException) {}
catch(ClassNotFoundException localClassNotFoundException) {}
catch(ClassCastException localClassCastException) {}
if(localSecurityManager != null)
System.setSecurityManager(localSecurityManager);
else
throw new InternalError("Could not create SecurityManager: " + str);
}
}
ExtClassLoader通过ExtClassLoader.getExtClassLoader()初始化。
AppClassLoader通过AppClassLoader.getAppClassLoader(ExtClassLoader)初始化。
public static ExtClassLoader getExtClassLoader() throws IOException {
File[] arrayOfFile = getExtDirs();
try {
return( (ExtClassLoader) AccessController.doPrivileged(new PrivilegedExceptionAction(arrayOfFile) {
public Launcher.ExtClassLoader run() throws IOException {
int i = this.val$dirs.length;
for( int j = 0; j < i; ++j ) {
MetaIndex.registerDirectory(this.val$dirs[j]);
}
return new Launcher.ExtClassLoader(this.val$dirs);
}
}) );
}
catch(PrivilegedActionException localPrivilegedActionException) {
throw( (IOException) localPrivilegedActionException.getException() );
}
}
getExtDirs找到系统配置System.getProperty("java.ext.dirs")中的路径下的所有文件,
让类加载器加载这些文件。
public ExtClassLoader( File[] paramArrayOfFile ) throws IOException {
super(getExtURLs(paramArrayOfFile), null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
ExtClassLoader初始化时将parent设置为null。
public static ClassLoader getAppClassLoader( ClassLoader paramClassLoader ) throws IOException {
String str = System.getProperty("java.class.path");
File[] arrayOfFile = ( str == null ) ? new File[0] : Launcher.access$200(str);
return( (ClassLoader) AccessController.doPrivileged(new PrivilegedAction(str, arrayOfFile, paramClassLoader) {
public Launcher.AppClassLoader run() {
URL[] arrayOfURL = ( this.val$s == null ) ? new URL[0] : Launcher.access$300(this.val$path);
return new Launcher.AppClassLoader(arrayOfURL, this.val$extcl);
}
}) );
}
AppClassLoader( URL[] paramArrayOfURL, ClassLoader paramClassLoader ) {
super(paramArrayOfURL, paramClassLoader, Launcher.factory);
this.ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
this.ucp.initLookupCache(this);
}
getAppClassLoader从System.getProperty("java.class.path")处获取要加载的文件,并在初始化时将类加载器的parent设置为ExtClassLoader。
类加载过程
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;
}
}
该函数逻辑为:
if 该类已经被当前加载器加载过
return;
else if 当前类加载器存在parent加载器
尝试是否parent加载器加载
else if 不存在parent加载器器
尝试使用BootstrapClassLoader加载器加载
if 父加载器无法加载该类
使用当前加载器加载。
因为AppClassLoader的parent为ExtClassLoader,而为ExtClassLoader的parent为null。依据上面的类加载过程,整个类的加载过程为
这种类加载的模式即为双亲委托模式,简单一句话描述就是:
类优先让父加载器加载,父加载器无法加载后才会自己加载。
委托机制的意义
防止内存中出现多份同样的字节码
比如两个类A和类B都要加载System类:
如果不用委托而是自己加载自己的,那么加载器A就会加载一份System字节码,然后加载器B又会加载一份System字节码,这样内存中就出现了两份System字节码。
如果使用委托机制,会递归的向父类查找,也就是首选用Bootstrap尝试加载,如果找不到再向下。这里的System就能在Bootstrap中找到然后加载,如果此时类B也要加载System,也从Bootstrap开始,此时Bootstrap发现已经加载过了System那么直接返回内存中的System即可而不需要重新加载,这样内存中就只有一份System的字节码了。
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
- 首先当前线程的类加载器去加载线程中的第一个类(假设为类A)。
注:当前线程的类加载器可以通过Thread类的getContextClassLoader()获得,也可以通过setContextClassLoader()自己设置类加载器。 - 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器去加载类B。
- 还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
违背双亲委派模式
当然双亲委派模式并不是强制要求,在有些情况下会打破双亲委派模式。
如Tomcat加载类过程。
commonLoader:类库可被Tomcat和所有的Web应用程序共同使用。
catalinaLoader:类库可被Tomcat使用,对所有的Web应用程序都不可见。
sharedLoader:类库可被所有的Web应用程序共同使用,但对Tomcat自己不可见。
webappclassLoader:每个Web应用程序单独使用,对其他web应用不可见。
如果有10个Web应用程序都是用Spring来进行组织和管理的话,可以把Spring放到Common或Shared目录下让这些程序共享。Spring要对用户程序的类进行管理,自然要能访问到用户程序的类,而用户的程序显然是放在/WebApp/WEB-INF目录中的,那么被CommonClassLoader或SharedClassLoader加载的Spring如何访问并不在其加载范围内的用户程序呢?
因为Spring的jar包在common或shared目录中,所以Spring的类加载器为commonLoader或者sharedLoader。而Spring中的注入的bean是在放在/WebApp/WEB-INF目录中的,根据 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器去加载类B,在Spring中引用web应用的类时,需要commonLoader或者sharedLoader加载去加载,而commonLoader或sharedLoader是无法加载到/WebApp/WEB-INF下的类的,这时候就需要commonLoader或者sharedLoader加载器来调用子加载器webappclassLoader来加载/WebApp/WEB-INF目录下的类,在实现时是使用线程上下文类加载器,可以实现父加载器对子加载器的逆向访问。
热部署
重新加载
ClassLoader中重要的方法
loadClass
ClassLoader.loadClass(...) 是ClassLoader的入口点。当一个类没有指明用什么加载器加载的时候,JVM默认采用AppClassLoader加载器加载没有加载过的class,调用的方法的入口就是loadClass(...)。如果一个class被自定义的ClassLoader加载,那么JVM也会调用这个自定义的ClassLoader.loadClass(...)方法来加载class内部引用的一些别的class文件。重载这个方法,能实现自定义加载class的方式,抛弃双亲委托机制,但是即使不采用双亲委托机制,比如java.lang包中的相关类还是不能自定义一个同名的类来代替,主要因为JVM解析、验证class的时候,会进行相关判断。
defineClass
系统自带的ClassLoader,默认加载程序的是AppClassLoader,ClassLoader加载一个class,最终调用的是defineClass(...)方法,这时候就在想是否可以重复调用defineClass(...)方法加载同一个类(或者修改过),最后发现调用多次的话会有相关错误:
...
java.lang.LinkageError
attempted duplicate class definition
...
所以一个class被一个ClassLoader实例加载过的话,就不能再被这个ClassLoader实例再次加载(这里的加载指的是,调用了defileClass(...)放方法,重新加载字节码、解析、验证。)。而系统默认的AppClassLoader加载器,他们内部会缓存加载过的class,重新加载的话,就直接取缓存。所与对于热加载的话,只能重新创建一个ClassLoader,然后再去加载已经被加载过的class文件。
class卸载
在Java中class也是可以unload。JVM中class和Meta信息存放在PermGen space区域。如果加载的class文件很多,那么可能导致PermGen space区域空间溢出。引起:java.lang.OutOfMemoryErrorPermGen space. 对于有些Class我们可能只需要使用一次,就不再需要了,也可能我们修改了class文件,我们需要重新加载 newclass,那么oldclass就不再需要了。那么JVM怎么样才能卸载Class呢。
JVM中的Class只有满足以下三个条件,才能被GC回收,也就是该Class被卸载(unload):
- 该类所有的实例都已经被GC。
- 加载该类的ClassLoader实例已经被GC。
- 该类的java.lang.Class对象没有在任何地方被引用。
GC的时机我们是不可控的,那么同样的我们对于Class的卸载也是不可控的。