jvm学习笔记6:类加载器

1. 类与类加载器

对于任意一个类,都需要有加载它的类加载器和这个类本身一同确认其在java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。

2. 双亲委派机制

2.1 加载器分类

从java虚拟机的角度来看,只存在两种不同的类加载器:

  • 启动类加载器(Bootstrap ClassLoader):C++语言实现,是java虚拟机的一部分;
  • 其他所有的类加载器:这些都是有Java语言实现的,独立于虚拟机外部,并且都继承自抽象类java.lang.ClassLoader。

从开发人员的角度来看,绝大部分Java程序都会使用以下三种类加载器,可以分为:

  • 启动类加载器(Bootstrap ClassLoader):负责<JAVA_HOME>/lib或者被-Xbootclasspath参数指定的路径。
  • 扩展类加载器(Extension ClassLoader):这个加载器由sun.misc.Launcher$ExtClassLoader实现。负责加载<JAVA_HOME>/lib/ext目录以及被java.ext.dirs系统参数指定的类库。
  • 应用类加载器(Application ClassLoader):这个类由sun.misc.Launcher$AppClassLoader实现。加载用户类路径(classPath)的类。

2.2 从源码来看双亲委派机制

双亲委派机制的图示如下,其其工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委派其父类加载器去完成,每一个层次的类加载器都是如此,因此最终所有的加载请求都应该传递给顶层的启动类加载器,只有父类加载器无法完成这个加载请求时,子加载器才会尝试自己去加载。
在这里插入图片描述
除了用C++实现的启动类加载器,其余所有的类加载器都要继承抽象类ClassLoader方法,下面分析其中几个关键方法的源码:

2.3.1 构造方法

    //指定一个父类加载器,指定null,则父类加载器为Bootstrap ClassLoader
    protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }
    //默认父类加载器为AppClassLoader
    protected ClassLoader() {
        this(checkCreateClassLoader(),  getSystemClassLoader());
    }

2.3.2 loadClass(String name, boolean resolve)

/**
* 双亲委派机制是通过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 thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    //如果父类加载器都没有加载成功,则调用自己的findClass 方法进行加载
                    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;
        }
    }

2.3.3 findClass(String name)

ClassLoder中的findClass方法是抽象方法,需要子类进行实现。ExtClassLoaderAppClassLoader都继承了URLClassLoader,而URLClassLoader继承了ClassLoder,并实现了findClass方法。

 protected Class<?> findClass(final String name)
        throws ClassNotFoundException
    {
        final Class<?> result;
        try {
            result = AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class<?>>() {
                    public Class<?> run() throws ClassNotFoundException {
                        String path = name.replace('.', '/').concat(".class");
                        //找到对应的clss资源
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                            //根据class字节流生产Class实例
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else {
                            return null;
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
        if (result == null) {
            throw new ClassNotFoundException(name);
        }
        return result;
    }

2.3.4 defineClass

所有的类加载器最后都要掉这个方法,它内部会调本地方法,负责将class字节流文件转换成Class实例。

    protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        //先进行校验,比如以java开头的类名,不能加载。
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }

3. 自定义类加载器

  • 如果要打破双亲委派机制,重写loadClass()方法;
  • 否则,重写findClass()方法即可。

3.1 自定义类加载器(加载指定目录)

package com.lihui.study.jvm;

import java.io.*;

/**
 * @author ex_lihui4
 * @ClassName MyClassLoader
 * @Description 自定义一个普通的类加载器,通过dir执行相应的类目录
 * @date 2020-11-17  11:50
 */

public class MyClassLoader extends ClassLoader {
    private String dir;

    public MyClassLoader(ClassLoader parent, String dir) {
        super(parent);
        this.dir = dir;
    }

    public MyClassLoader(String dir) {
        //如不指定父类加载器,
        this.dir = dir;
    }


    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //拼接类路径
        String className = name.substring(name.lastIndexOf(".") + 1)+".class";
        String classPth = "\\"+name.substring(0, name.lastIndexOf(".")).replace(".","\\")+"\\";
        String fileName = dir+classPth+className;
        //获取二进制数据
        File file = new File(fileName);
        try (FileInputStream is = new FileInputStream(file)){
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int length = 0;
            while ((length = is.read(buffer)) !=-1){
                os.write(buffer,0,length);
            }
            byte[] bytes = os.toByteArray();
            //返回类文件
            return defineClass(name,bytes,0,bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

4. 补充一下当前线程类加载器

JNDI服务:它由启动类加载器去加载,但JNDI就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的classPath下的JNDI接口提供者。
那么现在问题来了,启动类加载器加载的代码怎么去加载classPath下的类。默认使用调用者的类加载器,也就是启动类加载器去加载classpath下的接口提供者,是加载不了的。这个时候直接使用ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader()进行加载。如果创建线程时为指定线程类加载器,会从父类进行继承。如果全部范围都没有指定,默认为应用程序类加载器。所以解决了JNDI接口提供者的加载问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值