类加载机制上篇 https://blog.csdn.net/qq_36144187/article/details/81332970
概要
- 类加载器分为 启动类加载器(Bootstrap)、拓展类加载器(Extension)、应用程序类加载(Application/SystemApp)、自定义类加载器。
- 不同的类加载器加载的Class的类类型不相等。
- 类加载器采用双亲委派模型,即从上到下依次加载。
类加载器分类
启动类加载器(Bootstrap):由C++实现,负责将 <JAVA_HOME>/lib
路径下的核心类库或-Xbootclasspath
参数指定的路径下的jar包加载到内存中。注意,为了安全只接受特定名称的jar包,没有父类加载器。
拓展类加载器(Extension):Sun公司使用java实现的sun.misc.Launcher$ExtClassLoader
类,负责加载<JAVA_HOME>/lib/ext
目录下或者由系统变量-Djava.ext.dir指定位路径中的类库。父加载器为NULL。
应用程序类加载器(Application): Sun公司使用java实现的sun.misc.Launcher$AppClassLoader
。它负责加载系统类路径java -classpath
或-D java.class.path
指定路径下的类库,也就是我们经常用到的classpath路径,父加载器为ExtClassLoader。
自定义类加载器:由开发者自己编写的加载器,父类加载器为AppClassLoader。
假如你需要定义自定义类加载器就可以用以下方法,只需继承自ClassLoader,重载findClass方法。
public class MyClassLoader extends ClassLoader
{
private Static String Fileurl="C:\";
MyClassLoader(String Fileurl){
this.Fileurl=Fileurl;
}
//重载findClass方法即可
protected Class<?> findClass(String name) throws ClassNotFoundException
{
File file = getClassFile(name);
try
{
byte[] bytes = getClassBytes(file);
Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
return c;
}
catch (Exception e)
{
e.printStackTrace();
}
return super.findClass(name);
}
//获取文件位置
private File getClassFile(String name)
{
File file = new File(Fileurl);
return file;
}
//获取文件二进制流
private byte[] getClassBytes(File file) throws Exception
{
// 这里要读入.class的字节,因此要使用字节流
FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel wbc = Channels.newChannel(baos);
ByteBuffer by = ByteBuffer.allocate(1024);
while (true)
{
int i = fc.read(by);
if (i == 0 || i == -1)
break;
by.flip();
wbc.write(by);
by.clear();
}
fis.close();
return baos.toByteArray();
}
}
类加载器命名空间
对于任意一个类,都需要加载它的类加载器和这个类本身一同确立在JVM中的唯一性,每个类加载器,都拥有一个独立的类名称空间。可以影响equals(),isAssignableFrom(),isInstance()方法的返回值。
以下测试类展现了该特性。
//复用了以上的MyClassLoader类
public class TestClassLoader {
public static void main(String[] args) {
try {
MyClassLoader mcl =new MyClassLoader("F:/Person.class");
MyClassLoader mcl1 =new MyClassLoader("F:/Person.class");
Class<?> C1=Class.forName("com.xrq.classloader.Person", true, mcl);
Class<?> C2=Class.forName("com.xrq.classloader.Person", true, mcl1);
System.out.println("两个加载器是否相同:"+mcl.equals(mcl1));
System.out.println("两个类是否相同:"+C1.equals(C2));
Object person=cpreson.newInstance();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/*\
output:
两个加载器是否相同:false
两个类是否相同:false
*/
注意,请不要使用java源码中的类测试,有可能触发双亲委派机制,导致加载的类相同。
双亲委派模型
正如上文所说的,每个类加载器都有自己的父加载器,同时如果Class 由不同的类加载会影响某些关键判断语句,所以双亲委派机制应运而生,这样可以有效避免一个Class被加载两次。
父类加载器和类加载器类,他们并不是继承关系,查看源码可知:
//节选自Java源码:sun.misc.Launcher
public class Launcher {
static class AppClassLoader extends URLClassLoader {
//详细内容略
}
static class ExtClassLoader extends URLClassLoader {
//详细内容略
}
}
他们都是launcher的静态内部类,且都继承自URLClassLoader,并不是继承关系。总体继承关系为下图:
此图出自 https://blog.csdn.net/briblue/article/details/54973413
以下部分java源码展示了,ExtClassLoader的父加载器类为null,和AppClassLoader的父加载器为ExtClassLoader的事实。以及ExtClassLoader的父加载器虽然为null,但如何交由BootstrapClassLoader的过程。
//sun.misc.Launcher
public Launcher() {
// Create the extension class loader
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader", e);
}
// Now create the class loader to use to launch the application
try {
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader", e);
//省略部分
}
//经过一顿查找之后,发现以下部分,可以部分代表双亲委派机制。
//java.lang.Classloader
protected synchronized Class<?> loadClass(String name, boolean resolve){
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
//优先交给父加载类
if (parent != null) {
c = parent.loadClass(name, false);
}
//不然交由BootstrapClass加载器加载
else {
c = findBootstrapClass(name);
}
} catch (ClassNotFoundException e) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
但是双亲委托模型并不是一个强制性约束的模型。
1.继承ClassLoader类但是重写LoadClass方法。
2.为了JNDI服务而产生的ContextClassLoader。
3.为了热部署(hotSwap)替换每个Bundle(程序模块)时,连同类和类加载器一起替换。
部分内容引用地址
https://blog.csdn.net/briblue/article/details/54973413
https://www.cnblogs.com/szlbm/p/5504631.html
《深入理解JVM》 周志明