栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JVM之类加载器

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JVM之类加载器

JVM类加载器
  • 类的生命周期
  • 类加载并且初始化
  • 类可能会加载但不会初始化
  • 三类加载器
    • 加载器特点
    • 充要类
    • 获取类加载器及其class path
    • 自定义ClassLoder
    • 添加引用类的几种方式( 添加额外的类class文件或jar包给类加载加载)

类的生命周期

前面5个步骤合起来就是传统所说的类加载的整个过程
其中2、3、4合起来统称为链接阶段

类加载并且初始化
  1. 当虚拟机启动时,初始化用户指定的主类,就是启动执行的 main 方法所在的类
  2. 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类,就是 new 一个类的时候要初始化
  3. 当遇到调用静态方法的指令时,初始化该静态方法所在的类
  4. 当遇到访问静态字段的指令时,初始化该静态字段所在的类
  5. 子类的初始化会触发父类的初始化
  6. 如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化
  7. 使用反射 API 对某个类进行反射调用时,初始化这个类,其实跟前面一样,反射调用要么是已经有实例了,要么是静态方法,都需要初始化
  8. 当初次调用 MethodHandle(方法指针) 实例时,初始化该 MethodHandle 指向的方法所在的类
package jvm;

import java.util.function.Function;

public class HelloByteCode {
    public static void main(String[] args) {
        HelloByteCode helloByteCode = new HelloByteCode();
        System.out.println(MethodHandle(new HelloByteCode()::int2Str));
    }
														//方法指针function
    private static String MethodHandle(Function function){
        String itoStrVal = function.apply(1);
        return itoStrVal;

    }

    public String int2Str(int i){
        return String.valueOf(i);
    }
}
类可能会加载但不会初始化
  1. 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化
  2. 定义对象数组,不会触发该类的初始化
  3. 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类
  4. 通过类名获取 Class 对象,不会触发类的初始化,Hello.class 不会让 Hello 类初始化
  5. 通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化,Class.forName"jvm.Hello"默认会加载 Hello 类
  6. 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作(加载了,但是不初始化)
三类加载器


三类加载器:

  1. 启动类加载器(BootstrapClassLoader)

用于加载JVM最核心的一些jar包和类class文件,这些类都在JDk安装文件夹下的,几乎是所有Java程序都需要用到的一些公共类、最核心类(比如rt.jar)

  1. 扩展类加载器(ExtClassLoader)

作为启动类的补充加载器,负责给JVM加载一些额外的jar包或类class文件,这些jar包或类class文件存放在JDK或者JRE指定的地方(扩展类文件夹或目录),比如web应用项目的lab目录下的jar包也是通过扩展类加载器加载

  1. 应用类加载器(AppClassLoader)

用于加载我们自己编写的JVM代码生成的类class文件和它们依赖的一些其它类class文件、我们运行时指定的class path 下的jar包和类class文件

  1. 自定义类加载器

继承了应用类加载器,用于使用自定义的规则去加载类class文件或jar包

处于被启动类加载器和扩展类加载器加载的目录范围内的jar包或类都会在JVM启动时默认加载

加载器特点
  1. 双亲委托

当需要加载类class文件或者jar包时,首先去委托父类加载器进行类的加载,作用是防止重复加载,如果父类加载器已经加载过时,会直接把子类加载器需要加载的类class文件或jar包加载后的引用传递给子类加载器

  1. 负责依赖

当类加载器加载类class文件或者jar包时,发现这些类class文件和jar包还依赖了其它的类class文件或jar包,那么此时会一起被加载

  1. 缓存加载

已经加载过的类class文件或jar包,会被缓存到内存中,下次再遇到需要加载已经加载过的类class文件或jar包时,就不需要重复去加载,直接去内存里面取

充要类


ClassLoder是JDK里面的充要类,有一个具体的实现类就是URLClassLoder,通过指定URL的方式去加载类或者jar包,其它的类加载都是URLClassLoder的子类

启动类加载器(BootstrapClassLoader)是无法看到具体实现和代码的,因为它是由JVM底层实现的

除了启动类加载器,其它类加载器及其父类加载器都是可以通过ClassLoder类加载器拿到

获取类加载器及其class path
package jvm;

import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;

public class JvmClassLoaderPrintPath {

    public static void main(String[] args) {

        // 启动类加载器
        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        System.out.println("启动类加载器");
        for (URL url : urls) {
            System.out.println(" ===> " + url.toExternalForm());
        }

        // 扩展类加载器
        printClassloader("扩展类加载器",JvmClassLoaderPrintPath.class.getClassLoader().getParent());

        // 应用类加载器
        printClassloader("应用类加载器",JvmClassLoaderPrintPath.class.getClassLoader());

    }

    private static void printClassloader(String name, ClassLoader classLoader) {
        System.out.println();
        if (null != classLoader) {
            System.out.println(name + " Classloader -> " + classLoader.toString());
            printURLForClassloader(classLoader);
        } else {
            System.out.println(name + " Classloader -> null");
        }
    }

    private static void printURLForClassloader(ClassLoader classLoader) {
        //每一个ClassLoder都包含ucp属性
        Object ucp = insightField(classLoader,"ucp");
        //ucp属性中包含path属性,包含了ucp所属类加载器的所有class path
        Object path = insightField(ucp,"path");
        List paths = (List) path;
        for (Object p : paths) {
            System.out.println(" ===> " + p.toString());
        }
    }

    private static Object insightField(Object obj, String fName) {
        Field f = null;
        try {
            if (obj instanceof URLClassLoader) {
                f = URLClassLoader.class.getDeclaredField(fName);
            } else {
                f = obj.getClass().getDeclaredField(fName);
            }
            f.setAccessible(true);
            return f.get(obj);
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }
}

输出结果:

启动类加载器
 ===> file:/D:/java_oprationsystem/jre/lib/resources.jar
 ===> file:/D:/java_oprationsystem/jre/lib/rt.jar
 ===> file:/D:/java_oprationsystem/jre/lib/sunrsasign.jar
 ===> file:/D:/java_oprationsystem/jre/lib/jsse.jar
 ===> file:/D:/java_oprationsystem/jre/lib/jce.jar
 ===> file:/D:/java_oprationsystem/jre/lib/charsets.jar
 ===> file:/D:/java_oprationsystem/jre/lib/jfr.jar
 ===> file:/D:/java_oprationsystem/jre/classes

扩展类加载器 Classloader -> sun.misc.Launcher$ExtClassLoader@7a92922
 ===> file:/D:/java_oprationsystem/jre/lib/ext/access-bridge-64.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/cldrdata.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/dnsns.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/jaccess.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/jfxrt.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/localedata.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/nashorn.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/sunec.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/sunjce_provider.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/sunmscapi.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/sunpkcs11.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/zipfs.jar

应用类加载器 Classloader -> sun.misc.Launcher$AppClassLoader@18b4aac2
 ===> file:/D:/java_oprationsystem/jre/lib/charsets.jar
 ===> file:/D:/java_oprationsystem/jre/lib/deploy.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/access-bridge-64.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/cldrdata.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/dnsns.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/jaccess.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/jfxrt.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/localedata.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/nashorn.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/sunec.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/sunjce_provider.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/sunmscapi.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/sunpkcs11.jar
 ===> file:/D:/java_oprationsystem/jre/lib/ext/zipfs.jar
 ===> file:/D:/java_oprationsystem/jre/lib/javaws.jar
 ===> file:/D:/java_oprationsystem/jre/lib/jce.jar
 ===> file:/D:/java_oprationsystem/jre/lib/jfr.jar
 ===> file:/D:/java_oprationsystem/jre/lib/jfxswt.jar
 ===> file:/D:/java_oprationsystem/jre/lib/jsse.jar
 ===> file:/D:/java_oprationsystem/jre/lib/management-agent.jar
 ===> file:/D:/java_oprationsystem/jre/lib/plugin.jar
 ===> file:/D:/java_oprationsystem/jre/lib/resources.jar
 ===> file:/D:/java_oprationsystem/jre/lib/rt.jar
 ===> file:/D:/MyJavaProject/online-study/study/out/production/study/
 ===> file:/D:/ideaIU-2018.3.6.win/lib/idea_rt.jar
 ===> file:/D:/ideaIU-2018.3.6.win/lib/rt/debugger-agent.jar
自定义ClassLoder

作用:

  • 类的动态加载
  • 对代码的加密保护

通过字节码文件的反编译可以得到Java源代码,所以对字节码文件进行加密,再通过自定义的类加载器加载加密后的字节码文件(实际就是将加密的字节码文件先解密转换回正常的字节码文件二进制内容然后传到类加载器正常加载),在不影响代码正常运行的情况下,可以实现对代码的保护和加密

package jvm;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;


public class XlassLoader extends ClassLoader {

    public static void main(String[] args) throws Exception {
        // 相关参数
        final String className = "Hello";
        final String methodName = "hello";
        // 创建类加载器
        ClassLoader classLoader = new XlassLoader();
        // 加载相应的类
        Class clazz = classLoader.loadClass(className);
        // 看看里面有些什么方法
        for (Method m : clazz.getDeclaredMethods()) {
            System.out.println(clazz.getSimpleName() + "." + m.getName());
        }
        // 创建对象
        Object instance = clazz.getDeclaredConstructor().newInstance();
        // 调用实例方法
        Method method = clazz.getMethod(methodName);
        method.invoke(instance);
    }

    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        // 如果支持包名, 则需要进行路径转换
        String resourcePath = name.replace(".", "/");
        // 文件后缀
        final String suffix = ".xlass";
        // 获取输入流
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(resourcePath + suffix);
        try {
            // 读取数据
            int length = inputStream.available();
            byte[] byteArray = new byte[length];
            inputStream.read(byteArray);
            // 转换
            byte[] classBytes = decode(byteArray);
            // 通知底层定义这个类
            return defineClass(name, classBytes, 0, classBytes.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(name, e);
        } finally {
            close(inputStream);
        }
    }

    // 解码
    private static byte[] decode(byte[] byteArray) {
        byte[] targetArray = new byte[byteArray.length];
        for (int i = 0; i < byteArray.length; i++) {
        	//反向解密
            targetArray[i] = (byte) (255 - byteArray[i]);
        }
        return targetArray;
    }

    // 关闭
    private static void close(Closeable res) {
        if (null != res) {
            try {
                res.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

添加引用类的几种方式( 添加额外的类class文件或jar包给类加载加载)

1、放到 JDK 的 lib/ext 下,或者在编译Java文件,启动Java进程时添加额外的参数 -Djava.ext.dirs 指定额外的路径添加到class path。

在这两种方式所涉及的目录中类class文件或jar包都会被添加进扩展类加载器的加载范围

2、 java-cp或-classpath(将额外的类class文件或jar包所在路径添加到扩展类加载器的class path) 或者把额外的类class文件或jar包放到当前的class path
3、自定义 ClassLoader 加载
4、拿到当前执行类的 ClassLoader,反射调用 addUrl 方法添加 Jar 或路径(JDK9 无效)

package jvm;


import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class TestAddUrl {

    public static void main(String[] args) throws Exception {
        URLClassLoader classLoader = (URLClassLoader) TestAddUrl.class.getClassLoader();
        String dir = "/Users/anywhere/Hello";
        Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
        method.setAccessible(true);
        URL url = new URL(dir);
        method.invoke(classLoader, url);

        Class klass = Class.forName("Hello",true, classLoader);
        //JDK9以后应用类加载器和扩展类加载器和URLClassLoder平级了,不再有继承关系,此时不能再通过当前类的类加载器拿到URLClassLoder,可以使用如下方法
        //Class klass2 = Class.forName("Hello",true, new URLClassLoader(new URL[]{new URL(dir)}));
        Object obj = klass.newInstance();
        Method hello = klass.getDeclaredMethod("hello");
        hello.invoke(obj);
    }

}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/434959.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号