前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java类加载器的学习笔记

Java类加载器的学习笔记

原创
作者头像
逆回十六夜
修改2019-10-08 11:17:00
4030
修改2019-10-08 11:17:00
举报
文章被收录于专栏:逆回十六夜

学习资料来源:https://www.bilibili.com/video/av30023103/

部分段落链接:https://blog.csdn.net/ln152315/article/details/79223441

一个Java文件从编码完成到最终执行,一般主要包括两个过程

  • 编译
  • 运行

编译,即把我们写好的java文件,通过javac命令编译成字节码,也就是我们常说的.class文件。

运行,则是把编译声称的.class文件交给Java虚拟机(JVM)执行。

而我们所说的类加载过程即是指JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象的过程。

举个通俗点的例子来说,JVM在执行某段代码时,遇到了class A, 然而此时内存中并没有class A的相关信息,于是JVM就会到相应的class文件中去寻找class A的类信息,并加载进内存中,这就是我们所说的类加载过程。

由此可见,JVM不是一开始就把所有的类都加载进内存中,而是只有第一次遇到某个需要运行的类时才会加载,且只加载一次。

类加载器的作用

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时的数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区数据的访问入口。

类缓存

标准的javaSE类加载器可以按照要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过,JVM垃圾收集器可以回收这些Class对象。

类加载器的层次结构(树状结构)

引导类加载器(bootstrap class loader)------C语言编写

  • 它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar,或sun.boot.class.path路径下的内容),使用原生代码来实现的(C++),并不继承自java.lang.ClassLoader。
  • 加载扩展类和应用程序类加载器。并制定他们的父类加载器。

扩展类加载器(extensions class loader) ------java语言编写,继承自java.lang.ClassLoader

  • 用来加载java的扩展库(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路径下的内容)。java虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载java类。
  • 由sun.misc.Lancher$ExtClassLoader实现

应用程序类加载器(application class loader) ------java语言编写,继承自java.lang.ClassLoader

  • 它根据java应用的类路径(classpath,java.class.path路径)加载类,一般来说,java应用的类都是它来完成加载的。
  • 由sun.misc.Launcher$AppClassLoader实现

自定义类加载器 ------java语言编写,继承自java.lang.ClassLoader

  • 开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。

注:为什么会有自定义类加载器

  • 一方面是由于java代码很容易被反编译,如果需要对自己的代码加密的话,可以对编译后的代码进行加密,然后再通过实现自己的自定义类加载器进行解密,最后再加载。
  • 另一方面也有可能从非标准的来源加载代码,比如从网络来源,那就需要自己实现一个类加载器,从指定源进行加载

java.class.ClassLoader类介绍

作用:

java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个java类,即java.lang.Class类的一个实例

除此之外,ClassLoader还负责加载java应用所需的资源,如图像文件和配置文件等。

相关方法:

  • getParent() 返回该类加载器的父类加载器
  • loadClass(String name) 加载名称为name的类,返回结果是java.lang.Class类的实例
  • findClass(String name) 查找名称为name的类,返回结果是java.lang.Class类的实例
  • findLoadedClass(String name) 查找名称为name 的已经被加载过的类,返回的结果是java.lang.Class类的实例
  • defineClass(String name,byte[] b,int off,int len) 把字节数组b中的内容转换成java类,返回的结果是java.lang.Class类的实例。这个方法被声明为final
  • resolveClass(Class<?> c)链接指定的java类

对以上给出的方法,表示类名称的name参数的值是类的二进制名称,需要注意的是内部类的表示,如com.example.Sample$1和com.example.Sample$Inner等表示方式。

类加载器的代理模式

代理模式

  • 交给其他加载器来加载指定的类

双亲委托机制

  • 就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次追溯,知道最高的爷爷辈的,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
  • 双亲委托机制是为了保证java核心库的类型安全。---这种机制就保证不会出现用户自己能定义java.lang.Object类的情况.
  • 类加载器除了用于加载类,也是安全的最基本的屏障。

双亲委托机制是代理模式的一种

  • 并不是所有的类加载器都采用双亲委托机制。
  • tomcat服务器类加载器也使用代理模式,所不同的是它首先尝试去加载某个类,如果找不到再代理给父类加载器,这与一般类加载器的顺序是相反的。

如何自定义实现类加载器

  • 继承:java.lang.ClassLoader
  • 首先检查请求的类型是否已经被这个类装载器装载到命名空间中去了,如果已经装载,直接返回。
  • 委派类加载请求给父类加载器,如果父类加载器能够完成,则返回父类加载器加载的Class实例;
  • 调用本类加载器的findClass(...)方法,试图获取对应的字节码,如果获取的到,则调用defineClass(...)导入类型到方法区;如果获取不到对应的字节码或者其他原因失败,返回异常给loadClass(...),loadClass(...)转抛异常,终止加载过程。

注意:被两个类加载器加载的同一个类,JVM不认为是相同的类。

  • 文件类加载器
  • 网络类加载器
  • 加密解密类加载器(取反操作,des对称加密解密)

文件类加载器代码

代码语言:javascript
复制
package classinit;

import java.io.*;

public class FileSystemClassLoader extends java.lang.ClassLoader {
    //传一个类名,在对应的文件目录中寻找class文件
    private String rootDir;         //根目录

    public FileSystemClassLoader(String rootDir){
        this.rootDir = rootDir;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        Class<?> c =findLoadedClass(name);
        //查询有没有加载过这个类,如果已经加载,则直接返回加载好的类,如果没有,则加载新的类
        if(c!=null){
            return c;
        }else{
            ClassLoader parent = this.getParent();
            try {
                c = parent.loadClass(name);     //委派给父类加载器
            }catch (Exception e){
                //e.printStackTrace();
            }
            if(c!=null){
                return c;
            }else{
                //想办法读取这个文件,转化为字节数组
                byte[] classData = getClassData(name);
                if(classData==null){
                    throw new ClassNotFoundException();
                }else{
                    c = defineClass(name,classData,0,classData.length);
                }
            }
        }
        return c;
    }

    private byte[] getClassData(String classname) {
        String path = rootDir + "/" +classname.replace(".","/")+".class";
        //将流中的数据转化为字节数组
        InputStream is = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            is = new FileInputStream(path);
            byte[] buffer = new byte[1024];
            int temp = 0;
            while((temp = is.read(buffer))!=-1){
                baos.write(buffer,0,temp);
            }
            baos.flush();
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }finally {
            if(is!=null){
                try {
                    is.close();
                    baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
代码语言:javascript
复制
package classinit;

public class Demo03 {
    public static void main(String[] args){
        FileSystemClassLoader loader = new FileSystemClassLoader("d:/myjava");
        FileSystemClassLoader loader2 = new FileSystemClassLoader("d:/myjava");

        try {
            Class<?> c1 = loader.loadClass("pri.stu.lwj.HelloWorld");
            Class<?> c2 =loader.loadClass("pri.stu.lwj.HelloWorld");
            Class<?> c3 =loader2.loadClass("pri.stu.lwj.HelloWorld");
            Class<?> c4 =loader2.loadClass("java.lang.String");
            Class<?> c5 = loader2.loadClass("classinit.Demo03");
            System.out.println(c1);
            System.out.println(c1.hashCode());
            System.out.println(c2.hashCode());
            System.out.println(c3.hashCode());
            System.out.println(c4.hashCode());
            System.out.println(c5.hashCode());
            System.out.println(c1.getClassLoader());
            System.out.println(c3.getClassLoader());
            System.out.println(c4.getClassLoader());
            System.out.println(c5.getClassLoader());
        } catch (ClassNotFoundException e) {
            //e.printStackTrace();
        }
    }
}

结果

网络类加载器代码(对文件类加载器稍有修改)

代码语言:javascript
复制
package classinit;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

public class NetClassLoader extends ClassLoader {
    //www.XXX....
    private String rootUrl;         //Url

    public NetClassLoader(String rootUrl){
        this.rootUrl = rootUrl;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        Class<?> c =findLoadedClass(name);
        //查询有没有加载过这个类,如果已经加载,则直接返回加载好的类,如果没有,则加载新的类
        if(c!=null){
            return c;
        }else{
            ClassLoader parent = this.getParent();
            try {
                c = parent.loadClass(name);     //委派给父类加载器
            }catch (Exception e){
                //e.printStackTrace();
            }
            if(c!=null){
                return c;
            }else{
                //想办法读取这个文件,转化为字节数组
                byte[] classData = getClassData(name);
                if(classData==null){
                    throw new ClassNotFoundException();
                }else{
                    c = defineClass(name,classData,0,classData.length);
                }
            }
        }
        return c;
    }

    private byte[] getClassData(String classname) {
        String path = rootUrl + "/" +classname.replace(".","/")+".class";
        //将流中的数据转化为字节数组
        InputStream is = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            URL url = new URL(path);
            //is = new FileInputStream(path);
            is = url.openStream(); 
            byte[] buffer = new byte[1024];
            int temp = 0;
            while((temp = is.read(buffer))!=-1){
                baos.write(buffer,0,temp);
            }
            baos.flush();
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }finally {
            if(is!=null){
                try {
                    is.close();
                    baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

加密解密类加载器(取反操作,des对称加密解密)

加密工具:EncriptUtil

代码语言:javascript
复制
package classinit;

import java.io.*;

public class EncriptUtil {

    public static void main(String[] args){
        encript(new File("d:/myjava/HelloWorld.class"),new File("d:/myjava/temp/HelloWorld.class"));
    }

    public static void encript(File src, File dest){
        FileInputStream fis = null;
        FileOutputStream fos =null;
        try {
            fis = new FileInputStream(src);
            fos = new FileOutputStream(dest);
            //进行加密解密操作

            int temp = -1;
            while((temp = fis.read())!=-1){
                fos.write(temp^0xff);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(fis!=null) {
                    fis.close();
                }
            }catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(fos!=null) {
                    fos.close();
                }
            }catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

用工具加密后:

这个class无法被常规类加载器识别
这个class无法被常规类加载器识别

解密类加载器

代码语言:javascript
复制
package classinit;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class DecriptClassLoader extends ClassLoader {
    //传一个类名,在对应的文件目录中寻找class文件
    private String rootDir;         //根目录

    public DecriptClassLoader(String rootDir){
        this.rootDir = rootDir;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        Class<?> c =findLoadedClass(name);
        //查询有没有加载过这个类,如果已经加载,则直接返回加载好的类,如果没有,则加载新的类
        if(c!=null){
            return c;
        }else{
            ClassLoader parent = this.getParent();
            try {
                c = parent.loadClass(name);     //委派给父类加载器
            }catch (Exception e){
                //e.printStackTrace();
            }
            if(c!=null){
                return c;
            }else{
                //想办法读取这个文件,转化为字节数组
                byte[] classData = getClassData(name);
                if(classData==null){
                    throw new ClassNotFoundException();
                }else{
                    c = defineClass(name,classData,0,classData.length);
                }
            }
        }
        return c;
    }

    private byte[] getClassData(String classname) {
        String path = rootDir + "/" +classname.replace(".","/")+".class";
        //将流中的数据转化为字节数组
        InputStream is = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            is = new FileInputStream(path);

            int temp = -1;
            while((temp = is.read())!=-1){
                baos.write(temp^0xff);
            }
            baos.flush();
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }finally {
            if(is!=null){
                try {
                    is.close();
                    baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
采用解密类加载器后果然可以解密了
采用解密类加载器后果然可以解密了

测试程序

代码语言:javascript
复制
package classinit;

public class Demo04 {
    public static void main(String[] args) throws Exception {
        //测试取反操作
        //int a = 3;  //0000 0011
        //System.out.println(Integer.toBinaryString(a^0xff));

        //测试正常类加载器加载加密类
        //FileSystemClassLoader loader = new FileSystemClassLoader("d:/myjava/temp");
        //Class<?> c =loader.loadClass("HelloWorld");
        //System.out.println(c);

        //测试解密类加载器解密
        DecriptClassLoader loader = new DecriptClassLoader("d:/myjava/temp");
        Class<?> c = loader.loadClass("HelloWorld");
        System.out.println(c);
    }
}

线程上下文类加载器

双亲委托机制以及类加载器的问题

  • 一般情况下,保证同一个类中所关联的其他类都是有当前类的类加载器所加载的。

比如,ClassA本身在Ext下找到,那么他里面new出来的一些类也就只能用Ext去查找了(不会低一个级别),所以有些命名App可以找到的,却找不到了。

  • JDBC API,他有实现的driven部分(mysql/sql server),我们的JDBC API都是由Boot或者Ext来载入的,但是JDBC driver确实由Ext或者App来载入那么就有可能找不到driver了。在Java领域,其实只要分成这种Api+SPI(Service Provide Interface 特定厂商提供)的,都会遇到此问题。
  • 常见的SPI有JDBC,JCE,JNDI,JAXP和JBI等,这些SPI的接口由java核心库来提供,如JAXP的SPI接口定义包含在javax.xml.parsers包中。SPI的接口是java核心库的一部分,是引导类加载器来加载的;SPI实现的JAVA类一般是由系统类加载器来加载的。引导类加载器是无法找到SPI的实现类的,因为它只加载Java的核心库。

通常当你需要动态加载资源的时候,你至少有3个ClassLoader可以选择:

  1. 系统类加载器或者叫做应用类加载器(system classloader or application classloader)
  2. 当前类加载器
  3. 当前线程类加载器

线程类加载器是为了抛弃双亲委派加载链模式。

  • 每个线程都有一个关联的上下文类加载器。如果你使用new Thread()方式生成新的线程,新鲜车程将继承其父线程的上下文类加载器。如果程序对线程上下文类加载器没有任何改动的话,程序中的所有线程都将使用系统类加载器作为上下文类加载器。

Thread.currentThread().getContextClassLoader()

Thread.currentThread().setContextClassLoader()

简而言之,接口是由一方设计(Oracle),由某个类加载器实现(第三方),实现类由另一方设置,由另一个类加载实现,产生了冲突,因而要用新的机制暂时代替双亲委托机制。实现方法就是在类加载器的代码中不去遵守双亲委托机制的代码。

测试代码

代码语言:javascript
复制
package classinit;

public class Demo05 {
    public static void main(String[] args){
        ClassLoader loader = Demo05.class.getClassLoader();
        System.out.println(loader);

        ClassLoader loader2 = Thread.currentThread().getContextClassLoader();
        System.out.println(loader2);

        Thread.currentThread().setContextClassLoader(new FileSystemClassLoader("d:/myjava/"));
        System.out.println(Thread.currentThread().getContextClassLoader());

        try {
            Class<Demo01> c = (Class<Demo01>) Thread.currentThread().getContextClassLoader().loadClass("classinit.Demo01");
            System.out.println(c);
            System.out.println(c.getClassLoader());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

测试结果

因为ClassLoader写了双亲委派的代码,因而Demo01是给AppClassLoader解决的
因为ClassLoader写了双亲委派的代码,因而Demo01是给AppClassLoader解决的

Tomcat服务器的类加载机制

一切都是为了安全

Tomcat不能使用系统默认的类加载器

  • 如果Tomcat跑的WEB项目使用系统的类加载器是想象当危险的,你可以直接肆无忌惮的使用操作系统的各个目录了。
  • 对于运行在JavaEE容器的web应用来说,类加载器的实现方式与一般的java应用有所不同
  • 每个web应用都有一个对应的类加载器实例,该类加载器也使用代理模式(不用于前面说的双亲委托机制),所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般的类加载器的顺序是相反的。但也是为了安全,这样核心库就不在查询范围之内。

为了安全tomcat需要实现自己的类加载器

  • 我可以限制你只能把类写在指定的地方,否则不给你加载。

OSGI(面向Java的动态模型系统)开放服务网关协议,Open Service Gateway Initiative

  • 谁的类谁来加载。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 类加载器的作用
  • 类缓存
  • 类加载器的层次结构(树状结构)
    • 引导类加载器(bootstrap class loader)------C语言编写
      • 扩展类加载器(extensions class loader) ------java语言编写,继承自java.lang.ClassLoader
        • 应用程序类加载器(application class loader) ------java语言编写,继承自java.lang.ClassLoader
          • 自定义类加载器 ------java语言编写,继承自java.lang.ClassLoader
          • java.class.ClassLoader类介绍
            • 作用:
              • 相关方法:
              • 类加载器的代理模式
                • 代理模式
                  • 双亲委托机制
                    • 双亲委托机制是代理模式的一种
                    • 如何自定义实现类加载器
                      • 文件类加载器代码
                        • 结果
                          • 网络类加载器代码(对文件类加载器稍有修改)
                            • 加密解密类加载器(取反操作,des对称加密解密)
                              • 加密工具:EncriptUtil
                              • 用工具加密后:
                              • 解密类加载器
                              • 测试程序
                          • 线程上下文类加载器
                            • 双亲委托机制以及类加载器的问题
                              • 通常当你需要动态加载资源的时候,你至少有3个ClassLoader可以选择:
                                • 线程类加载器是为了抛弃双亲委派加载链模式。
                                  • Thread.currentThread().getContextClassLoader()
                                  • Thread.currentThread().setContextClassLoader()
                                  • 测试代码
                                  • 测试结果
                              • Tomcat服务器的类加载机制
                                • 一切都是为了安全
                                  • Tomcat不能使用系统默认的类加载器
                                • 为了安全tomcat需要实现自己的类加载器
                                • OSGI(面向Java的动态模型系统)开放服务网关协议,Open Service Gateway Initiative
                                相关产品与服务
                                容器服务
                                腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                                领券
                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档