1 类与类加载器
类加载:通过一个类的全限定名来获取描述此类的二进制字节流
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确定其在Java虚拟机中的唯一性。换句话说,如果两个类来源于同一个class文件,但是由不同的类加载器加载,那么这就是两个不同的类
1.1 代码验证
package basicKnowledge.jvm.classload;
import sun.misc.Launcher;
import java.io.IOException;
import java.io.InputStream;
/**
* @基本功能:
* @program:summary
* @author:peicc
* @create:2019-08-13 10:57:04
**/
public class TestClassLoader {
public static void main(java.lang.String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
ClassLoader myLoader=new ClassLoader() {
@Override
public Class<?> loadClass(java.lang.String name) throws ClassNotFoundException {
java.lang.String fileName=name.substring(name.lastIndexOf(".")+1)+".class";
InputStream is=getClass().getResourceAsStream(fileName);
if(is==null){
return super.loadClass(name);
}
try {
byte[] b=new byte[is.available()];
is.read(b);
return defineClass(name,b,0,b.length);
}
catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
//利用自定义的类加载器加载该类
Class clas=myLoader.loadClass("basicKnowledge.jvm.classload.TestClassLoader");
//通过该类实例化对象
Object obj=clas.newInstance();
//输出该对象所属的类
System.out.println(obj.getClass());
//判断该对象是否由basicKnowledge.jvm.classload.TestClassLoader类实例化而来
System.out.println(obj instanceof TestClassLoader);
//输出该对象所属的类的类加载器
System.out.println(obj.getClass().getClassLoader());
//输出basicKnowledge.jvm.classload.TestClassLoader类的类加载器
System.out.println(basicKnowledge.jvm.classload.TestClassLoader.class.getClassLoader());
}
}
1.2 运行结果
class basicKnowledge.jvm.classload.TestClassLoader
false
basicKnowledge.jvm.classload.TestClassLoader$1@591f989e
sun.misc.Launcher$AppClassLoader@18b4aac2
1.3 结果分析
- 第一个输出结果很好理解,obj对象是类basicKnowledge.jvm.classload.TestClassLoader实例化得到的对象
- 第二个输出结果可能不好理解,obj对象在与类basicKnowledge.jvm.classload.TestClassLoader进行类型检查时,输出了false。也就是说obj并非basicKnowledge.jvm.classload.TestClassLoader实例化得到的对象,貌似与第一个输出结果矛盾。
- 接着,我们通过第三、四个输出结果对obj所属类的类加载器以及basicKnowledge.jvm.classload.TestClassLoader类的类加载器进行了输出,可以发现,两者的类加载器不同,所以类型检查时输出了false
- 在java虚拟机中存在两个TestClassLoader,一个由系统应用程序类加载器加载,另一个由自定义的类加载器myLoader加载。虽然来自同一个字节码文件,但依然是两个独立的类。
2 双亲委派模型
2.1 类加载器分类
从JVM的角度分:
- 启动类加载器(Bootstrap ClassLoader):C++实现,是JVM的一部分
- 其他类加载器:java语言实现,独立于虚拟机,全部继承自java.lang.ClassLoader
从java开发人员的角度:
- 启动类加载器(Bootstrap ClassLoader):负责加载<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中,并且是JVM所识别的类(仅按照文件名识别,如rt.jar,名字不符合的类库及时放在lib目录中也不会被被加载)。开发人员无法直接使用启动类加载器,用户在编写类加载器时,如果需要把加载请求委派给引导类加载器,直接使用null代替即可。
- 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。开发人员可以直接使用扩展类加载器
- 应用程序类加载器(Application ClassLoader):负责加载用户路径(ClassPath)上所指定的类,一般情况下为程序的默认类加载器
2.2 双亲委派模型
- 上图所示的类加载器的层次关系称为类加载器的双亲委派模型。
- 除了Bootstrap ClassLoader外,其余的类加载器都有自己的父类加载器(这里的父子关系以组合的方式体现)
2.3 双亲委派的工作过程
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载
3 扩展:能否自己写一个String类
这是一个面试可能被问的问题,网上关于此问题的答案有不统一,有说可以的,也有说不行的。事实情况究竟是什么样的呢?经过我亲自测试,我觉得应该分情况讨论:
如果新建一个java.lang包,然后在里面自己定义一个String,那么我告诉你是不行的。网上很多答案都说可以,这些答案认为可以自己实现一个String,但由于双亲委派机制,自己实现的String只是无法被加载运行,因此启动类加载器在加载的时候会加载rt.jar中的String,而自己实现的String无法被使用到。但经过我测试,发现不仅不会被使用到,而且还会出现运行时的异常。进一步分析,只要你自己新建java包,这里面的类就不会被加载通过。
代码测试
package java.lang;
/**
* @基本功能:
* @program:untitled
* @author:peicc
* @create:2019-08-13 14:38:57
**/
public class String {
public static void main(String[] args) {
System.out.println();
}
}
运行结果
错误: 在类 java.lang.Object 中找不到 main 方法, 请将 main 方法定义为:
public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application
如果新建一个其他包,然后在其他包里去自定义一个String,那么我告诉你是可以的。
自定义String类
package basicKnowledge.jvm.classload;
/**
* @基本功能:
* @program:summary
* @author:peicc
* @create:2019-08-13 13:15:34
**/
public class String {
public String() {
System.out.println("自定义String");
}
}
测试类
package basicKnowledge.jvm.classload;
/**
* @基本功能:
* @program:summary
* @author:peicc
* @create:2019-08-13 15:10:06
**/
public class Test {
public static void main(java.lang.String[] args) {
String a=new String();
}
}
运行结果
自定义String