欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【性能调优专题】【Jvm性能调优】【JVM【类加载机制详解】【手写定义类加载器】

程序员文章站 2022-05-06 14:05:18
...

上一期我们讲启动类、扩展类、应用程序类加载器详解

这一期我们自己手写一个类加载器,如下:

package com.example.demo;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author chevy
 */
//继承ClassLoader类,重写findclass方法。
public class MyClassLoader extends ClassLoader {
    private String rootPath;


    public MyClassLoader(String s) {
        rootPath = s;
    }


    //用于寻找类文件
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] b = loadClassData(name);
        return defineClass(name, b, 0, b.length);
    }

    public byte[] loadClassData(String name) {
        //name = rootPath + name + ".class";
        name = rootPath + name.replace(".", "/") + ".class";
        InputStream in = null;
        ByteArrayOutputStream out = null;
        try {
            in = new FileInputStream(new File(name));
            out = new ByteArrayOutputStream();
            int i = 0;
            while ((i = in.read()) != -1) {
                out.write(i);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return out.toByteArray();
    }
}

但是在JVM中,即使这个两个类对象(class对象)来源同一个Class文件,被同一个虚拟机所加载,但只要加载它们的ClassLoader实例对象不同,那么这两个类对象也是不相等的,这是因为不同的ClassLoader实例对象都拥有不同的独立的类名称空间,所以加载的class对象也会存在不同的类名空间中。

前提是覆写loadclass方法
在方法第一步会通过Class<?> c = findLoadedClass(name); 从缓存查找,类名完整名称相同则不会再次被加载,因此我们必须绕过缓存查询才能重新加载class对象。当然也可直接调用findClass()方法,这样也避免从缓存查找,如下 :

/**
 * 测试我的类加载器
 */
class TestMyClass {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        //创建两个不同的自定义类加载器实例
        MyClassLoader myClassLoaderTest1 = new MyClassLoader("/Users/chevy/Projects/PMS/pms_cifi_name/demo/target/classes/");
        MyClassLoader myClassLoaderTest2 = new MyClassLoader("/Users/chevy/Projects/PMS/pms_cifi_name/demo/target/classes/");
        Class<?> classTest1 = myClassLoaderTest1.findClass("com.example.demo.SayHello");
        Class<?> classTest2 = myClassLoaderTest2.findClass("com.example.demo.SayHello");
        System.out.println("findClass->class1:" + classTest1.hashCode());
        System.out.println("findClass->class2:" + classTest2.hashCode());
        //Object o = aClass.newInstance();
        //Method sayHello = aClass.getMethod("sayHello", new Class<?>[]{});
        //Object invoke = sayHello.invoke(o, new Object[]{});
        //System.out.println(invoke);
    }
}
/**
*运行结果为
*/
findClass->class1:521645586
findClass->class2:1296064247

如果调用父类的loadClass方法,结果如下,除非重写loadClass()方法去掉缓存查找步骤,不过现在一般都不建议重写loadClass()方法。

/**
 * 测试我的类加载器
 */
class TestMyClass {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        //创建两个不同的自定义类加载器实例
        MyClassLoader myClassLoaderTest1 = new MyClassLoader("/Users/chevy/Projects/PMS/pms_cifi_name/demo/target/classes/");
        MyClassLoader myClassLoaderTest2 = new MyClassLoader("/Users/chevy/Projects/PMS/pms_cifi_name/demo/target/classes/");
        Class<?> classTest1 = myClassLoaderTest1.loadClass("com.example.demo.SayHello");
        Class<?> classTest2 = myClassLoaderTest2.loadClass("com.example.demo.SayHello");
        System.out.println("findClass->class1:" + classTest1.hashCode());
        System.out.println("findClass->class2:" + classTest2.hashCode());
        //Object o = aClass.newInstance();
        //Method sayHello = aClass.getMethod("sayHello", new Class<?>[]{});
        //Object invoke = sayHello.invoke(o, new Object[]{});
        //System.out.println(invoke);
    }
}
/**
*运行结果为
*/
findClass->class1:2128227771
findClass->class2:2128227771

所以如果不从缓存查询相同完全类名的class对象,那么只有ClassLoader的实例对象不同,同一字节码文件创建的class对象自然也不会相同。

单加载器测试最终结果:

package com.example.demo;

/**
 * @author chevy
 */
public class SayHello {
    public void sayHello() {
        System.out.println("Hello,World");
    }
}


/**
 * 测试我的类加载器
 */
class TestMyClass {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        //创建两个不同的自定义类加载器实例
        MyClassLoader myClassLoaderTest = new MyClassLoader("/Users/chevy/Projects/PMS/pms_cifi_name/demo/target/classes/");
        Class<?> classTest = myClassLoaderTest.loadClass("com.example.demo.SayHello");
        Object o = classTest.newInstance();
        Method sayHello = classTest.getMethod("sayHello", new Class<?>[]{});
        Object invoke = sayHello.invoke(o, new Object[]{});
    }
}
/**
*运行结果为
*/
Hello,World

该章节为:Java架构学习路线-性能调优专题-Jvm性能调优-JVM类加载机制详解-手写定义类加载器。

如果喜欢可以关注该专栏。该专栏讲解整体Java架构学习路线