33

玩命学JVM(二)—类加载机制

 4 years ago
source link: http://www.cnblogs.com/cleverziv/p/13759175.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前言

Java程序运行图:

FrEbAvM.png!mobile

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

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

6bInqyJ.png!mobile

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

1. 是什么?

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

5. 双亲委派模型

双亲委派模型图如下:

Zbu6byn.png!mobile

对于“双亲委派模型”,首先需要纠正一点,“双亲”并不是说它有“两个亲”。实际上行“双亲委派模型”和“双”毫无关系,只和“亲”有关系。

其实“双亲”是翻译的一个错误,原文出处是“parent”,被翻译成了“双亲”,在计算机领域更常见的说法是“父节点”。所以如果将“双亲委派模型”改为“父委派模型”,应该更好理解。

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

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

QVZbau.png!mobile

接下来我们从源码上来分析下 双亲委派模型

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

参考文献


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK