JVM深入学习(二)类的加载和双亲委托机制

JVM深入学习(二)类的加载和自定义类加载器

  • 类加载器种类
  • 类初始化的步骤
  • 类加载器的父亲(双亲)委托机制
  • 类加载器流程图
    • 各个类加载器介绍
  • 系统类加载器源码分析(重点)
  • 分析代码classload
  • 获取ClassLoader途径
  • 类加载器源码分析(本节重点)
    • 自定义类加载器
    • 自定义类加载器继承ClaasLoader:
  • 结果分析(双亲委托机制)
  • 分析自定义类加载器自定义父加载器

类加载器种类

JVM深入学习(二)类的加载和双亲委托机制_第1张图片

在这里插入图片描述

与接口相反,例如子类没有主动使用到父类,但是类加载器会加载父类。但是对接口并不是
JVM深入学习(二)类的加载和双亲委托机制_第2张图片

类初始化的步骤

JVM深入学习(二)类的加载和双亲委托机制_第3张图片

初始化时机如下
JVM深入学习(二)类的加载和双亲委托机制_第4张图片

类加载器流程图:

JVM深入学习(二)类的加载和双亲委托机制_第5张图片
它们是包含关系,只要三个加载器中的一个加载成功就成功,不然就抛异常

类加载器的父亲(双亲)委托机制

JVM深入学习(二)类的加载和双亲委托机制_第6张图片

JVM深入学习(二)类的加载和双亲委托机制_第7张图片

流程:loader 加载器加载Sample类,自己并不会加载而是交给它的父类系统类加载器,发现此时加载器还有父类,继续网上传递给父类,一直到根类加载器;但是根类加载器并不能成功加载Sample类,继续往回交给扩展类加载器,发现不能解决,继续传给系统类加载器,最终由系统类加载器完成加载Sample类,返回结果给loader1加载器;

(只要这个过程中有一个类加载器加载成功,返回正确结果了)
一句话概括就是:
某个类想要加载特定的类,自己并不会去加载,而是一直交给父类加载,如果一直传动根类加载器都不能解决,就往回交给子类加载,这个过程中只要有一个类加载器成功就宣告加载动作成功,返回正确结果。

类加载器流程图

JVM深入学习(二)类的加载和双亲委托机制_第8张图片

各个类加载器介绍

分三种

JVM深入学习(二)类的加载和双亲委托机制_第9张图片

系统类加载器源码分析(重点)

getClassLoader

作用:返回针对于这个类的类加载器(真正加载这个类的类加载器),某些实现可能会使用null表示启动类加载器,如果这个类是由启动类(根加载器)加载器加载的就会返回null

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

//自定义类加载器
public class MyClassLoader extends ClassLoader {

    private String classLoaderName;
private String path;

private final String fileExtension = ".class";

public void setPath(String path) {
    this.path = path;
}

public MyClassLoader(){
    super();
}

public MyClassLoader(String classLoaderName) {
    super();//将系统类加载器当作该类加载器的父加载器
    this.classLoaderName = classLoaderName;
}

public MyClassLoader(ClassLoader parent, String classLoaderName) {
    super(parent); //显示指定该类加载器的父加载器 (可以自己自定义父加载器是谁)
    this.classLoaderName = classLoaderName;
}

@Override
public String toString() {
    return "[" + this.classLoaderName + "]";
}

//根据传入的name 寻找对应的Class
@Override
protected Class findClass(String classname) throws ClassNotFoundException {
    byte[] data=this.loadClassData(classname);
    //调用该方法可以返回Class对象,很重要的方法
    return this.defineClass(classname,data,0,data.length);
}

// 传入class文件的名字 返回类的字节数组
private byte[] loadClassData(String name) {
    InputStream is = null;
    byte[] data = null;
    ByteArrayOutputStream baos = null;

    name=name.replace(".",File.separator);

    try {
       // this.classLoaderName = this.classLoaderName.replace(".", "/");
        is = new FileInputStream(new File(this.path+name + this.fileExtension));
        baos = new ByteArrayOutputStream();
        int ch = 0;
        while (-1 != (ch = is.read())) {
            baos.write(ch);
        }
        data = baos.toByteArray();
    } catch (Exception ex) {
        ex.printStackTrace();
    } finally {
        try {
            is.close();
            baos.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    return data;
}

public static  void test(ClassLoader classLoader) throws Exception {
    Class clazz=classLoader.loadClass("com.xuge.MyClassLoader");
    Object object=clazz.newInstance(); //返回实例化对象
    System.out.println(clazz.getClassLoader());
}

public static  void main(String[]args) throws Exception {
    MyClassLoader loader1=new MyClassLoader("loader1");
    test(loader1);

}

}

分析代码classload

JVM深入学习(二)类的加载和双亲委托机制_第10张图片
在这里插入图片描述

分析:在这里插入图片描述
通过反射才是对类的主动使用

获取ClassLoader途径

//获得当前类的ClassLoder

clazz.getClassLoader();

//获取当前线程上下文的ClassLoader

Thread.currentThread().getContextClassLoader();

//获得系统的ClassLoader

ClassLoader.getSystemClassLoader();

//获得调用者的ClassLoader

DriverManager.getCallerClassLoader();

类加载器源码分析(本节重点)

ClassLoader是一个抽象类

Java的class类全是由类加载器加载的,但是针对数组类的class对象并不是由类加载器加载的,而是在运行时根据情况jvm动态生成的类型。但是它的类加载器是根据数组中的类决定的。如果数组中的类是原生类,那么它是没有类加载器的

自定义类加载器

源码:

ClassLoader方法中的:

findClass可以通过传入类文件名,得到一个类Class,

loadClassData方法,根据类的名字,获取到类的二进制字节数组

defineClass方法,可以把类的二进制字节数组构建成一个类返回

*         public Class findClass(String name) {
*             byte[] b = loadClassData(name);
*             return defineClass(name, b, 0, b.length);
*         }
*         private byte[] loadClassData(String name) {
*             // load the class data from the connection
*              . . .
*         }
*     }

自定义类加载器继承ClaasLoader:

  1. 继承抽象类 ClassLoader2. 重写findClass和loadClassData方法3. 重写构造方法,可以指定父加载器

代码如下:

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

//自定义类加载器
public class MyClassLoader extends ClassLoader {

    private String classLoaderName;
private final String fileExtension = ".class";

public MyClassLoader(){
    super();
}

public MyClassLoader(String classLoaderName) {
    super();//将系统类加载器当作该类加载器的父加载器
    this.classLoaderName = classLoaderName;
}

public MyClassLoader(ClassLoader parent, String classLoaderName) {
    super(parent); //显示指定该类加载器的父加载器 (可以自己自定义父加载器是谁)
    this.classLoaderName = classLoaderName;
}

@Override
public String toString() {
    return "[" + this.classLoaderName + "]";
}

//根据传入的name 寻找对应的Class
@Override
protected Class findClass(String classname) throws ClassNotFoundException {
    byte[] data=this.loadClassData(classname);
    //调用该方法可以返回Class对象,很重要的方法
    return this.defineClass(classname,data,0,data.length);
}

// 传入class文件的名字 返回类的字节数组
private byte[] loadClassData(String name) {
    InputStream is = null;
    byte[] data = null;
    ByteArrayOutputStream baos = null;
    try {
        this.classLoaderName = this.classLoaderName.replace(".", "/");
        is = new FileInputStream(new File(name + this.fileExtension));
        baos = new ByteArrayOutputStream();
        int ch = 0;
        while (-1 != (ch = is.read())) {
            baos.write(ch);
        }
        data = baos.toByteArray();
    } catch (Exception ex) {
        ex.printStackTrace();
    } finally {
        try {
            is.close();
            baos.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    return data;
}

public static  void test(ClassLoader classLoader) throws Exception {
    Class clazz=classLoader.loadClass("com.xuge.MyClassLoader");
    Object object=clazz.newInstance(); //返回实例化对象
    System.out.println(clazz.getClassLoader());
}

public static  void main(String[]args) throws Exception {
    MyClassLoader loader1=new MyClassLoader("loader1");
    test(loader1);

}

}

public MyClassLoader(String classLoaderName)方法中调用的super(),可以使用系统默认的类加载作为父加载器,其类加载器一定是系统类(应用)加载器AppClassLoader

public MyClassLoader(ClassLoader parent, String classLoaderName)方法中调用了super(parent);可以自己选择指定哪一个类加载器作为该类的加载器,可以不是系统类(应用)加载器AppClassLoader。

Super(parent);调用的源码是ClassLoader中的 protected ClassLoader(ClassLoader parent)

源码如下:

/**
 * Creates a new class loader using the specified parent class loader for
 * delegation.
 *
 * 

If there is a security manager, its {@link * SecurityManager#checkCreateClassLoader() * checkCreateClassLoader} method is invoked. This may result in * a security exception.

* * @param parent * The parent class loader * * @throws SecurityException * If a security manager exists and its * checkCreateClassLoader method doesn't allow creation * of a new class loader. * * @since 1.2 */ protected ClassLoader(ClassLoader parent) { this(checkCreateClassLoader(), parent); }

protected final Class defineClass(String name, byte[] b, int off, int len)又调用了
protected final Class defineClass(String name, byte[] b, int off, int len ProtectionDomain protectionDomain);

源码

/**
 * Converts an array of bytes into an instance of class Class,
 * with an optional ProtectionDomain.  If the domain is
 * null, then a default domain will be assigned to the class as
 * specified in the documentation for {@link #defineClass(String, byte[],
 * int, int)}.  Before the class can be used it must be resolved.
 *
 * 

The first class defined in a package determines the exact set of * certificates that all subsequent classes defined in that package must * contain. The set of certificates for a class is obtained from the * {@link java.security.CodeSource CodeSource} within the * ProtectionDomain of the class. Any classes added to that * package must contain the same set of certificates or a * SecurityException will be thrown. Note that if * name is null, this check is not performed. * You should always pass in the binary name of the * class you are defining as well as the bytes. This ensures that the * class you are defining is indeed the class you think it is. * *

The specified name cannot begin with "java.", since * all classes in the "java.* packages can only be defined by the * bootstrap class loader. If name is not null, it * must be equal to the binary name of the class * specified by the byte array "b", otherwise a {@link * NoClassDefFoundError NoClassDefFoundError} will be thrown.

* * @param name * The expected binary name of the class, or * null if not known * * @param b * The bytes that make up the class data. The bytes in positions * off through off+len-1 should have the format * of a valid class file as defined by * The Java™ Virtual Machine Specification. * * @param off * The start offset in b of the class data * * @param len * The length of the class data * * @param protectionDomain * The ProtectionDomain of the class * * @return The Class object created from the data, * and optional ProtectionDomain. * * @throws ClassFormatError * If the data did not contain a valid class * * @throws NoClassDefFoundError * If name is not equal to the binary * name of the class specified by b * * @throws IndexOutOfBoundsException * If either off or len is negative, or if * off+len is greater than b.length. * * @throws SecurityException * If an attempt is made to add this class to a package that * contains classes that were signed by a different set of * certificates than this class, or if name begins with * "java.". */ protected final Class defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) throws ClassFormatError { protectionDomain = preDefineClass(name, protectionDomain); String source = defineClassSourceLocation(protectionDomain); Class c = defineClass1(name, b, off, len, protectionDomain, source); postDefineClass(c, protectionDomain); return c; }

结果分析(双亲委托机制)

发现自定义MyClassLoader重写的findClass并没有被调用执行。原因: 双亲委托机制,因为MyClassLoader loader1=new MyClassLoader(“loader1”);我们使用的父类加载器是AppClassLoader,所以它交给父类加载器去加载,而系统类类加载器(父类)能够加载成功
所以并不是我们自定义的加载器加载的!

改造一下代码:
JVM深入学习(二)类的加载和双亲委托机制_第11张图片

指定要加载MyTest7.class的文件的路径,将工程中生成MyTest7.class的文件放到指定目录下,并删除系统文件中的MyTest7.class。

运行程序,打印结果如下:

JVM深入学习(二)类的加载和双亲委托机制_第12张图片

程序交给父类加载器去系统目录下加载class文件失败,交回给自定义加载器加载,调用了自定义类加载器的findClass方法

分析自定义类加载器自定义父加载器

改造代码,设置loader2的父加载器为loader1,MyClassLoader loader2=new MyClassLoader(loader1,“loader2”); 如下
JVM深入学习(二)类的加载和双亲委托机制_第13张图片

删除生成的MyTest7.class文件,将其放到自定义路径下。
运行结果如下:

JVM深入学习(二)类的加载和双亲委托机制_第14张图片

分析:
loader1加载MyTest7.class,交给父类加载器APPClassLoader加载,发现系统classpath,路径下找不到文件,加载失败,交回给loader1加载器加载,loader1调用了自己重写的方法findClass函数,打印了 class loader name: MyTest7 ;接着loader2加载MyTest7.class,它交给它的父类加载器loader1加载,而loader1加载器能加载成功,所以loader2没有起到作用,没有执行findClass函数。 而两次MyTest7.class都是同一个类加载器加载loader1加载成功的,所以hashcode值一样。

Loader1 和loader2 这两个加载器实例都是MyClassLoader New出来的,是相同类的两个对象,但是他们在类加载器方面却能父子关系,原因:它们并不是树形结构,而是包含关系。

你可能感兴趣的:(java,jvm,java类加载器)