类的加载器

概述

1、类加载器是 JVM 执行类加载机制的前提

2、作用

(1)ClassLoader 是 Java 核心组件,所有 Class 由 ClassLoader 进行加载

(2)ClassLoader 负责通过各种方式,将 Class 信息的二进制数据流读入JVM内部,转换为一个与目标类对应的 java.lang.Class 对象实例,然后交给 JVM 进行链接、初始化等操作

(3)ClassLoader 在整个装载阶段,只能影响到类的加载,而无法通过 ClassLoader 改变类的链接、初始化,至于它是否可以运行,则由 Execution Engine 决定

 

类加载器的分类

1、.class 文件的显式加载、隐式加载,指 JVM 加载 .class 文件到内存的方式

(1)显式加载:在代码中,通过调用 ClassLoader 加载 Class 对象,如:直接使用 Class.forName(name)、this.getClass().getClassLoader().loadClass() 加载 Class 对象

(2)隐式加载:不直接在代码中调用 ClassLoader 方法加载 Class 对象,而是通过虚拟机自动加载到内存中,如:在加载某个类的 .class 文件时,该类的 .class 文件中引用另外一个类的对象,此时额外引用的类将通过 JVM 自动加载到内存中

2、在开发中

(1)混用以上两种方式

(2)一般情况下,Java 开发并不需要在程序中显式地使用类加载器

 

类加载器的必要性

1、避免在开发中遇到 java.lang.ClassNotFoundException 异常、java.lang.NoClassDefFoundError 异常

2、需要支持类的动态加载,需要对编译后的字节码文件进行加解密操作

3、可以在程序中编写自定义类加载器,重新定义类的加载规则,以实现一些自定义的处理逻辑

 

类的唯一性

1、对于任意一个类,都需要由加载它的类加载器、这个类本身,共同确认其在 JVM 中的唯一性

2、每一个类加载器,都拥有一个独立的类命名空间:比较两个类是否相等

3、只有在两个类是由同一个类加载器加载的前提下才有意义,否则,即使两个类源自同一个 .class 文件,被同一个虚拟机加载,只要加载他们的类加载器不同,则两个类就必定不相等

 

命名空间

1、每个类加载器都有自己的命名空间

2、命名空间由该加载器,及所有父加载器所加载的类组成

3、在同一命名空间中,不会出现全类名相同的两个类

4、在不同的命名空间中,有可能会出现全类名相同的两个类

5、在大型应用中,借助命名空间,来运行同一个类的不同版本

 

类加载机制的基本特征

1、双亲委派模型

(1)不是所有类加载都遵守这个模型

(2)有时启动类加载器所加载的类型,可能要加载用户代码,如:JDK 内部 ServiceProvider / ServiceLoader 机制

(3)用户可以在标准 API 框架上,提供自定义实现,JDK 需要提供默认的参考实现,如:Java 中 JNDI、JDBC、文件系统、Cipher 等,这种情况不会使用双亲委派模型加载,而是利用上下文加载器

2、可见性

(1)子类加载器可以访问父加载器加载的类型

(2)但不允许父类加载器访问子类加载器的类型

(3)因为缺少必要的隔离,没有办法利用类加载器去实现容器的逻辑

3、单一性

(1)由于父加载器的类型对于子加载器是可见的,所以父加载器中加载过的类型,不会在子加载器中重复加载

(2)但不同类加载器,因为同一类型互相并不可见,所以可以被加载多次

 

类加载器之间的关系

1、Launcher 类核心代码

Launcher.ExtClassLoader var1;

try {
    var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
    throw new InternalError("Could not create extension class loader", var10);
}

try {
    this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
    throw new InternalError("Could not create application class loader", var9);
}

Thread.currentThread().setContextClassLoader(this.loader);

(1)ExtClassLoader 的 Parent 类是 null

(2)AppClassLoader 的 Parent 类是 ExtClassLoader

(3)当前线程的 ClassLoader 是 AppClassLoader

2、此处 Parent 类并不是 Java 的继承关系,而是一种包含关系

 

类的加载器分类

1、JVM 支持两种类型的类加载器

(1)启动类加载器(Bootstrap ClassLoader):使用 C++ 语言实现,是虚拟机自身的一部分

(2)自定义类加载器(User-Defined ClassLoader):其他所有的类加载器,这些类加载器都由 Java 语言实现,独立存在于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader

2、自定义类加载器一般指程序中由开发人员自定义的类加载器,但是 JVM 规范却没有定义,而是将所有派生于抽象类 ClassLoader 的类加载器都划分为自定义类加载器

(1)启动类加载器通过 C / C++ 编写,而自定义类加载器都是由 Java 编写

(2)虽然扩展类加载器、应用程序类加载器,是被 JDK 开发人员使用 Java 编写,但也属于自定义类加载器

3、无论类加载器的类型如何划分,在程序中最常见的类加载器始终只有 3 个

(1)启动类加载器(Bootstrap ClassLoader)<- 扩展类加载器(Extension ClassLoader)<- 应用程序类加载器(AppClassLoader)/ 系统类加载器 <- 用户自定义类加载器(User Defined Class Loader)

(2)除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加戟器

(3)不同类加载器不是继承关系,实际上是包含关系,在下层加载器中,包含着上层加载器的引用

(4)因为子类加载器中包含父类加载器的引用,所以可以通过子类加载器的方法,获取对应的父类加载器

(5)启动类加载器使用 C++ 编写,不是一个 Java 类,因此在 Java 程序中只能打印出空值

5、数组类 Class 对象,不是由类加载器去创建,而是在 Java 运行期,JVM 根据需要自动创建

(1)对于数组类的类加载器,通过 Class.getClassLoader() 返回,与数组当中元素类型的类加载器相同

(2)如果数组当中的元素类型为基本数据类型,数组类没有类加载器

 

启动类加载器 / 引导类加载器

1、Bootstrap ClassLoader

2、使用 C / C++ 实现,嵌套在 JVM 内部

(1)C / C++:指针函数 & 函数指针、C++ 支持多继承、更加高效

(2)Java:由 C++ 演变,单继承

3、作用

(1)加载 Java 核心库:<JAVA_HOME>\lib目录,或者被 -Xbootclasspath 参数所指定的路径中存放的,而且是 Java 虚拟机能够识别的类库加载到虚拟机的内存中(按照文件名识别,如:rt.jar、tools.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)

(2)提供 JVM 自身需要的类

4、并不继承自 java.lang.ClassLoader,没有父加载器

5、出于安全考虑,启动类加载器只加载包名为 java、javax、sun 等开头的类

6、加载扩展类加载器、应用程序类加载器,并为其指定父类加载器

7、启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器去处理,那直接使用 null 代替即可

 

扩展类加载器

1、Extension ClassLoader

2、在 sun.misc.Launcher$ExtClassLoader 以 Java 代码的形式实现

3、继承 ClassLoader 类

4、父类加载器为启动类加载器

5、加载类库

(1)从 java.ext.dirs 系统属性所指定的目录中加载类库

(2)从 <JAVA_HOME>\ib\ext 子目录下加载类库,如果用户创建的 jar 包放在此目录下,也会被扩展类加载器自动加载

6、Java 系统类库的扩展机制

(1)JDK 的开发团队允许用户将具有通用性的类库放置在 ext 目录里以扩展 Java SE 的功能

(2)在 JDK 9 之后,这种扩展机制被模块化带来的天然的扩展能力所取代

(3)由于扩展类加载器是由 Java 代码实现的,开发者可以直接在程序中使用扩展类加载器来加载 Class 文件

 

系统类加载器 / 应用程序类加载器

1、AppClassLoader

2、Java 编写,由 sun.misc.Launcher$AppClassLoader 实现

3、继承 ClassLoader 类

4、父类加载器为扩展类加载器

5、负责加载环境变量 classpath,或系统属性 java.class.path 指定路径下的类库

6、应用程序中的类加载器,默认是系统类加载器

7、是用户自定义类加载器的默认父加载器

8、通过 ClassLoader 的 getSystemClassLoader(),获取到该类加载器,所以有些场合中也称它为“系统类加载器”

9、开发者同样可以直接在代码中使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器

 

用户自定义类加载器

1、在 Java 开发中,类的加载几乎由上述 3 种类加载器相互配合执行

2、在必要时,可以自定义类加载器,定制类的加载方式

(1)可以实现类库的动态加载,加载源可以是本地 JAR 包,可以是网络上的远程资源

(2)可以实现插件机制,,如:OSGI 组件框架、Eclipse 插件机制

(3)类加载器为应用程序提供一种动态增加新功能的机制,这种机制无须重新打包发布应用程序就能实现

(4)可以实现应用隔离,如:Tomcat,Spring 等中间件、组件框架都在内部实现自定义的加载器,并通过自定义加载器隔离不同的组件模块

3、自定义类加载器通常需要继承 ClassLoader

 

ClassLoader 主要方法

1、抽象类 java.lang.ClassLoader

(1)内部没有抽象方法

(2)所有用户自定义的类加载器,都应该继承 ClassLoader

2、返回该类加载器的父类加载器

public final ClassLoader getParent()

3、加载名称为 name 的类

public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}

(1)返回结果为 java.lang.Class 类的实例

(2)如果找不到类,则返回 ClassNotFoundException 异常

(3)该方法中的逻辑实现:双亲委派模式

 4、查找二进制名称为 name 的类

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

(1)返回结果为 java.lang.Class 类的实例

(2)一个 protected 方法,JVM 鼓励重写此方法,需要自定义加载器遵循双亲委托机制

(3)该方法会在检查完父类加载器之后被 loadClass() 调用

(4)在 JDK 1.2 之前,在自定义类加载时,必须继承 ClassLoader 类,并重写 loadClass(),从而实现自定义的类加载类

(5)在 JDK 1.2 之后,不再建议用户去覆盖 loadClass(),而建议把自定义的类加载逻辑,定义在 findClass() 中

(6)findClass() 是在 loadClass() 中被调用,当 loadClass() 中的父加载器加载失败后,则会调用自己的 findClass() 完成类加载,保证自定义的类加载器也符合双亲委托模式

(7)ClassLoader 类中并没有实现 findClass() 具体代码逻辑,而是抛出 ClassNotFoundException 异常,同时 findClass() 通常和 defineClass() 一起使用的

(8)一般情况下,在自定义类加载器时,会直接覆盖 ClassLoader 的 findClass(),并编写加载规则,取得要加载类的字节码后,转换成流,然后调用 defineClass() 方法生成类的 Class 对象

5、根据给定的字节数组 b 转换为 Class 的实例

protected final Class<?> defineClass(String name, byte[] b,int off,int len)

(1)off、len 表示实际 Class 信息在 byte 数组中的位置和长度,其中 byte 数组 b 是 ClassLoader 从外部获取的

(2)protected 方法,只有在自定义 ClassLoader 子类中可以使用

(3)将 byte 字节流解析成 JVM 能够识别的 Class 对象(ClassLoader 中已实现该方法逻辑)

(4)这个方法不仅能够通过 .class 文件实例化 Class 对象,也可以通过其他方式实例化 Class 对象,如:网络接收一个类的字节码,然后转换为 byte 字节流创建对应的 Class 对象

(5)defineClass() 通常与 findClass() 一起使用,一般情况下,在自定义类加载器时,会直接覆盖 ClassLoader 的 findClass(),并编写加载规则,取得要加载类的字节码后转换成流,然后调用 defineClass() 生成类的 Class 对象

6、链接指定的一个 Java 类

protected final void resolveClass(Class<?> c)

(1)使用该方法可以使用类的 Class 对象,创建完成的同时也被解析

(2)链接阶段主要是对字节码进行验证、为类变量分配内存、设置初始值,同时将字节码文件中的符号引用,转换为直接引用

7、查找名称为 name 的已经被加载过的类

protected final Class<?> findLoadedClass(String name)

(1)返回结果为 java.lang.Class 类的实例

(2)final 方法,无法被重写

8、一个 ClassLoader 实例

private final ClassLoader parent;

(1)表示的 ClassLoader 为本 ClassLoader 的双亲

(2)在类加载的过程中,ClassLoader 可能会将某些请求交予自己的双亲处理

 

SecureClassLoader、URLClassLoader

1、SecureClassLoader 扩展 ClassLoader

(1)新增几个与使用相关的代码源(对代码源的位置及其证书的验证),权限定义类验证(主要指对 class 源码的访问权限)的方法

(2)一般不直接使用该类,而是使用它的子类 URLClassLoader

2、URLClassLoader

(1)为 ClassLoader 抽象类的空方法,提供具体实现

(2)新增 URLClassPath 类,协助取得 Class 字节码流等功能

(3)在编写自定义类加载器时,若没有复杂需求,可以直接继承 URLClassLoader 类,可以避免编写 findClass(),及其获取字节码流的方式,使自定义类加载器编写更加简洁

 

ExtClassLoader、AppClassLoader

1、拓展类加载器(ExtClassLoader)、系统类加载器(AppClassLoader),都继承 URLClassLoader,是 sun.misc.Launcher 静态内部类

2、sun.misc.Launcher

(1)主要被系统用于启动主应用程序

(2)ExtClassLoader、AppClassLoader 都是由 sun.misc.Launcher 创建

(3)ExtClassLoader 没有重写 loadClass(),AppClassLoader 重写 loadClass()方法,但最终调用父类 loadClass(),两者都遵守双亲委派模式

 

Class.forName()

1、静态方法

2、最常用:Class.forName(String className);

3、根据传入的全类名,返回一个 Class 对象

4、该方法在将 .class 文件加载到内存,同时会执行类的初始化

 

ClassLoader.loadClass()

1、实例方法,需要一个 ClassLoader 对象来调用该方法

2、该方法将 .class 文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化

3、该方法因为需要得到一个 ClassLoader 对象,所以可以根据需要指定使用某个类加载器

 

双亲委派模型

1、类加载器把类加载到 JVM 中,从 JDK 1.2 开始,类的加载过程采用双亲委派机制,更好地保证 Java 平台的安全

2、定义

(1)如果一个类加载器在接到加载类的请求时,首先不会加载该类,而是把这个请求任务委托到父类加载器完成,依次递归到顶层父类,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中

(2)如果父类加载器可以完成类加载任务,就成功返回

(3)如果父类加载器无法完成此加载任务时(它的搜索范围中没有找到所需的类),向下递归直接子类加载,子加载器才会尝试自己去完成加载

(4)如果类加载器检测到该类已加载,则不会向下递归

3、规定类加载的顺序

(1)引导类加载器先加载

(2)若不能加载,由扩展类加载器加载

(3)若不能加载,由系统类加载器,或自定义的类加载器加载

4、双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。不过这里类加载器之间的父子关系一般不是以继承(Inheritance)的关系来实现的,而是通常使用组合(Composition)关系来复用父加载器的代码

 

双亲委派机制的优点

1、避免类的重复加载,确保一个类的全局唯一性

(1)Java 类随着它的类加载器,具备一种带有优先级的层次关系,通过层级关系可以避免类的重复加载

(2)当父亲已经加载该类时,子 ClassLoader 没有必要再加载一次

2、保护程序安全,防止核心 API 被随意篡改

 

双亲委派机制的缺点

1、检查类是否加载的委托过程为单向

(1)结构清晰,明确各个 ClassLoader 职责

(2)同时顶层 ClassLoader,无法访问底层 ClassLoader 所加载的类

2、通常情况下

(1)启动类加载器中的类为系统核心类,包括一些重要的系统接口,而在应用类加载器中的类,为应用类

(2)应用类访问系统类没有问题,但系统类访问应用类就会出现问题

 

双亲委派机制的代码实现

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 {
                    //parent = null,表名父类加载器是引导类加载器
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                //如果不能从非null类加载器中,查找到对应类,抛出 ClassNotFoundException
            }
            
            //当前类加载器,与其父类加载器,未加载此类
            if (c == null) {
                //若仍未找到,则调用当前ClassLoader的findClass()
                long t1 = System.nanoTime();
                c = findClass(name);

                //这是定义类的加载器;记录统计信息
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        //是否进行解析操作
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

1、过程

(1)先在当前加载器的缓存中,查找是否存在目标类,如果存在,直接返回

(2)判断当前加载器的父加载器是否为空,如果不为空,则调用 parent.loadClass(name, false) 进行加载

(3)反之,如果当前加载器的父类加载器为空,则调用 findBootstrapClassorNull(name),让引导类加载器进行加载

(4)如果通过以上 3 条路径都不能成功加载,则调用 findClass(name) 进行加载,该方法最终会调用 java.lang.ClassLoader 的 defineClass 系列的 native 方法加载目标 Java 类

2、双亲委派机制:在 1 中的(2)(3)中体现

(1)如果在自定义的类加载器中重写 java.lang.ClassLoader.loadClass(String) 或 java.lang.ClassLoader.loadclass(String, boolean),抹去双亲委派机制,仅保留 1 中的(1)(4),也不能加载核心类库

(2)因为 JDK 还为核心类库提供一层保护机制,不管是自定义的类加载器,还是系统类加载器、扩展类加载器,最终都必须调用 java.lang.ClassLoader.defineclass(String, byte[], int, int, ProtectionDomain),而该方法会执行 preDefineClass(),该接口中提供对 JDK 核心类库的保护

3、Java 虚拟机规范,没有明确要求类加载器的加载机制一定要使用双亲委派模型,而是建议采用

(1)在Tomcat 中,类加载器所采用的加载机制,与双亲委派模型有一定区别

(2)当默认的类加载器接收到一个类的加载任务时,首先会由它自行加载,当它加载失败时,才会将类的加载任务委派给它的父类加载器执行,这同时是 Serylet 规范推荐的一种做法

 

破坏双亲委派机制

1、双亲委派模型不是一个具有强制性约束的模型,而是 Java 设计者推荐的类加载器实现方式

2、直到 Java 模块化出现为止,双亲委派模型主要出现过 3 次大规模被破坏的情况

3、第一次破坏

(1)在双亲委派模型出现之前,即 JDK 1.2 以前

(2)类加载器的概念、抽象类 java.lang.ClassLoader 在 Java 第一个版本中已经存在

(3)引入双亲委派机制时,为了兼容用户自定义类加载器,无法以技术手段避 免loadClass() 被子类覆盖的可能性,只能在 JDK 1.2 之后的 java.lang.ClassLoader 中,添加一个新 protected 方法 findClass(),并引导用户编写的类加载逻辑时,去重写该方法,而不是在 loadClass() 中编写代码

(4)loadClass() 实现双亲委派的具体逻辑,如果父类加载失败,自动调用自己的 findClass() 完成加载,既不影响自定义逻辑加载类,又保证自定义类加载器符合双亲委派规则

4、第二次破坏:线程上下文类加载器

(1)原因:模型自身的缺陷

(2)双亲委派解决各个类加载器协作时,基础类型的一致性问题(越基础的类由越上层的加载器进行加载)

(3)基础类型之所以基础,是因为它们总是作为被用户代码继承、调用 API 存在

(4)存在基础类型,又要调用回用户的代码

(5)例:JNDI 是 Java 标准服务,它由启动类加载器完成加载(在 JDK 1.3 时加入到 rt.jar),但 JNDI 目的是查找资源、并集中管理,它需要调用由其他厂商实现,并部署在应用程序的 ClassPath 下的 JNDI 服务提供者接口(Service Provider Interface,SPI)代码

(6)服务提供者接口(Service Provider Interface,SPI):在 Java 平台中,核心类 rt.jar 中提供外部服务、可由应用层自行实现的接口

(7)解决:线程上下文类加载器(ThreadContextClassLoader):通过 java.lang.Thread 类的 setContextClassLoader() 设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置,则该类加载器默认为应用程序类加载器

(8)JNDI 服务使用线程上下文类加载器,加载所需的 SPI 服务代码,这是一种父类加载器请求子类加载器完成类加载的行为,实际打通双亲委派模型的层次结构,来逆向使用类加载器,违背双亲委派模型的一般性原则

(9)如:JNDI、JDBC、JCE、JAXB、JBI 等

(10)当 SPI 服务提供者多于一个时,代码就只能根据具体提供者的类型来硬编码判断

(11)在 JDK 6 时,JDK 提供 java.util.ServiceLoader 类,以 META-INF/services 中的配置信息,辅以责任链模式,

(12)默认上下文加载器为应用类加载器,以上下文加载器为中介,使得启动类加载器中的代码,可以访问应用类加载器中的类

5、第三次破坏

(1)原因:用户追求程序动态性而导致,如:代码热替换(Hot Swap)、模块热部署(Hot Deployment)等

(2)JSR-291(OSGi R4.2)实现模块化热部署,每一个程序模块(OSGi 中称为 Bundle)都有一个自己的类加载器,当需要更换一个 Bundle 时,就把 Bundle 连同类加载器一起换掉,以实现代码的热替换

(3)在 OSGi 环境下,类加载器不再为双亲委派模型的树状结构,而是发展为复杂的网状结构

(4)当收到类加载请求时,OSGi 将按照以下顺序进行类搜索

(5)将以 java.∗ 开头的类,委派给父类加载器加载

(6)否则,将委派列表名单内的类,委派给父类加载器加载

(7)否则,将 Import 列表中的类,委派给 Export 类的 Bundle 的类加载器加载

(8)否则,查找当前 Bundle 的 ClassPath,使用自己的类加载器加载

(9)否则,查找类是否在自己的 Fragment Bundle 中,如果在,则委派给 Fragment Bundle 的类加载器加载

(10)否则,查找 Dynamic Import 列表的 Bundle,委派给对应 Bundle 的类加载器加载

(11)否则,类查找失败

(12)只有(5)(6)仍然符合双亲委派模型的原则,其余的类查找都是在平级的类加载器中进行

 

热替换的实现

1、热替换:在程序的运行过程中,不停止服务,只通过替换程序文件来修改程序的行为

2、Java

(1)非原生支持热替换,如果一个类已经加载到系统中,通过修改类文件,无法让系统再来加载,并重定义该类

(2)在 Java 中实现热替换,唯一可行方法是运用 ClassLoader

3、模拟热替换的实现

(1)第一次启动:创建自定义 ClassLoader 实例 1 -> 加载需要热替换的类 1 -> 创建新 ClassLoader 实例 1,创建 Class 对象 1 -> 运行对象方法

(2)热替换时:创建自定义 ClassLoader 实例 2 -> 加载需要热替换的类 2 -> 创建新 ClassLoader 2,创建 Class 对象 2 -> 运行对象方法

(3)注意:由不同 ClassLoader 加载的同名类属于不同的类型,不能相互转换和兼容,即两个不同 ClassLoader 加载同一个类,在虚拟机内部,会认为是完全不同的两个类

 

沙箱安全机制

1、沙箱:一个限制程序运行的环境

2、Java 安全模型核心:Java 沙箱

(1)沙箱机制:将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,保证对代码的有限隔离,防止对本地系统造成破坏

(2)沙箱主要限制系统资源访问,系统资源:CPU、内存、文件系统、网络

(3)不同级别的沙箱,对资源访问的限制不同

(4)所有 Java 程序运行都可以指定沙箱,可以定制安全策略

3、作用

(1)保证程序安全

(2)保护 Java 原生 JDK 代码

4、JDK 1.0

(1)在 Java 中将执行程序分为:本地代码、远程代码

(2)本地代码默认视为可信任,可以访问一切本地资源

(3)远程代码视为不受信,对于非授信的远程代码在早期 Java 实现中,安全依赖于沙箱机制

5、JDK 1.1

(1)JDK 1.0 严格的安全机制限制程序的功能扩展,如:用户的远程代码无法访问本地系统文件

(2)改进安全机制,增加安全策略:允许用户指定代码对本地资源的访问权限

6、JDK 1.2

(1)改进安全机制,增加代码签名

(2)本地代码、远程代码,都按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,实现差异化的代码执行权限控制

7、JDK 1.6

(1)引入域(Domain)概念

(2)虚拟机把所有代码加载到不同的系统域、应用域

(3)系统域:专门负责与关键资源进行交互

(4)各个应用域:通过系统域的部分代理,对各种需要的资源进行访问

(5)虚拟机中不同的受保护域(Protected Domain),对应不同权限(Permission)

(6)存在不同域中的类文件,具有当前域的全部权限

 

自定义类的加载器

1、作用

(1)隔离加载类:在某些框架内隔离中间件、应用模块隔离,把类加载到不同的环境

(2)修改类加载的方式:类的加载模型并非强制,除 Bootstrap 外,其他的加载并非一定要引入,或根据实际情况在某个时间点进行按需进行动态加载

(3)扩展加载源

(4)防止源码泄漏:Java 代码容易被编译和篡改,可以进行编译加密,则类加载也需要自定义,还原加密的字节码

2、常见场景

(1)实现类似进程内隔离,类加载器实际上用作不同的命名空间,以提供类似容器、模块化的效果,如:两个模块依赖于某个类库的不同版本,如果分别被不同的容器加载,就可以互不干扰

(2)应用需要从不同的数据源获取类定义信息,如:网络数据源,而不是本地文件系统,或需要应用操纵字节码,动态修改、生成类型

(3)注意:在一般情况下,使用不同的类加载器,加载不同的功能模块,会提高应用程序的安全性,但如果涉及 Java 类型转换,只有两个类型都是由同一个加载器所加载,才能进行类型转换,否则转换时会发生异常

3、实现方式

(1)所有用户自定义的类加载器,都应该继承 ClassLoader 类

(2)自定义 ClassLoader 子类时:重写 loadClass() / 重写 findclass()

(3)loadClass() 会调用 findClass(),但不要直接修改 loadClass() 内部逻辑

(4)loadclass() 实现双亲委派模型逻辑,修改该方法会导致模型被破坏

(5)建议:只在 findClass() 重写自定义类的加载方法,根据参数指定类的名字,返回对应 Class 对象引用

(6)避免重写 loadClass() 所必须的双亲委托的重复代码

(7)编写完自定义类加载器后,便可以在程序中调用 loadClass() 实现类加载操作

4、说明

(1)父类加载器:系统类加载器

(2)JVM 中的所有类加载,都会使用 java.lang.ClassLoader.loadClass(String),包括 JDK 核心类库,自定义类加载器并重写 java.lang.ClassLoader.loadClass(String) 除外

posted @   半条咸鱼  阅读(393)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示