<JVM上篇:内存与垃圾回收篇>02-类加载子系统(下)

简介: <JVM上篇:内存与垃圾回收篇>02-类加载子系统

2.3.2. 用户自定义类加载器


在 Java 的日常应用程序开发中,类的加载几乎是由上述 3 种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式。 为什么要自定义类加载器?


隔离加载类(比如项目中的中间件和使用到的框架,有可能会有相同的类路径也一样,可能出现冲突问题,需要类加载器进行冲裁.)

修改类加载的方式(修改处理Bootstrap加载器外其他类加载器的使用方式,需要时进行加载.)

扩展加载源(比如:数据库,机顶盒…)

防止源码泄漏(利用自定义加载类对加密的字节码文件 加载并进行解密)

用户自定义类加载器实现步骤:


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

在 JDK1.2 之前,在自定义类加载器时,总会去继承 ClassLoader 类并重写 loadClass() 方法,从而实现自定义的类加载类,但是在 JDK1.2 之后已不再建议用户去覆盖 loadclass() 方法,而是建议把自定义的类加载逻辑写在 findClass()方法中

在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承 URLClassLoader 类,这样就可以避免自己去编写 findClass() 方法及其获取字节码流的方式,使自定义类加载器编写更加简洁。


代码演示:

/**
 * 自定义用户类加载器
 * @author shkstart
 * @create 2019 下午 12:21
 */
public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] result = getClassFromCustomPath(name);
            if(result == null){
                throw new FileNotFoundException();
            }else{
                return defineClass(name,result,0,result.length);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        throw new ClassNotFoundException(name);
    }
  //获取字节码文件的字节流
    private byte[] getClassFromCustomPath(String name){
        //从自定义路径中加载指定类:细节略
        //如果指定路径的字节码文件进行了加密,则需要在此方法中进行解密操作。
        return null;
    }
    public static void main(String[] args) {
        CustomClassLoader customClassLoader = new CustomClassLoader();
        try {
            Class<?> clazz = Class.forName("One",true,customClassLoader);
            Object obj = clazz.newInstance();
            System.out.println(obj.getClass().getClassLoader());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.4. ClassLoader 的使用说明

ClassLoader 类是一个抽象类,其后所有的类加载器都继承自 ClassLoader(不包括启动类加载器)

252bb992cb6107568ed2de2bfbee8bfe.png

常见方法如下:

1090b99fa4191151246cc6c778b1e826.png

sun.misc.Launcher 它是一个 java 虚拟机的入口应用

a22114b608dffe484041b591d486a7fd.png


获取 ClassLoader 的途径

  • 方式一:获取当前 ClassLoader
clazz.getClassLoader()


方式二:获取当前线程上下文的 ClassLoader

Thread.currentThread().getContextClassLoader()


方式三:获取系统的 ClassLoader

ClassLoader.getSystemClassLoader()


方式四:获取调用者的 ClassLoader

DriverManager.getCallerClassLoader()


代码演示:

/**
 * @author shkstart
 * @create 2020 上午 10:59
 */
public class ClassLoaderTest2 {
    public static void main(String[] args) {
        try {
            //1.
            ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader();
            System.out.println(classLoader);//null
            //2.
            ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader();
            System.out.println(classLoader1);//sun.misc.Launcher$AppClassLoader@18b4aac2
            //3.
            ClassLoader classLoader2 = ClassLoader.getSystemClassLoader().getParent();
            System.out.println(classLoader2);//sun.misc.Launcher$ExtClassLoader@1b6d3586
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}


2.5. 双亲委派机制


Java 虚拟机对 class 文件采用的是**按需加载的方式,也就是说当需要使用该类时才会将它的 class 文件加载到内存生成 class 对象。而且加载某个类的 class 文件时,Java 虚拟机采用的是双亲委派模式**,即把请求交由父类处理,它是一种任务委派模式。


工作原理


1)如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;

2)如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;

3)如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。


6e113e1da9f9c305afd3c0fe517af9c9.png

代码演示:

项目下建立java.lang包,里面创建String类

public class String {
    static{
        System.out.println("我是自定义的String类的静态代码块");
    }
}

在java1包下创建StringTest类

public class StringTest {
    public static void main(String[] args) {
        java.lang.String str = new java.lang.String();
        System.out.println("hello,atguigu.com");
        StringTest test = new StringTest();
        System.out.println(test.getClass().getClassLoader());
    }
}

执行StringTest方法


a1ae499b2d00aabbda304d609712ef64.png


在String类中增加Main方法,执行观察结果:


19c66a19fd8d8bbd0e3d414a005af58b.png


举例


当我们加载 jdbc.jar 用于实现数据库连接的时候,首先我们需要知道的是 jdbc.jar 是基于 SPI 接口进行实现的,所以在加载的时候,会进行双亲委派,最终从根加载器(启动类)中加载 SPI 核心类.然后在加载 SPI 接口类,接着在进行反向委派,通过线程上下文类加载器进行实现类 jdbc.jar 的加载。


**补充:**spi是引导类加载器加载,第三方的类库实现是系统类加载器加载。当使用spi时,是无法访问系统类加载器加载,所以引入了线程上下文类加载器,由线程上下文类加载器来加载第三方的库,这样就能使用到第三方的库了


62a9ee37c13333bba5ee92330af0cbc5.png


优势


避免类的重复加载

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

自定义类:java.lang.String

自定义类:java.lang.ShkStart(报错:阻止创建 java.lang 开头的类)


a64ff4d8f6717dba26674d0675198d1c.png


沙箱安全机制


自定义 String 类,但是在加载自定义 String 类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载 jdk 自带的文件(rt.jar 包中 java\lang\String.class),报错信息说没有 main 方法,就是因为加载的是 rt.jar 包中的 string 类。这样可以保证对 java 核心源代码的保护,这就是沙箱安全机制。


2.6. 其他


如何判断两个 class 对象是否相同


在 JVM 中表示两个 class 对象是否为同一个类存在两个必要条件:


类的完整类名必须一致,包括包名。

加载这个类的 ClassLoader(指 ClassLoader 实例对象)必须相同。

换句话说,在 JVM 中,即使这两个类对象(class 对象)来源同一个 Class 文件,被同一个虚拟机所加载,但只要加载它们的 ClassLoader 实例对象不同,那么这两个类对象也是不相等的。


对类加载器的引用


JVM 必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么 JVM 会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。当解析一个类型到另一个类型的引用的时候,JVM 需要保证这两个类型的类加载器是相同的。


类的主动使用和被动使用


Java 程序对类的使用方式分为:主动使用和被动使用。


主动使用,又分为七种情况:


创建类的实例


访问某个类或接口的静态变量,或者对该静态变量赋值


调用类的静态方法


反射(比如:Class.forName(“com.atguigu.Test”))


初始化一个类的子类


Java 虚拟机启动时被标明为启动类的类


JDK 7 开始提供的动态语言支持:


java.lang.invoke.MethodHandle 实例的解析结果


REF_getStatic、REF_putStatic、REF_invokeStatic 句柄对应的类没有初始化,则初始化


除了以上七种情况,其他使用 Java 类的方式都被看作是对类的被动使用,都不会导致类的初始化。


相关文章
|
22小时前
|
存储 缓存 算法
深入剖析Java中JVM的内存模型!!!
对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像C/C++程序开发程序员这样为内一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。
4 1
|
2天前
|
存储 算法 Java
Java虚拟机内存管理机制
【2月更文挑战第7天】本文主要介绍了Java虚拟机内存管理机制的基本原理和实现方式。Java虚拟机的内存管理机制是Java程序运行的重要组成部分,对程序性能和稳定性有着直接的影响。文章首先从Java虚拟机内存模型入手,介绍了Java虚拟机中堆内存、方法区、栈、PC寄存器等内存区域的功能特点和使用方式;然后详细阐述了Java虚拟机内存管理机制的垃圾回收算法和回收器的分类、优化和实现过程;最后介绍了一些常见的内存问题和优化技巧,以及如何通过代码调优和合理使用内存配置参数来提高程序的性能和稳定性。
|
4天前
|
监控 Java 编译器
优化Go语言程序中的内存使用与垃圾回收性能
【2月更文挑战第5天】本文旨在探讨如何优化Go语言程序中的内存使用和垃圾回收性能。我们将深入了解内存分配策略、垃圾回收机制,并提供一系列实用的优化技巧和建议,帮助开发者更有效地管理内存,减少垃圾回收的开销,从而提升Go程序的性能。
|
6天前
|
存储 监控 Java
JVM内存泄漏怎么办?有啥影响?
JVM内存泄漏怎么办?有啥影响?
|
6天前
|
存储 监控 Java
JVM内存泄漏的分析与解决方案
JVM内存泄漏的分析与解决方案
|
6天前
|
监控 算法 Java
百度搜索:蓝易云【JAVA系列之JVM内存调优】
以上策略需要根据具体的应用场景和需求来进行调优。调优时建议先进行性能测试和分析,再根据测试结果来选择合适的参数配置,以获得最佳的性能和稳定性。 买CN2云服务器,免备案服务器,高防服务器,就选蓝易云。百度搜索:蓝易云
143 3
|
28天前
|
安全 Java 程序员
深入理解jvm - 类加载过程
深入理解jvm - 类加载过程
35 0
|
1月前
|
存储 算法 Java
理解JVM的内存模型和垃圾回收算法
理解JVM的内存模型和垃圾回收算法
27 2
|
1月前
|
缓存 监控 算法
jvm性能调优实战 - 39一次大促导致的内存泄漏和Full GC优化
jvm性能调优实战 - 39一次大促导致的内存泄漏和Full GC优化
47 0
|
1月前
|
存储 Java 数据库
jvm性能调优 - 06线上应用部署JVM实战_堆内存预估与设置
jvm性能调优 - 06线上应用部署JVM实战_堆内存预估与设置
41 0