Jvm提供了三大内置的类加载器,不同的类加载器负责将不同的类加载到内存之中
直接看代码:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* 自定义类加载器必须是ClassLoader的直接或者间接类
*
*
*/
public class MyClassLoader extends ClassLoader {
// 定义默认的Class存放路径
private final static Path DEFAULT_CLASS_DIR = Paths.get("/A" );
private final Path classDir ;
// 使用默认的路径
public MyClassLoader(){
super();
this.classDir = DEFAULT_CLASS_DIR ;
}
// 允许传入指定的class路径
public MyClassLoader(String classDir, ClassLoader parent){
super(parent);
this.classDir = Paths.get(classDir);
}
// 重写父类的findClass方法, 重要!!!!
@Override
protected Class> findClass(String name) throws ClassNotFoundException{
// 读取class的二进制数据
byte[] classBytes = this.readClassBytes(name);
if(null ==classBytes || classBytes.length == 0 ){
throw new ClassNotFoundException(" Can not load the class " + name ) ;
}
// 调用defineClass方法定义class
return this.defineClass(name,classBytes,0,classBytes.length );
}
// 读取class的二进制数据
private byte[] readClassBytes(String name) throws ClassNotFoundException {
String classPath = name.replace(".",",") ;
Path classFullPath = classDir.resolve(Paths.get(classPath+".class")) ;
if(!classFullPath.toFile().exists()){
throw new ClassNotFoundException(" The class " + name + "not found." ) ;
}
try(ByteArrayOutputStream baos = new ByteArrayOutputStream()){
Files.copy(classFullPath,baos);
return baos.toByteArray();
} catch (IOException e) {
throw new ClassNotFoundException(" load the class " + name +" error ." ,e) ;
}
}
@Override
public String toString(){
return "My ClassLoader " ;
}
}
public class HelloWord {
static{
System.out.println("Hello World Class is Initialized .");
}
public String welcome(){
return "Hello World" ;
}
}
记得将编译后的class文件放到DEFAULT_CLASS_DIR 对应的目录下。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MyClassLoaderTest {
public static void main(String[] args) throws Exception {
MyClassLoader classLoader = new MyClassLoader();
Class> aClass = classLoader.loadClass("com.zl.step10.HelloWord") ;
System.out.println(aClass.getClassLoader());
Object helloWorld = aClass.newInstance();
System.out.println(helloWorld);
Method welcomeMethod = aClass.getMethod("welcome") ;
String result = (String) welcomeMethod.invoke(helloWorld);
System.out.println("Result:"+result);
}
}
输出结果:
Connected to the target VM, address: '127.0.0.1:64540', transport: 'socket'
My ClassLoader
Hello World Class is Initialized .
com.zl.step10.HelloWord@2d6a9952
Result:Hello World
Disconnected from the target VM, address: '127.0.0.1:64540', transport: 'socket'
Process finished with exit code 0
如果一个类加载器收到了一个类加载请求,它不会自己去尝试加载这个类,而是把这个请求转交给父类加载器去完成。每一个层次的类加载器都是如此。因此所有的类加载请求都应该传递到最顶层的启动类加载器中,只有到父类加载器反馈自己无法完成这个加载请求(在它的搜索范围没有找到这个类)时,子类加载器才会尝试自己去加载。委派的好处就是避免有些类被重复加载。
public Class> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
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;
}
}
直接看
1.首先检查当前类加载器是否已经缓存过类。
2.如果存在发现父加载器,则调用父类的加载器的loadClass(name,false)方法对其进行加载。
3.如果当前类加载器不存在父类加载器,则直接调用根类加载器对该类进行加载。
4.如果当前类的所有父类都没有成功加载class ,则调用当前类加载器的findClass方法对其进行加载。(自定义类重写的方法)
5.最后如果类成功被加载,则做一些性能数据的统计
6.由于loadClass指定了resolve为false,所以不会进行连接阶段的继续执行。
resolveClass最终调用了一个本地方法做link,这里的link主要做了这么几步事情:
重写loadClass方法就好,代码如下:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* 破坏双亲委托机制
*
*
*/
public class BrokerDelegateClassLoader extends ClassLoader {
// 定义默认的Class存放路径
private final static Path DEFAULT_CLASS_DIR = Paths.get("/A" );
private final Path classDir ;
// 使用默认的路径
public BrokerDelegateClassLoader(){
super();
this.classDir = DEFAULT_CLASS_DIR ;
}
// 允许传入指定的class路径
public BrokerDelegateClassLoader(String classDir, ClassLoader parent){
super(parent);
this.classDir = Paths.get(classDir);
}
@Override
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 根据类的全局经名称进行加锁, 确保每一个类在多线程的情况下只被加载一次。
synchronized (getClassLoadingLock(name)) {
// 到已加载类的缓存中查看类是否已经被加载,如果已经加载则直接返回
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
// 首次加载 ,如果类的全路径以java 和javax开头,直接委托给系统类加载器对其加载
if(name.startsWith("java.") || name.startsWith("javax")){
try{
c = getSystemClassLoader().loadClass(name) ;
}catch (Exception e) {
// ignore
}
}else{
try {
c = this.findClass(name) ;
}catch (Exception e) {
// ignore
}
// 如果自定义类没有完成对类的记载,委托给父加载器或者系统类加载器进行加载
if(c == null ){
try {
if (getParent() != null) {
c = getParent().loadClass(name);
} else {
c = getSystemClassLoader().loadClass(name);
}
} catch (Exception e) {
// ignore
}
}
}
}
if(null == c){
// 无法加载,抛出异常
throw new ClassNotFoundException("The class : "+ name + " not found ." ) ;
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
// 重写父类的findClass方法, 重要!!!!
@Override
protected Class> findClass(String name) throws ClassNotFoundException{
// 读取class的二进制数据
byte[] classBytes = this.readClassBytes(name);
if(null ==classBytes || classBytes.length == 0 ){
throw new ClassNotFoundException(" Can not load the class " + name ) ;
}
// 调用defineClass方法定义class
return this.defineClass(name,classBytes,0,classBytes.length );
}
// 读取class的二进制数据
private byte[] readClassBytes(String name) throws ClassNotFoundException {
String classPath = name.replace(".","/") ;
Path classFullPath = classDir.resolve(Paths.get(classPath+".class")) ;
if(!classFullPath.toFile().exists()){
throw new ClassNotFoundException(" The class " + name + "not found." ) ;
}
try(ByteArrayOutputStream baos = new ByteArrayOutputStream()){
Files.copy(classFullPath,baos);
return baos.toByteArray();
} catch (IOException e) {
throw new ClassNotFoundException(" load the class " + name +" error ." ,e) ;
}
}
@Override
public String toString(){
return "My ClassLoader " ;
}
}
我们知道java中很可能出现类名相同的类,但是JVM却能正常的加载,是因为我们将相同的类名的类放在了不通的包(package)下面,这个也成为命名空间,每个类加载器都有自己的命名空间,命名空间是由该加载器以及所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的完整名字(包名+类名)相同的两个类;在不同的命名空间中,有可能出现类的完整名字相同的两个类。
由同一类加载器加载的属于相同包的类组成了运行时包。决定两个类是不是属于同一个运行时包,不仅要看他们的包名称是否相同,还要看定义类加载器是否相同。只有属于同一运行时包的类之间才能相互访问可见(默认访问级别)的类和成员。假设用户自定义了一个类java.lang.TestCase并由用于自定义的类加载器加载,由于java.lang.TestCase和核心类库java.lang.*由不同的类加载器加载,他们属于不同的运行时包,所以java.lang.TestCase不能访问核心库java.lang包中的包可见成员。
同一个命名空间内的类是相互可见的。
子类加载器的命名空间包含所有父类加载器的命名空间,因此由子类加载器加载的类能看见父类加载器加载的类,相反,由父类加载器加载的类不能看见子类加载器加载的类。如果两个加载器之间没有直接或者间接的父子关系,那么他们各自加载的类互不可见。
决定两个类是不是属于同一个运行时包,不仅要看它们的包名是否相同,还要看定义类加载器是否相同,只有属于同一运行时包的类才能互相访问包可见的类和类成员,这样的限制能避免用户自定义的类冒充核心类库的类区访问核心类库的包可见成员。