zoukankan      html  css  js  c++  java
  • 玩命学JVM(二)—类加载机制

    前言

    Java程序运行图:
    Alt

    上一篇玩命学JVM(一)—认识JVM和字节码文件我们简单认识了 JVM 和字节码文件。那JVM是如何使用字节码文件的呢?从上图清晰地可以看到,JVM 通过类加载器完成了这一过程。

    以下是类加载机制的知识框架:

    Alt

    接下来我们对思维导图中重难点部分做补充。

    1. 是什么?

    类的加载就是将 .class 文件的二进制数据读入到内存中,将其放在 JVM 的运行时数据区的方法区内。然后在堆区内创建一个 java.lang.Class 对象,用于封装类在方法区内的数据结构。

    5. 双亲委派模型

    双亲委派模型图如下:
    Alt

    对于“双亲委派模型”,首先需要纠正一点,“双亲”并不是说它有“两个亲”。实际上行“双亲委派模型”和“双”毫无关系,只和“亲”有关系。
    其实“双亲”是翻译的一个错误,原文出处是“parent”,被翻译成了“双亲”,在计算机领域更常见的说法是“父节点”。所以如果将“双亲委派模型”改为“父委派模型”,应该更好理解。

    结合实际的类加载器来说,就是:

    1. 每个类加载器都会向上找自己父类加载器尝试完成类加载;
    2. 父加载器加载失败会向下找加载器尝试加载。
      如图:

    Alt

    接下来我们从源码上来分析下 双亲委派模型
    Bootstrap ClassLoader外,其它的类加载器都是ClassLoader的子类。加载类的方法为loadClass,查看源码可发现,loadClassClassLoader中有具体的实现,且在各个子类中都没有被覆盖。

    先介绍三个重要的函数,对后续的源码阅读有帮助:
    loadClass:调用父类加载器的loadClass,加载失败则调用自己的findClass方法。
    findClass:根据名称读取文件存入字节数组。
    defineClass:把一个字节数组转为Class对象。

    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // First, check if the class has already been loaded
                // 在JVM中查看类是否已经被加载
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    long t0 = System.nanoTime();
                    try {
                        if (parent != null) {
    	                    // 调用父类加载器的 loadClass方法,parent是该类加载器的父类,parent的值可能为 Application ClassLoader、Extension ClassLoader,当想要继续往上找 Extension ClassLoader时,由于Bootstrap ClassLoader是C/C++实现的,所以在java中是Null
                            c = parent.loadClass(name, false);
                        } else {
    	                    // 寻找 Bootstrap ClassLoader 加载
                            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();
                        // 父加载器开始尝试加载.class文件,加载成功就返回一个java.lang.Class,加载不成功就抛出一个ClassNotFoundException,给子加载器去加载
                        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) {
                // 如果要解析这个.class文件的话,就解析一下,解析的作用主要就是将符号引用替换为直接引用的过程
                    resolveClass(c);
                }
                return c;
            }
        }
    

    所谓的双亲委派模型,就是利用了loadClass只在父类中实现了这一点。

    自定义类加载器

    自定义类加载主要有两种方式:

    1. 遵守双亲委派模型:继承ClassLoader,重写findClass()方法。

    2. 破坏双亲委派模型:继承ClassLoader,重写loadClass()方法。 通常我们推荐采用第一种方法自定义类加载器,最大程度上的遵守双亲委派模型。

    我们看一下实现步骤
    (1)创建一个类继承ClassLoader抽象类
    (2)重写findClass()方法
    (3)在findClass()方法中调用defineClass()

    第一步,自定义一个实体类Person.java,我把它编译后的Person.class放在D盘根目录下:

    package com.xrq.classloader;
    
    public class Person
    {
        private String name;
    
        public Person()
        {
    
        }
    
        public Person(String name)
        {
            this.name = name;
        }
    
        public String getName()
        {
            return name;
        }
    
        public void setName(String name)
        {
            this.name = name;
        }
    
        public String toString()
        {
            return "I am a person, my name is " + name;
        }
    }
    

    第二步,自定义一个类加载器,里面主要是一些IO和NIO的内容,另外注意一下 defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class----只要二进制字节流的内容符合Class文件规 范。我们自定义的MyClassLoader继承自java.lang.ClassLoader,就像上面说的,只实现findClass方法:

    public class MyClassLoader extends ClassLoader
    {
        public MyClassLoader()
        {
            
        }
        
        public MyClassLoader(ClassLoader parent)
        {
            super(parent);
        }
        
        protected Class<?> findClass(String name) throws ClassNotFoundException
        {
            File file = getClassFile(name);
            try
            {
                byte[] bytes = getClassBytes(file);
                Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
                return c;
            } 
            catch (Exception e)
            {
                e.printStackTrace();
            }
            
            return super.findClass(name);
        }
        
        private File getClassFile(String name)
        {
            File file = new File("D:/Person.class");
            return file;
        }
        
        private byte[] getClassBytes(File file) throws Exception
        {
            // 这里要读入.class的字节,因此要使用字节流
            FileInputStream fis = new FileInputStream(file);
            FileChannel fc = fis.getChannel();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            WritableByteChannel wbc = Channels.newChannel(baos);
            ByteBuffer by = ByteBuffer.allocate(1024);
            
            while (true)
            {
                int i = fc.read(by);
                if (i == 0 || i == -1)
                    break;
                by.flip();
                wbc.write(by);
                by.clear();
            }
            
            fis.close();
            
            return baos.toByteArray();
        }
    }
    

    第三步,Class.forName有一个三个参数的重载方法,可以指定类加载器,平时我们使用的Class.forName("XX.XX.XXX")都是使用的系统类加载器Application ClassLoader。写一个测试类:

    public class TestMyClassLoader
    {
        public static void main(String[] args) throws Exception
        {
            MyClassLoader mcl = new MyClassLoader();
            Class<?> c1 = Class.forName("com.xrq.classloader.Person", true, mcl);
            Object obj = c1.newInstance();
            System.out.println(obj);
            System.out.println(obj.getClass().getClassLoader());
        }
    }
    

    运行结果:

    I am a person, my name is null
    com.xrq.classloader.MyClassLoader@5d888759

    参考文献

    https://baijiahao.baidu.com/s?id=1636309817155065432&wfr=spider&for=pc
    https://blog.csdn.net/qq_44836294/article/details/105439753

  • 相关阅读:
    Python [Leetcode 350]Intersection of Two Arrays II
    jade学习
    pageX、clientX、screenX、offsetX、layerX、x
    AngularJS--转载
    AngularJS
    超级强大的SVG动画详解
    javascript event对象的clientX,offsetX,screenX,pageX区别
    console的调试方法
    javascript--函数参数与闭包--详解
    如何把你的图标转换成web字体
  • 原文地址:https://www.cnblogs.com/cleverziv/p/13759175.html
Copyright © 2011-2022 走看看