0
点赞
收藏
分享

微信扫一扫

必懂!JVM调优之类加载,你真的懂吗?

南柯Taylor 2021-10-04 阅读 68
日记本

Java从编码到执行

在理解类加载前, 我觉得我们应该要先知道为什么我们写的代码可以被执行, 看下图:

我们写的代码编译成.class文件后, 被classloader加载到内存中, 同时也会把java自带的类库load到内存中

然后会编译器进行编译, 最后通过执行引擎执行

类加载的过程

先上个图:

在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始。

另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。

加载(loading)

就是把一个.class文件load到内存中, load进去后在内存中创建了两块内容, 一块存储了这个.class文件的二进制内容, 二是创建了这个Class的对象, 并且将这个对象指向了第一块内容

验证(verification)

​ 校验你这个.class文件内容是否合法

准备 (preparation)

​ 会在这个阶段为你的静态变量赋初始值

解析(resolution)

​ 在这个阶段将类、方法、属性等符号引用解析为直接引用(指针、偏移量等内存地址)

类加载器

类加载器分类以及加载范围

双亲委派

​ 上图中当要加载某一个类时, 自下向上 检查该类是否已经加载, 又自上而下进行实际的查找和加载, 这就是双亲委派, 继续看图

当某个类加载器接收到类加载请求时, 会先去自己的缓存中监测有没有, 没有就交给自己的父加载器, 依次类推, 直到查找到返回为止;

如果BootStrap也没查找到, 就会进行加载, 当BootStrap发现这个类不归它管时, 则交给子加载器加载, 依次类推, 直到加载到返回为止;

如果都没加载上, 就抛出ClassNotFoundException以后看见这个异常就别怕了

举个例子:

​ java.lang.String类, 我们知道这个Java自带的String位于rt.jar中, 那它的加载将经过Custom、Application、ExtensionClassloader, String这个类都不归前面三个加载器负责, 最终由Bootstrap加载并返回;

为什么要搞双亲委派

​ 最主要的原因是为了安全

其次是节约资源

双亲委派如何实现的

可以看ClassLoader的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 {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 如果从非空父类加载器中找不到类,则抛出ClassNotFoundException
                }

                if (c == null) {
                    // 如果仍然找不到,请调用findClass以便找到该类。
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // 这是定义类加载器;记录统计数据
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

如何打破双亲委派机制

​ 其实我们可以继承ClassLoader, 重写他的loadClass方法

private static class MyLoader extends ClassLoader {
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {

            File f = new File("E:\\workspace\\joys\\pay\\epay-service\\src\\main\\java" + name.replace(".", "/").concat(".class"));

            if(!f.exists()) {
                return super.loadClass(name);
            }

            try {

                InputStream is = new FileInputStream(f);

                byte[] b = new byte[is.available()];
                is.read(b);
                return defineClass(name, b, 0, b.length);
            } catch (IOException e) {
                e.printStackTrace();
            }

            return super.loadClass(name);
        }
    }

    public static void main(String[] args) throws Exception {
        MyLoader m = new MyLoader();
        Class clazz = m.loadClass("com.joy.epay.feign.Hello");

        m = new MyLoader();
        Class clazzNew = m.loadClass("ccom.joy.epay.feign.Hello");

        System.out.println(clazz == clazzNew);
    }

如何自定义一个类加载器

  1. 继承ClassLoader
  2. 重写findClass
  1. 自定义加载器逻辑

下面是个例子, 并且验证了第二次是否会被加载

public class ClassLoaderTest extends ClassLoader{

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        File f = new File("D:/test/", name.replace(".", "/").concat(".class"));
        try {
            FileInputStream fis = new FileInputStream(f);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;

            while ((b=fis.read()) !=0) {
                baos.write(b);
            }

            byte[] bytes = baos.toByteArray();
            baos.close();
            fis.close();

            return defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //throws ClassNotFoundException
        return super.findClass(name); 
    }

    public static void main(String[] args) throws Exception {
        ClassLoader l = new ClassLoaderTest();
        // 加载Hello类
        Class clazz = l.loadClass("com.joy.epay.feign.Hello");

        Hello h = (Hello)clazz.newInstance();
        h.print();

        // 重复加载, 验证第二次是否还会加载
        Class clazz1 = l.loadClass("com.joy.epay.feign.Hello");
        System.out.println(clazz == clazz1);

        // 打印该类的加载器
        System.out.println(l.getClass().getClassLoader());
    }
}
public class Hello {
    void print(){
        System.out.println("hello world!");
    }
}
举报

相关推荐

0 条评论