JVM 之 (14) 类加载器详解和双亲委派模型
类加载器 虚拟机设计团队把类加载阶段中“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的模块称为“类加载器”。类加载器分类 启动(Bootstrap)类加载器 启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,它负责
·
类加载器
虚拟机设计团队把类加载阶段中“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的模块称为“类加载器”。类加载器分类
启动(Bootstrap)类加载器启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,它负责将 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,注意必由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录下也是没有作用的(出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)。
扩展(Extension)类加载器
扩展类加载器是指Sun公司(已被Oracle收购)实现的sun.misc.Launcher$ExtClassLoader类,由Java语言实现的,是Launcher的静态内部类,它负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库,开发者可以直接使用标准扩展类加载器。
//ExtClassLoader类中获取路径的代码
private static File[] getExtDirs() {
//加载<JAVA_HOME>/lib/ext目录中的类库
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;
}
系统(System)类加载器/应用程序类加载器
指 Sun公司实现的sun.misc.Launcher$AppClassLoader。它负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。
自定义类加载器
//该加载器可以加载与自己在同一路径下的Class文件
public class MyClassLoader extends ClassLoader{
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException{
try {
String fileName=name.substring(name.lastIndexOf(".")+1)+".class";
InputStream is=getClass().getResourceAsStream(fileName);
if(is==null){
//不在当前路径下的类,例如Object类(JavaBean的父类),采用委派模型加载
return super.loadClass(name);
}else{
//在当前路径下的类,例如JavaBean类,直接通过自己加载其Class文件
byte[] b=new byte[is.available()];
is.read(b);
return defineClass(name,b,0,b.length);
}
} catch (IOException e) {
throw new ClassNotFoundException();
}
}
}
类的唯一性
对于任意一个类,都需要由加载它的类加载器和类的全限定名一同确定其在Java虚拟机中的唯一性。
只有被同一个类加载器加载的类才可能会想等。相同的字节码被不同的类加载器加载的类不想等。
public class ClassLoaderTest {
public static void main(String[] args) throws Exception{
ClassLoader myLoader=new MyClassLoader();
Object classLoaderTest=myLoader.loadClass("com.loader.ClassLoaderTest").newInstance();
System.out.println(classLoaderTest.getClass());
System.out.println(classLoaderTest instanceof ClassLoaderTest);
}
}
class com.loader.ClassLoaderTest
false
双亲委派模型
类加载器之间的层次关系,称为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动类加载器外,其余类加载器都应该有自己的父类加载器。注意,这里类加载器之间的父子关系一般不会以继承的关系实现,而是使用组合关系来复用父加载器的代码。
双亲委派工作原理
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该首先传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。 如果最初发起的类加载的类加载器也无法完成加载请求时候,将会抛出ClassNotFound,而不再调用子类的加载器去加载请求。双亲委派源码
类加载器均是继承自java.lang.ClassLoader抽象类。首先,我们看一看java.lang.ClassLoader类的loadClass()方法:
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 {
//父加载器为null,说明this为扩展类加载器的实例,父加载器为启动类加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果父加载器抛出ClassNotFoundException
// 说明父加载器无法完成加载请求
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 如果父加载器无法加载
// 调用本身的findClass方法来进行类加载
// 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;
}
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
..
if (parent != null) {//父加载器不为null,即父加载器为ClassLoader类型
c = parent.loadClass(name, false);//委派请求给父加载器
} else {//父加载器为null,说明this为扩展类加载器的实例
c = findBootstrapClassOrNull(name);//通过启动类加载器加载类
}
..
}
/**通过启动类加载器加载类
* Returns a class loaded by the bootstrap class loader;
* or return null if not found.
*/
private Class findBootstrapClassOrNull(String name)
{
if (!checkName(name)) return null;
return findBootstrapClass(name);
}
// return null if not found 启动类加载器通过本地方法加载类
private native Class findBootstrapClass(String name);
默认情况下,应用程序中的类由
应用程序类加载器
(
AppClassLoader
)加载。该加载器加载
系统类路径
下的类,因此一般也称为
系统类加载器
。
双亲委派优势
类加载器一起具备了一种带优先级的层次关系,越是基础的类,越被上层的类加载器锁加载,保证了java程序的稳定性。更多推荐
已为社区贡献9条内容
所有评论(0)