类从被加载到虚拟机内存中开始,到卸载出内存为止,整个生命周期如下图所示。
类的实例化是指创建一个类的实例(对象)的过程;
类的初始化是指为类中各个类成员(被static修饰的成员变量)赋初始值的过程,是类生命周期中的一个阶段。
ClassLoader作用就是把.class文件加载到JVM中。JVM只认识.class,不认识.java。对于java文件,需要使用javac编译为.class。其他语言编写的程序,通过对应的编译器编译为合法的.class文件,JVM也是可以执行的。比如JRuby程序(.rb)通过jrubyc编译器,也可以编译为.class文件。
类与类加载器比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义。否则,即使这两个类来自同一个.class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
双亲委派模型(Parents Delegation Model)下文内容限于HotSpot,其他虚拟机实现可能有不同。
系统提供了3种类加载器,如下图所示。
启动类加载器(Bootstrap ClassLoader),该加载器由C++语言实现,无法被Java程序直接引用。负责加载核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和classes等。另外可以通过-Xbootclasspath来指定。
扩展类加载器(Extension ClassLoader),该加载器由Java语言实现,具体为sun.misc.Launcher$ExtClassLoader,可以直接使用该加载器。负责加载%JRE_HOME%\lib\ext目录中的,或者java.ext.dirs所指定目录中的类库。
应用程序类加载器(Application ClassLoader),该加载器由Java语言实现,具体为sun.misc.Launcher$AppClassLoader,可以直接使用该加载器。负责加载用户类路径上的类库。如果应用程序没有自定义类加载器,一般情况下这个就是程序中默认的类加载器。由于该加载器是ClassLoader.getSystemClassLoader()的返回值,该加载器也称为系统类加载器。
除顶层的启动类加载器外,其余类加载器都应有自己的父类加载器。扩展类加载器的父类加载器为启动类加载器,在Java中不可见,为null。
通过如下代码查看层次关系。
public class TestClassLoader {
public static void main(String[] args) {
ClassLoader cl = TestClassLoader.class.getClassLoader();
System.out.println(cl);
ClassLoader cl2 = cl.getParent();
System.out.println(cl2);
ClassLoader cl3 = cl2.getParent();
System.out.println(cl3);
}
}
输出
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1b701da1
null
各个加载器加载内容可通过以下代码查看
System.out.println(System.getProperty("sun.boot.class.path"));
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println(System.getProperty("java.class.path"));
双亲委派模型工作过程:
如果一个类加载器收到了类加载的请求,该加载器不会马上自己尝试加载这个类,而是会把这个请求委托给父类加载器去完成,每一个层次的类加载器都是如此。因此所有的加载请求最终都会传递到启动类加载器中,只有当父加载器返回自己无法完成这个加载请求,子加载器才会尝试自己加载。
源码如下:
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;
}
}
使用双亲委派模型的好处是,Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层次关系可以避免类的重复加载,当父类加载器已经加载了该类时,子类加载器就不用再加载一次,保证了Java的基础行为。
自定义类加载器在JDK 1.2以后,一般继承ClassLoader并重写findClass()。
package com.nowfox.commons.lang;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DiskClassLoader extends ClassLoader {
Logger logger = LoggerFactory.getLogger(DiskClassLoader.class);
private String path;
public DiskClassLoader(String path) {
this.path = path;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String fileName = getFileName(name);
File file = new File(path, fileName);
InputStream is = null;
try {
is = new FileInputStream(file);
byte[] data = new byte[is.available()];
is.read(data);
return defineClass(name, data, 0, data.length);
} catch (IOException e) {
logger.warn("读取文件异常", e);
throw new ClassNotFoundException(name);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
logger.warn("关闭文件异常", e);
}
}
}
}
private String getFileName(String name) {
int index = name.lastIndexOf('.');
if (index == -1) {
return name + ".class";
}
return name.substring(index + 1) + ".class";
}
}
定义一个接口
package com.nowfox.test.classloading;
public interface TestService {
void create();
}
实现这个接口
package com.nowfox.test.classloading;
public class TestClass implements TestService {
@Override
public void create() {
System.out.println("create();");
}
}
注意:编写完TestClass.java,产生TestClass.class(可手动使用javac编译)后,把TestClass.java(可删除)、TestClass.class移到其他目录下,本文为D:/classes目录下使用自定义ClassLoader
package com.nowfox.test.classloading;
public class TestClassLoader2 {
public static void main(String[] args){
ClassLoader loader = new DiskClassLoader("D:/classes/");
try {
Class<?> clazz = loader.loadClass("cn.antsaas.test.classloading.TestClass");
System.out.println(clazz.getClassLoader());
TestService testObject = (TestService) clazz.newInstance();
testObject.create();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
如果没有移除TestClass.java,运行TestClassLoader2,显示的是
sun.misc.Launcher$AppClassLoader@18b4aac2
create();
这是因为虽然使用了自定义类加载器,但是根据双亲委派模型,会先由父类加载器加载,DiskClassLoader的父类加载器就是sun.misc.Launcher$AppClassLoader。在没移除TestClass.java和TestClass.class时,AppClassLoader可以找到TestClass.class,因此由AppClassLoader加载。
如果移除了TestClass.java,运行TestClassLoader2,显示的是
com.nowfox.commons.lang.DiskClassLoader@5679c6c6
create();
说明我们自定义类加载器生效了。
破坏双亲委派模型到目前,双亲委派模型有3次较大规模的“被破坏”情况,JDK 1.2发布以前、线程上下文类加载器以及OSGi。
本文这里主要谈一下线程上下文类加载器。
JNDI、JDBC等目前是Java标准服务,称为服务提供者接口(SPI,Service Provider Interface),在rt.jar里,它的代码由启动类加载器加载。但是这些提供服务的具体实现类在应用的classpath下,启动类加载器不能直接加载这些具体实现类。
因此引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。可以通过java.lang.Thread类的setContextClassLoaderr()方法进行设置,默认继承父线程。
以JDBC为例
public class DriverManager {
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
///////
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
}
}
}
}
public final class ServiceLoader<S> implements Iterable<S>{
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
}
以上两段代码都位于rt.jar,可见加载具体实现类时是通过上下文类加载器来加载的。
参考1、周志明 《深入理解Java虚拟机》