JVM类加载机制和双亲委派

2024-06-05 18:04

本文主要是介绍JVM类加载机制和双亲委派,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

类加载机制

java文件需要编译成字节码文件(.class文件),jvm是通过类加载机制,将.class文件加载进内存,经过验证连接->初始化直到使用该对象的过程就是类加载机制,当new对象的时候,jvm首先去常量池寻找该类的符号引用,找不到此引用,则执行类加载,简而言之就是jvm通过类加载器加载.class文件变成对象的过程就是类加载机制

三个重要的内置ClassLoader 

  • BootstrapClassLoader(启动类加载器(根)加载器) 负责加载\lib下的类库加载进内存,用来加载java的核心库
  • ExtensionClassLoader (扩展类加载器) 负责加载lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类
  • AppClassLoader (应用类加载器) 加载 Classpath 环境变量里定义的路径中的 jar 包和目录,继承自ClassLoader抽象类,所以自定义加载器也需要继承此接口,并重写findClass方法
 protected Class<?> findClass(String name) throws ClassNotFoundException {throw new ClassNotFoundException(name);}

自定义类加载器

自定义加载器继承自ClassLoader,重写findClass方法

package com.alibaba.fescar.core.protocol.test;import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;public class MyDefineClassLoader extends ClassLoader {@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {try {// 读取字节数据Path path = Paths.get("D:\\601\\acm601\\cldm_springcloud\\wsd-common\\src\\main\\java\\com\\alibaba\\fescar\\core\\protocol\\test\\TestClass.class");byte[] classData = Files.readAllBytes(path);// 将字节码内容转换为Class对象return defineClass(name, classData, 0, classData.length);} catch (IOException e) {throw new ClassNotFoundException("Class not found: " + name, e);}}
}

定义测试类,并生成.class文件

package com.alibaba.fescar.core.protocol.test;public class TestClass {public void testClassLoader(){System.out.println("test my define classloader");}
}

自定义类加载器的使用 

package com.alibaba.fescar.core.protocol.test;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;public class Test {public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {MyDefineClassLoader myDefineClassLoader = new MyDefineClassLoader();// 加载测试类生成Class对象 一定要带包名Class<?> testClass = myDefineClassLoader.loadClass("com.alibaba.fescar.core.protocol.test.TestClass");// 使用反射获得对象Object o =testClass.getDeclaredConstructor().newInstance();Method testClassLoader = testClass.getMethod("testClassLoader");// 调用方法testClassLoader.invoke(o);}
}

运行结果如下 

类加载的过程 

  • 加载阶段(将.class文件加载进内存)
  • 验证、准备、解析阶段(验证.class文件的正确性 准备(为类变量(静态变量)分配内存和初始化默认值)解析(将常量池池中的符号引用转化为直接引用))
  • 初始化阶段(执行类构造器init)

加载阶段 

 将.class字节码文件的二进制数据读入内存中,然后将这些数据翻译成类的元数据,元数据包括方法代码,变量名,方法名,访问权限与返回值,接着将元数据存入方法区,最后会在堆中创建一个Class对象

.class文件读入内存——>元数据放进方法区——>Class对象放进堆中

验证、准备、解析阶段 

验证被加载类的正确性与安全性,看class文件是否正确,是否对会对虚拟机造成安全问题等,主要去验证文件格式与符号引用等

对整个类加载机制而言,验证阶段是一个很重要但是非必需的阶段,毕竟验证需要花费一定的的时间,可以使用-Xverfity:none来关闭大部分的验证

准备 在这个阶段中,主要是为类变量(静态变量)分配内存以及初始化默认值,因为静态变量全局只有一份,是跟着类走的,因此分配内存其实是在方法区上分配。

  • 在准备阶段,虚拟机只为静态变量分配内存,实例变量要等到初始化阶段才开始分配内存
  • 为静态变量初始化默认值,是初始化对应数据类型的默认值,不是自定义的值
  • 被final修饰的静态变量,如果值比较小,则在编译后直接内嵌到字节码中。如果值比较大,也是在编译后直接放入常量池中。准备阶段结束后,final类型的静态变量已经有了用户自定义的值,而不是默认值

 解析阶段,主要是将class文件中常量池中的符号引用转化为直接引用

 符号引用:可以直接理解为是一个字符串,用这个字符串来表示一个目标

 直接引用:直接引用是一个指向目标的指针,能够通过直接引用定位到目标

Logger logger = new Logger();

我们可以通过引用变量logger直接定位到新创建出的Logger 对象实例,将符号引用转化为直接引用,就能将字符串logger转化为指向对象的指针

 初始化阶段

初始化,就是虚拟机执行类构造器<clinit>方法的过程,<clinit>方法是由编译器自动去搜集类中的所有类变量与静态语句块合并产生的。可能存在多个线程同时执行某个类的<clinit>()方法,虚拟机此时会对该方法进行加锁,保证只有一个线程能执行

在此阶段类变量与类成员变量才会被赋予用户自定义的值,只有在初始化阶段完成后,类才能被正常使用

初始化顺序 

父类的静态域->子类的静态域->父类的非静态域->子类的非静态域->父类的构造方法->子类的构造方法

静态域包括静态变量与静态代码块,静态变量和静态代码块的执行顺序由编码顺序决定

静态先于非静态,父类先于子类,构造方法在最后

双亲委派机制

java虚拟机中有多个类加载器,双亲委派机制的核心是解决一个类到底由谁加载的问题,针对的是类加载器(ClassLoader),避免了类的重复加载

当一个类加载器收到了一个类加载请求时,它自己不会先去尝试加载这个类,而是把这个请求转交给父类加载器,每一个层的类加载器都是如此,因此所有的类加载请求都应该传递到最顶层的启动类加载器中。只有当父类加载器在自己的加载范围内没有搜寻到该类时,并向子类反馈自己无法加载后,子类加载器才会尝试自己去加载

如果一个类重复出现在三个类加载器的加载位置,应该由启动类加载器(根加载器)加载,因为根据双亲委派机制,它的优先级是最高的 

打破双亲委派 

打破双亲委派机制的主要原因是为了满足一些特定的需求和场景:

  • 实现类的热部署:在某些应用场景下,需要在运行时动态加载和替换类,以实现热部署的功能。而双亲委派机制会导致类的加载只发生一次,无法实现类的热替换
  • 加载非标准的类文件:有些特殊的类文件,如动态生成的字节码、非标准的类文件格式等,无法通过标准的类加载器加载
  • 实现类加载的动态控制:有些应用需要对类的加载进行特殊的控制,例如对特定的类进行加密、解密或验证等操作

打破双亲委派的方法 

自定义类加载器 通过自定义ClassLoader的子类,重写findClass()方法,实现自定义的类加载逻辑,不委托给父类加载器,从而打破双亲委派机制

使用Java动态代理 Java动态代理机制可以在运行时生成代理类,并在代理类中实现特定的逻辑。通过使用动态代理,可以在类加载时动态生成代理类,从而打破双亲委派机制

线程上下文类加载器通过Thread类的setContextClassLoader()方法,可以设置线程的上下文类加载器,从而打破双亲委派机制

当然还有其他方法和框架打破双亲委派,比如OSGi框架、动态代理框架等

这篇关于JVM类加载机制和双亲委派的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1033776

相关文章

Java NoClassDefFoundError运行时错误分析解决

《JavaNoClassDefFoundError运行时错误分析解决》在Java开发中,NoClassDefFoundError是一种常见的运行时错误,它通常表明Java虚拟机在尝试加载一个类时未能... 目录前言一、问题分析二、报错原因三、解决思路检查类路径配置检查依赖库检查类文件调试类加载器问题四、常见

Java注解之超越Javadoc的元数据利器详解

《Java注解之超越Javadoc的元数据利器详解》本文将深入探讨Java注解的定义、类型、内置注解、自定义注解、保留策略、实际应用场景及最佳实践,无论是初学者还是资深开发者,都能通过本文了解如何利用... 目录什么是注解?注解的类型内置注编程解自定义注解注解的保留策略实际用例最佳实践总结在 Java 编程

Java 实用工具类Spring 的 AnnotationUtils详解

《Java实用工具类Spring的AnnotationUtils详解》Spring框架提供了一个强大的注解工具类org.springframework.core.annotation.Annot... 目录前言一、AnnotationUtils 的常用方法二、常见应用场景三、与 JDK 原生注解 API 的

Java controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

Java中的StringBuilder之如何高效构建字符串

《Java中的StringBuilder之如何高效构建字符串》本文将深入浅出地介绍StringBuilder的使用方法、性能优势以及相关字符串处理技术,结合代码示例帮助读者更好地理解和应用,希望对大家... 目录关键点什么是 StringBuilder?为什么需要 StringBuilder?如何使用 St

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

Maven中引入 springboot 相关依赖的方式(最新推荐)

《Maven中引入springboot相关依赖的方式(最新推荐)》:本文主要介绍Maven中引入springboot相关依赖的方式(最新推荐),本文给大家介绍的非常详细,对大家的学习或工作具有... 目录Maven中引入 springboot 相关依赖的方式1. 不使用版本管理(不推荐)2、使用版本管理(推

Java 中的 @SneakyThrows 注解使用方法(简化异常处理的利与弊)

《Java中的@SneakyThrows注解使用方法(简化异常处理的利与弊)》为了简化异常处理,Lombok提供了一个强大的注解@SneakyThrows,本文将详细介绍@SneakyThro... 目录1. @SneakyThrows 简介 1.1 什么是 Lombok?2. @SneakyThrows

在 Spring Boot 中实现异常处理最佳实践

《在SpringBoot中实现异常处理最佳实践》本文介绍如何在SpringBoot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧... 目录一、Spring Boot 异常处理的背景与核心概念1.1 为什么需要异常处理?1.2 Spring B