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

java中classloader学习

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

java中classloader学习

文章目录
  • 0. java相关环境变量:
    • JAVA_HOME
    • PATH
    • CLASSPATH
  • 1. 什么是classloader
  • 2. 一个类怎么被加载到jvm中的
      • 1. 三个加载器
      • 2. 加载顺序/委托加载机制
      • 3. 代码实现类的加载
        • 3.1 几个重要的函数
          • 3.1.1 loadClass
          • 3.1.2 defineClass
        • 3.2 利用URLClassLoader加载远程class文件
        • 3.3 使用ClassLoader类的defineClass函数直接加载字节码
  • 参考文章

0. java相关环境变量: JAVA_HOME

指的是你JDK安装的位置,一般默认安装在C盘,如

C:Program FilesJavajdk1.8.0_91

PATH

path路径中的可执行文件,在命令行输入名字后可直接执行,不需要加上绝对路径,例如javac、java等命令。

PATH=%JAVA_HOME%bin;%JAVA_HOME%jrebin;%PATH%;

CLASSPATH

CLASSPATH=.;%JAVA_HOME%lib;%JAVA_HOME%libtools.jar

jar包路径。需要注意的是前面的.;,.代表当前目录。

1. 什么是classloader

就是在程序的运行中将其他的java class文件加载到jvm虚拟机中的一种技术。classloader本质是一个类,这个类中有实现此种技术的函数。

2. 一个类怎么被加载到jvm中的

类是被类加载器加载到jvm中的

1. 三个加载器

理论上所有的加载器都可以将字节码加载到jvm中。

我们可以自己定义一个classloader加载器,这个加载器的父类加载器是应用类加载器(appClassLoader),应用类加载器(appClassLoader)的父类加载器是平台类加载器(PlatformClassLoader),平台类加载器(PlatformClassLoader)但父类加载器是根类加载器,这里的父类与类中继承概念中的父类要区分,可以仅仅将这个父类理解成一个名字,而不是真正的继承关系。他们三个其实都是继承了URLClassLoader类。


  1. 根类加载器(Bootstrap ClassLoader)
    根装载器不是ClassLoader的子类,由C++编写,因此在java中看不到他,负责装载JRE的核心类库,如JRE目录下的rt.jar,charsets.jar等。
    根据代码逻辑,平台类加载器的父加载器为null,接着看代码我们知道,如果父加载器为null的时候则自动会调用Bootstrap ClassLoader这个加载器作为此加载器的父加载器。因此从逻辑上Bootstrap ClassLoader加载器是平台类加载器的父加载器。

  2. 平台类加载器(PlatformClassLoader)(jdk1.8之后的版本,之前的称为扩展类加载器 ExtClassLoader)
    虽说能拿到,但是我们在实践中很少用到它,它主要加载扩展目录%JAVA_HOME%libext下的jar包,。还可以加载-D java.ext.dirs=path选项指定的目录。

  3. 应用类加载器(appClassLoader)
    它主要加载我们应用程序中的类,它会加载当前应用的classpath的所有类。

2. 加载顺序/委托加载机制

这时候我们假设要加载一个test.class类,并写好了加载的代码:

  1. 我们写的classloader123加载器会先查看缓存中是否有test.class这个类,如果有的话就直接返回,如果没有则询问它的父加载器应用类加载器(appClassLoader)。
  2. 应用类加载器(appClassLoader)会先查看缓存中是否有test.class这个类,如果有的话就直接返回,如果没有则询问它的父加载器平台类加载器(PlatformClassLoader)。
  3. 平台类加载器(PlatformClassLoader)接收到这个命令后会先查看缓存中是否有test.class这个类,如果有的话就直接返回,如果没有则询问它的父加载器根类加载器。
  4. 根类加载器接收到这个命令后会先查看缓存中是否有test.class这个类,如果有的话就直接返回,如果没有则,在它自己事先设定好的寻找路径中找是否有test.class文件,如果有则加载,如果没有则会让它的子加载器平台类加载器(PlatformClassLoader)去尝试加载。
  5. 平台类加载器(PlatformClassLoader)在它自己事先设定好的寻找路径中找是否有test.class文件,如果有则加载,如果没有则会让它的子加载器应用类加载器(appClassLoader)。
  6. 应用类加载器(appClassLoader)会在自己的设定的路径下查看是否有test.class文件,如果有就直接加载到jvm中,如果没有则会问它的子加载器,也就是我们自己写的那个加载器classloader123。
  7. 加载器classloader123会在自己的设定的路径下查看是否有test.class文件,如果有就将这个数据传递给defineclass函数,defineclass函数会将这个类直接加载到jvm中。如果没有找到test.class则会加载失败。
3. 代码实现类的加载 3.1 几个重要的函数 3.1.1 loadClass

protected Class loadClass(String name,boolean resolve)

这个loadclass函数逻辑大概如下:

  1. 调用findLoadedClass(String name)去检测这个class是不是已经加载过了。
  2. 执行父加载器的loadClass方法。如果父加载器为null,则用jvm内置的加载器Bootstrap ClassLoader去替代。这也解释了平台加载器的父加载器为null,但仍然说Bootstrap ClassLoader是它的父加载器。等自行到Bootstrap ClassLoader的时候,就不会再寻找父加载器了,因为已经没有了。
  3. 如果找不到父类加载器等时候,则通过findClass(String name)在各自加载器设定好的路径中查找是不是有这个类,如果没有,则在子加载器中事先设定好的路径中查找。如果找到了就传送给defineclass函数进行加载,defineclass函数会将类直接加载到jvm中。
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) {
                    	//父加载器不为空则调用父加载器的loadClass
                        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();
                    //父加载器没有找到,则调用findclass
                    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()加载寻找到的类
                resolveClass(c);
            }
            return c;
        }
    }
3.1.2 defineClass

defineClass方法是protected属性的,且它是来自ClassLoader类的。我们无法直接从外部进行调用,所以我们这里需要借助反射来调用这个方法。

这个函数的功能是,加载二进制的java字节码到jvm中。

3.2 利用URLClassLoader加载远程class文件

加载的流程如下:
1.

classloader代码:

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

public class LoadHttpRemote {
    public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        //注意url结尾需要有斜杠
        URL[] urls = {new URL("http://121.111.111.111:8000/")};
        URLClassLoader loader = URLClassLoader.newInstance(urls);
        Class c= loader.loadClass("Test");
        Object obj = c.newInstance();
        Method method = c.getDeclaredMethod("say",null);
        //通过反射调用Test类的say方法
        method.invoke(obj, null);
    }
}

test.java代码:

public class Test {

    public void say(){
        System.out.println("Say Hello1");
    }

}

3.3 使用ClassLoader类的defineClass函数直接加载字节码

因为defineClass是protected属性的,所以只能通过反射来使用:

import sun.misc.base64Decoder;

import java.lang.reflect.Method;

public class DefineClassTest {
    public static void main(String[] args) throws Exception {
        //利用反射来调用。
        Class clas = Class.forName("java.lang.ClassLoader");
        Method defineclass = clas.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        defineclass.setAccessible(true);
        String base64_code = "yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEAn" +
            "Bjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVsn" +
            "bG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZhn" +
            "L2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3Ryn" +
            "ZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nn" +
            "OylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAn" +
            "AAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM";
        base64Decoder base64Decoder = new base64Decoder();
        byte[] code = base64Decoder.decodeBuffer(base64_code);
        //调用AppClassLoader加载器中的defineclass函数来实现类的加载
        Class Hello = (Class)defineclass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
        //调用ExtClassLoader加载器中的defineclass函数来实现类的加载
        //Class Hello = (Class)defineclass.invoke(ClassLoader.getSystemClassLoader().getParent(), "Hello", code, 0, code.length);
        Hello.newInstance();

    }
}
参考文章

一篇文章带你深入理解 Java 中的Class.getClassLoader
一看你就懂,超详细java中的ClassLoader详解
Java安全-Java动态加载字节码方法

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

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

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