关于jvm:JVM搭建自定义加载器拓展性的实施

34次阅读

共计 3660 个字符,预计需要花费 10 分钟才能阅读完成。

一、背景

名单管理体系是手机上各个模块将需要 管控 的使用装备到文件中,而后下发到手机上进行使用管控的体系,比如各个使用的耗电量管控;各个模块的管控使用文件思考到平安问题,有本人的不同的加密办法,依照以往的经历,咱们能够使用 模板办法 + 工厂形式 来根据模块的类型来获取到不同的加密办法。代码类层次结构暗示如下:

使用工厂形式和模板办法形式,在有新的加密办法时,咱们能够通过增加新的 handler 来满足 ” 对修改敞开,对扩大敞开 ” 的准则,然而这种办法不可避免的需要修改代码和需要从新发版别和上线。那么有没有更好的办法能够去解决这个问题,这儿便是咱们今日要要点讲的主题。

二、类加载的机会

一个类型从被加载到虚拟机内存中开始,到卸载出内存停止,它的整个生命周期将会经历 `
加载(Loading)、验证(Verification)、筹备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)

尽管 classloader 的加载过程有凌乱的 7 步,但事实上除了加载之外的四步,其它都是由 JVM 虚拟机操控的,咱们除了习惯它的规范进行开发外,能够干预的空间并不多。而加载则是咱们操控 classloader 完结特地目标最重要的办法了。也是接下来咱们介绍的要点了。

protected Class<?> loadClass(String name, boolean resolve)

    throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        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) {
                // If still not found, then invoke findClass in order
                // to find the class.
                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;
    }
}

loadClass 加载办法流程:

判断此类是否曾经加载;
如果父加载器不为 null,则应用父加载器进行加载;反之,应用根加载器进行加载;
如果后面都没加载胜利,则应用 findClass 办法进行加载。
所以,为了不影响类的加载过程,咱们重写 findClass 办法即可简略不便的实现自定义类加载。

六、代码实现

6.1 实现自定义的类加载器

public class DynamicClassLoader extends ClassLoader {
 
    private static final String CLASS_EXTENSION = "class";
 
    @Override
    public Class<?> findClass(String encryptClassInfo) {EncryptClassInfo info = JSON.parseObject(encryptClassInfo, EncryptClassInfo.class);
        String filePath = info.getAbsoluteFilePath();
        String systemPath = System.getProperty("java.io.tmpdir");
        String normalizeFileName = FilenameUtils.normalize(filePath, true);
        if (StringUtils.isEmpty(normalizeFileName) || !normalizeFileName.startsWith(systemPath)
                ||getApkFileExtension(normalizeFileName) == null
                || !CLASS_EXTENSION.equals(getApkFileExtension(normalizeFileName))) {return null;}
 
        String className = info.getEncryptClassName();
        byte[] classBytes = null;
        File customEncryptFile = new File(filePath);
        try {Path path = Paths.get(customEncryptFile.toURI());
            classBytes = Files.readAllBytes(path);
        } catch (IOException e) {log.info("加密谬误", e);
        }


## 三、加载“加载”(Loading)阶段是整个“类加载”(Class Loading)过程中的一个阶段。在加载阶段,Java 虚拟机需要完结以下三件作业:通过一个类的全限定名来获取定义此类的二进制字节俭。将这个字节俭所代表的动态存储构造转化为办法区的运行时数据结构。在内存中生成一个代表这个类的 java.lang.Class 指标,作为办法区这个类的各种数据的拜访进口。《Java 虚拟机规范》对这三点没有进行特地具体的要求,而后留给虚拟机完结与 Java 使用的灵敏度都是相当大的。例如“通过一个类的全限定名来获取定义此类的二进制字节俭”这条规矩,它并没有指明二 进制字节俭必须得从某个 Class 文件中获取,切当地说是底子没有指明要从哪里获取、如何获取。比如咱们能够从 ZIP 压缩包中读取、从网络中获取、运行时核算生成、由其余文件生成、从数据库中读取。也能够能够从加密文件中获取。从这儿咱们能够看出,只需要咱们能够获取到加密类的.class 文件,咱们就能够通过类加载器获取到对应的加密类 class 指标,从而通过反射去调用具体的加密办法。因此类加载器在.class 文件的加载过程有着至关重要的位置。## 四、双亲委派模型
当初 Java 虚拟机现已存在三种类加载器,别离为发动类加载器、扩大类加载器和使用程序类加载器;绝大多数的 Java 程序都会使用这三种类加载器进行加载。### 4.1 发动类加载器
这个类由 C ++ 完结,负责加载寄存在 \lib 目录,或者被 -Xbootclasspath 参数所指定的路径中寄存的,而且是 Java 虚拟机能够识别的(依照文件名识别,如 rt.jar、tools.jar,名字不合乎的类库即便放在 lib 目录中也不会被加载)类库加载到虚拟机的内存中。发动类加载器无奈被 Java 程序间接引证,用户在编写自定义类加载器时,假如需要把加载请求委派给疏导类加载器去解决,那间接使用 null 代替即可。### 4.2 扩大类加载器
这个类加载器是在类 sun.misc.Launcher$ExtClassLoader 中以 Java 代码的形式完结的。它负责加载 \lib\ext 目录中,或者被 java.ext.dirs 体系变量所指定的路径中所有的类库。根据“扩大类加载器”这个名称,就能够推断出这是一种 Java 体系类库的扩大机制,JDK 的开发团队答应用户将具备通用性的类库搁置在 ext 目录里以扩大 Java SE 的功能,在 JDK9 之后,这种扩大机制被模块化带来的人造的扩大能力所代替。因为扩大类加载器是由 Java 代码完结的,开发者能够间接在程序中使用扩大类加载器来加载 Class 文件。### 4.3 使用程序类加载器
这个类加载器由 sun.misc.Launcher$AppClassLoader 来完结。因为使用程序类加载器是 ClassLoader 类中的 getSystemClassLoader()办法的返回值,所以有些场合中也称它为“体系类加载器”。它负责加载用户类路径(ClassPath)上所有的类库,开发者同样能够间接在代码中使用这个类加载器。假如使用程序中没有自定义过本人的类加载器,个别状况下这个便是程序中默认的类加载器。

正文完
 0