美文网首页
类加载2

类加载2

作者: 得力小泡泡 | 来源:发表于2020-11-19 15:45 被阅读0次

jvm参数
-XX:+TraceClassLoading:用来查看类的加载信息
-XX:+TraceClassUnloading:用来查看类的卸载信息
例子1

class Parent {
    static int a = 3;

    static {
        System.out.println("Parent static block");
    }

    static void doSomething() {
        System.out.println("do something");
    }
}
class Child extends Parent {

    static {
        System.out.println("Child static block");
    }
}
public class MyTest {
    public static void main(String[] args) {
        System.out.println(Child.a);
        System.out.println("+++");
        Child.doSomething();
    }
}

输出
Parent static block
3
+++
do something

分析:
定义在哪,就是对谁的主动使用

例子2

package com.test;

import java.util.Random;
import java.util.UUID;

class CL {
    static {
        System.out.println("Class Cl");
    }
}
public class MyTest {
    public static void main(String[] args) throws Exception {
        ClassLoader loader = ClassLoader.getSystemClassLoader();

        Class<?> clazz = loader.loadClass("com.test.CL");

        System.out.println(clazz);

        System.out.println("+++++");

        clazz = Class.forName("com.test.CL");

        System.out.println(clazz);
    }
}

输出
class com.test.CL
+++++
Class Cl
class com.test.CL

分析:
调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。而Class.forName("com.test.CL")的反射才会对类进行初始化

1、类加载器(ClassLoader)

image.png

对于数组来说,JavaDoc经常将构成数组的元素称为Component,实际上就是将数组降低一个维度后的类型

例子

package com.test;

import java.util.Random;
import java.util.UUID;

public class MyTest {
    public static void main(String[] args) throws Exception {
        String[] s = new String[2];
        System.out.println(s.getClass().getClassLoader());

        System.out.println("+++++");

        MyTest[] myTest = new MyTest[2];
        System.out.println(myTest.getClass().getClassLoader());

        System.out.println("+++++");

        int[] a = new int[2];
        System.out.println(a.getClass().getClassLoader());
    }
}

输出
null
+++++
sun.misc.Launcher$AppClassLoader@18b4aac2
+++++
null

分析:
数组类的类加载器与其元素类型的类加载器相同,如果该元素类型是基本类型,则该数组类没有加载器
1、String[] 返回String类型的加载器 null(这里指根加载器)
2、MyTest[] 返回MyTest类型的加载器 AppClassLoader(系统加载器)
3、int[] 是基本类型,因此返回null(这里指没有加载器)

2、自定义类加载器

package com.test;

import java.io.*;


public class MyTest extends ClassLoader {

    private String classLoaderName;

    private final String fileExtension = ".class";

    public MyTest(String classLoaderName)
    {
        super();// 将系统类加载器当做该类加载器的父加载器
        this.classLoaderName = classLoaderName;
    }
    public MyTest(ClassLoader parent, String classLoaderName)
    {
        super(parent);// 显示指定该类加载器的父加载器
        this.classLoaderName = classLoaderName;
    }

    @Override
    protected Class<?> findClass(String classname) throws ClassNotFoundException {

        System.out.println("findClass invoked:" + classname);
        System.out.println("class loader name: " + this.classLoaderName);

        byte[] data = this.loadClassData(classname);

        return this.defineClass(classname, data, 0, data.length);
    }

        private byte[] loadClassData(String className)
    {
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;

        className = className.replace(".", "/");
        try {

            is = new FileInputStream(new File(this.path + className + this.fileExtension));
            baos = new ByteArrayOutputStream();

            int ch = 0;
            while(((ch = is.read()) != -1))
            {
                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 main(String[] args) throws Exception{
        MyTest loader1 = new MyTest("loader1");
        Class<?> clazz = classLoader.loadClass("com.test.MyTest1");
        Object object = clazz.newInstance();

        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());
    }
}

从磁盘上指定好的一个class文件,读取到内存当中,放入到一个字节数组里面,class的信息就位于内存当中,再通过defineClass方法,将字节数组转换成一个class对象。
1、自定义加载器必须实现继承ClassLoader类
2、loadClassData方法,根据这个名字,把对应的文件找到,以输入输出流的形式获取类文件的字节码数组
3、definclass()方法,是将你定义的字节码文件经过字节数组流解密之后,将该字节流数组生成字节码文件,也就是该类的 文件的类名.class,产生对应的内部数据结构放置到方法区(一般在findClass方法中读取到对应字节码后调用,final的,不能被继承 )

protected final Class<?> defineClass(String name, byte[] b, int off, int len)
    throws ClassFormatError
{
    return defineClass(name, b, off, len, null);
}

4、findClass() 方法

     * Finds the class with the specified <a href="#name">binary name</a>.
     * This method should be overridden by class loader implementations that
     * follow the delegation model for loading classes, and will be invoked by
     * the {@link #loadClass <tt>loadClass</tt>} method after checking the
     * parent class loader for the requested class.  The default implementation
     * throws a <tt>ClassNotFoundException</tt>.

文档翻译:
查找具有指定的名字binary name 。 该方法应该被加载类的委托模型后面的类加载器实现覆盖,并且在检查所请求的类的父类加载器之后将被loadClass方法调用。 默认实现会抛出一个ClassNotFoundException
findClass方法是通过loadClass方法调用使用的

    @Override
    protected Class<?> findClass(String classname) throws ClassNotFoundException {
        byte[] data = this.loadClassData(classname);

        return this.defineClass(classname, data, 0, data.length);
    }

5、loadClass()方法
源码

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先判断该类是否已经被加载  
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                //如果没有被加载,就委托给父类加载或者委派给启动类加载器加载  
                try {
                    //如果存在父类加载器,就委派给父类加载器加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {   // 递归终止条件
                      // 由于启动类加载器无法被Java程序直接引用,因此默认用 null 替代
                      // parent == null就意味着由启动类加载器尝试加载该类,  
                      // 即通过调用 native方法 findBootstrapClass0(String name)加载  
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    
                }
            // 如果父类加载器不能完成加载请求时,再调用自身的findClass方法进行类加载,若加载成功,findClass方法返回的是defineClass方法的返回值
            // 注意,若自身也加载不了,会产生ClassNotFoundException异常并向上抛出
                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;
        }
    }

3、分析最初的自定义类加载器代码(代码在上面)

输出是:
com.test.MyTest1@1540e19d
sun.misc.Launcher$AppClassLoader@18b4aac2

很明显,findClass根本没有执行过,在说明几个函数的分析,可以知道对于加载MyTest1类加载成功的类加载器是系统类加载器,已经由上一层的类加载器解决了,因此自己定义的类加载器不需要进行加载,所有没有使用过自己定义的findClass

有如下操作
在项目中有com.Test.MyTest1的文件,在桌面中也创建一个com.Test.MyTest1的文件,使用一个自定义的类加载器去加载桌面上的MyTest1文件,分两种情况
1、若在项目中存在MyTest1.class 文件,则父加载器就可以解决,类加载器是系统类加载器
2、若在项目中不存在MyTest1.class文件,则会调用自己创建的类加载器的findClass方法,类加载器是MyTest(父加载器找不到这个MyTest1.class,并且自己不能进行加载,因此只能抛出异常给儿子自己加载)

具体代码

package com.test;

import java.io.*;

public class MyTest extends ClassLoader {

    private String classLoaderName;//加载器名字

    private String path;

    private final String fileExtension = ".class";

    public MyTest(String classLoaderName)
    {
        super();// 将系统类加载器当做该类加载器的父加载器
        this.classLoaderName = classLoaderName;
    }
    public MyTest(ClassLoader parent, String classLoaderName)
    {
        super(parent);// 显示指定该类加载器的父加载器
        this.classLoaderName = classLoaderName;
    }

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

    @Override
    protected Class<?> findClass(String classname) throws ClassNotFoundException {

        System.out.println("findClass invoked:" + classname);
        System.out.println("class loader name: " + this.classLoaderName);

        byte[] data = this.loadClassData(classname);

        return this.defineClass(classname, data, 0, data.length);
    }

    private byte[] loadClassData(String className)
    {
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;

        className = className.replace(".", "/");
        try {

            is = new FileInputStream(new File(this.path + className + this.fileExtension));
            baos = new ByteArrayOutputStream();

            int ch = 0;
            while(((ch = is.read()) != -1))
            {
                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 main(String[] args) throws Exception{
        MyTest loader1 = new MyTest("loader1");
        //loader1.setPath("F:/titled2/src/main/java/");
        //当父类加载器都解决不了的时候,才会调用自身的findClass往这个地址找
        loader1.setPath("C:/Users/小呆呆/Desktop/");

        Class<?> clazz = loader1.loadClass("com.test.MyTest1");
        System.out.println("class: " + clazz.hashCode());
        Object object = clazz.newInstance();
        System.out.println(object);
    }
}

输出

findClass invoked:com.test.MyTest1
class loader name: loader1
class: 21685669
com.test.MyTest1@7f31245a

命名空间(和类加载3 命名空间的关系结合一起看)

  • 每个类加载器都有自己的命名空间,命名空间由该加载器的类及所有父加载器所加载的类组成
  • 在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类
  • 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类
package com.test;

import java.io.*;

public class MyTest extends ClassLoader {

    private String classLoaderName;//加载器名字

    private String path;

    private final String fileExtension = ".class";

    public MyTest(String classLoaderName)
    {
        super();// 将系统类加载器当做该类加载器的父加载器
        this.classLoaderName = classLoaderName;
    }
    public MyTest(ClassLoader parent, String classLoaderName)
    {
        super(parent);// 显示指定该类加载器的父加载器
        this.classLoaderName = classLoaderName;
    }

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

    @Override
    protected Class<?> findClass(String classname) throws ClassNotFoundException {

        System.out.println("findClass invoked:" + classname);
        System.out.println("class loader name: " + this.classLoaderName);

        byte[] data = this.loadClassData(classname);

        return this.defineClass(classname, data, 0, data.length);
    }

    private byte[] loadClassData(String className)
    {
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;

        className = className.replace(".", "/");
        try {

            is = new FileInputStream(new File(this.path + className + this.fileExtension));
            baos = new ByteArrayOutputStream();

            int ch = 0;
            while(((ch = is.read()) != -1))
            {
                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 main(String[] args) throws Exception{
        MyTest loader1 = new MyTest("loader1");
        //loader1.setPath("F:/titled2/src/main/java/");

        loader1.setPath("C:/Users/小呆呆/Desktop/");//当父类加载器都解决不了的时候,才会调用自身的findClass往这个地址找
        Class<?> clazz = loader1.loadClass("com.test.MyTest1");
        System.out.println("class: " + clazz.hashCode());
        Object object = clazz.newInstance();
        System.out.println(object);

        System.out.println();

        MyTest loader2 = new MyTest("loader2");
        //loader1.setPath("F:/titled2/src/main/java/");

        loader2.setPath("C:/Users/小呆呆/Desktop/");//当父类加载器都解决不了的时候,才会调用自身的findClass往这个地址找
        Class<?> clazz2 = loader2.loadClass("com.test.MyTest1");
        System.out.println("class: " + clazz2.hashCode());
        Object object2 = clazz2.newInstance();
        System.out.println(object2);
}

在项目中有com.Test.MyTest1的文件,在桌面中也创建一个com.Test.MyTest1的文件,使用两个自定义的类加载器去加载桌面上的MyTest1文件,分两种情况

同一个命名空间中,加载同一个.class文件产生出来的.class对象是相同的
1、若在项目中存在MyTest1.class文件,都是由系统类加载器直接去加载,不会调用自己创建的类加载器的findClass方法,加载的类的类名相同(因此clazz == clazz2 返回true)

输出

class: 356573597
com.test.MyTest1@677327b6

class: 356573597
com.test.MyTest1@677327b6

不同的命名空间中,加载同一个.class文件产生出来的.class对象是不同的,两个.class对象各自放在对应的命名空间中
2、若在项目中不存在MyTest1.class文件,则两个自定义加载器都会调用自己创建的类加载器的findClass方法,类加载器是自己本身,同时在同一个命名空间中,不会出现类相同的完整名字的相同两个类,因此两个类的类名不同(hascode不同)(loader1和loader2没有任何关系,在内存当中形成了两个命名空间,在各自的命名空间加载出对应的类,加载出来的两个.class对象是不同的,因此clazz == clazz2 返回false)
输出

findClass invoked:com.test.MyTest1
class loader name: loader1
class: 21685669
com.test.MyTest1@7f31245a

findClass invoked:com.test.MyTest1
class loader name: loader2
class: 1173230247
com.test.MyTest1@330bedb4

类的卸载

  • 当MySample类被加载、连接和初始化后,它的生命周期就开始了。当代表MySample类的Class对象不再被引用,即不可触及时,Class对象就会结束生命周期,MySample类在方法区的数据也会被卸载,从而结束Sample类的生命周期
  • 一个类何时结束生命周期,取决于代表它的Class对象何时结束生命周期
  • 由Java虚拟机自带的类记载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。前面介绍过,Java虚拟机自带的类加载器包括根类加载器、扩展类加载器和系统类加载器。Java虚拟机本身会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类的Class对象,因此这些Class对象始终是可触及的
  • 由用户自定义的类加载器所加载的类是可以被卸载的

例子
将项目中的MyTest1删除,通过自定义类记载器加载桌面上的MyTest1,加载完后,将loader1指向其他地方,并进行垃圾回收

package com.test;

import java.io.*;

public class MyTest extends ClassLoader {

    private String classLoaderName;//加载器名字

    private String path;

    private final String fileExtension = ".class";

    public MyTest(String classLoaderName)
    {
        super();// 将系统类加载器当做该类加载器的父加载器
        this.classLoaderName = classLoaderName;
    }
    public MyTest(ClassLoader parent, String classLoaderName)
    {
        super(parent);// 显示指定该类加载器的父加载器
        this.classLoaderName = classLoaderName;
    }

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

    @Override
    protected Class<?> findClass(String classname) throws ClassNotFoundException {

        System.out.println("findClass invoked:" + classname);
        System.out.println("class loader name: " + this.classLoaderName);

        byte[] data = this.loadClassData(classname);

        return this.defineClass(classname, data, 0, data.length);
    }

    private byte[] loadClassData(String className)
    {
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;

        className = className.replace(".", "/");
        try {

            is = new FileInputStream(new File(this.path + className + this.fileExtension));
            baos = new ByteArrayOutputStream();

            int ch = 0;
            while(((ch = is.read()) != -1))
            {
                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 main(String[] args) throws Exception{
        MyTest loader1 = new MyTest("loader1");
        //loader1.setPath("F:/titled2/src/main/java/");

        loader1.setPath("C:/Users/小呆呆/Desktop/");//当父类加载器都解决不了的时候,才会调用自身的findClass往这个地址找
        Class<?> clazz = loader1.loadClass("com.test.MyTest1");
        System.out.println("class: " + clazz.hashCode());
        Object object = clazz.newInstance();
        System.out.println(object);

        System.out.println();

        loader1 = null;
        clazz = null;
        object = null;

        System.gc();//调用垃圾回收

        loader1 = new MyTest("loader1");
        //loader1.setPath("F:/titled2/src/main/java/");

        loader1.setPath("C:/Users/小呆呆/Desktop/");//当父类加载器都解决不了的时候,才会调用自身的findClass往这个地址找
        clazz = loader1.loadClass("com.test.MyTest1");
        System.out.println("class: " + clazz.hashCode());
        Object object2 = clazz.newInstance();
        System.out.println(object2);
    }
}

输出(加入 -XX:+TraceClassUnloading:用来查看类的卸载信息)

findClass invoked:com.test.MyTest1
class loader name: loader1
class: 21685669
com.test.MyTest1@7f31245a

[Unloading class com.test.MyTest1 0x0000000100061028]
findClass invoked:com.test.MyTest1
class loader name: loader1
class: 1173230247
com.test.MyTest1@330bedb4

相关文章

网友评论

      本文标题:类加载2

      本文链接:https://www.haomeiwen.com/subject/bkeciktx.html