JVM—类加载子系统

JVM—类加载子系统

JVM的类加载是通过ClassLoader及其子类来完成的。

有哪些类加载器

类加载器如下:

类加载器

  • 启动类加载器(BootStrap ClassLoader):负责加载JAVA_HOME\lib目录或通过-Xbootclasspath参数指定路径中的且被虚拟机认可(rt.jar)的类库;
  • 扩展类加载器(Extension ClassLoader):负责加载JAVA_HOME\lib\ext目录或通过java.ext.dirs系统变量指定路径中的类库;
  • 应用程序类加载器(Application ClassLoader):负责加载用户路径classpath上的类库;
  • 自定义类加载器(Custom ClassLoader):加载应用之外的类文件;

类加载器执行顺序

类加载器执行顺序如下图:

类加载器执行顺序

  1. 自底向上检查类是否已经加载:

    加载过程中会先检查类是否已被加载,从自定义加载器到BootStrap逐层检查,只要某个类加载器已加载某个类,就视为此类已加载,可以保证此类使得所有ClassLoader只加载一次;

  2. 自顶向下尝试加载类:由上层来逐层尝试加载此类。

类加载时机与过程

类加载的四个时机:

  1. 遇到new、getStatic、putStatic、invokeStatic四条指令;

    比如有如下类:

    public class MyTest {public static int hello;public static void testMethod(){}
    }
    

    当使用如下三种代码时,此类会被加载:

    //第一种
    MyTest.age;
    //第二种
    MyTest.testMethod();
    //第一种
    new MyTest();
    
  2. 使用java.lang.reflect包方法对类进行反射调用;

    比如:

    Class clazz = Class.forName("com.sjdwz.MyTest");
    
  3. 初始化一个类,发现其父类还没初始化,要先初始化其父类;

  4. 当虚拟机启动时,用户需要指定一个主类main,需要先将主类加载。

一个类的一生

一个类的一生如下:

一个类的一生

类加载做了什么

主要做了三件事:

  1. 根据类全限定名称,定位到class文件,以二进制字节流形式加载到内存中;
  2. 把字节流静态数据加载到方法区(永久代,元空间);
  3. 基于字节流静态数据,创建字节码Class对象。

类加载途径

类加载途径如下图:

类加载途径

自定义类加载器

我们可以自定义类加载器,来加载D:\sjdwzTest目录下的lib文件夹下的类。

步骤如下:

  1. 新建一个类MyTest.java

    package com.sjdwz.myclassloader;
    public class MyTest {public void sayHello(){System.out.println("hello world!");}
    }
    
  2. 使用javac MyTest.java命令,将生成的MyTest.class文件放到D:\sjdwzTest\lib\com\sjdwz\myclassloader文件夹下

    注意:包路径不能错。

    编译的位置

  3. 自定义类加载器,继承ClassLoader,重写findClass()方法 ,调用defineClass()方法:

    /*** @Description 自定义类加载器* @Created by 随机的未知*/
    public class SjdwzClassLoader extends ClassLoader {private String classpath;public SjdwzClassLoader(String classpath) {this.classpath = classpath;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {try {//输入流,通过类的全限定名称加载文件到字节数组byte[] classDate = getData(name);if (classDate != null) {//defineClass方法将字节数组数据 转为 字节码对象return defineClass(name, classDate, 0, classDate.length);}} catch (IOException e) {e.printStackTrace();}return super.findClass(name);}/*** 加载类的字节码数据* @param className* @return* @throws IOException*/private byte[] getData(String className) throws IOException{String path = classpath + File.separatorChar +className.replace('.', File.separatorChar) + ".class";try (InputStream in = new FileInputStream(path);ByteArrayOutputStream out = new ByteArrayOutputStream()) {byte[] buffer = new byte[2048];int len = 0;while ((len = in.read(buffer)) != -1) {out.write(buffer, 0, len);}return out.toByteArray();} catch (FileNotFoundException e) {e.printStackTrace();}return null;}
    }
    
  4. 测试类如下:

    public class SjdwzClassLoaderTest {public static void main(String[] args) throws Exception {//自定义类加载器的记载路径SjdwzClassLoader sjdwzClassLoader = new SjdwzClassLoader("D:\\sjdwzTest\\lib");Class<?> testClazz = sjdwzClassLoader.loadClass("com.sjdwz.myclassloader.MyTest");if(testClazz != null){Object testObj = testClazz.newInstance();Method sayHelloMethod = testClazz.getMethod("sayHello", null);sayHelloMethod.invoke(testObj,null);System.out.println(testClazz.getClassLoader().toString());}}
    }
    

    输出如下:

    输出

双亲委派与打破双亲委派

什么是双亲委派

当一个类加载器收到类加载任务,会先交给其父类加载器去完成。 因此,最终加载任务都会传递到顶层的启动类加载器,只有当父类加载器无法完成加载任务时,子类才会尝试加载任务。

为什么需要双亲委派

主要考虑安全因素,双亲委派可以避免重复加载核心的类,当父类加载器已经加载了该类时,子类加载器不会再去加载。

为什么还需要破坏双亲委派

在实际应用中,双亲委派解决了Java基础类统一加载的问题,但是存在着缺陷。JDK中的基础类的方法作为典型的API被用户类用户调用,但是也存在API调用用户代码的情况,比如:SPI代码。这种情况就需要打破双亲委派模式。

比如:数据库驱动DriverManager。以Driver接口为例,Driver接口定义在JDK中,其实现由各个数据库的服务商来提供,由系统类加载器加载。这个时候就需要启动类加载器来委托子类来加载Driver实现,这就破坏了双亲委派。

如何破坏双亲委派

  1. 重写ClassLoader的loadClass方法;

    在JDK1.2之后,新加了一个findClass方法让用户重写;

  2. SPI,父类委托子类加载器加载Class;

  3. 热部署和不停机更新用到的OSGI技术。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/585667.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入探究Shiro反序列化漏洞

Shiro反序列化漏洞 什么是shiro反序列化漏洞环境搭建漏洞判断rememberMe解密流程代码分析第一层解密第二层解密2.1层解密2.2层解密 exp 什么是shiro反序列化漏洞 Shiro是Apache的一个强大且易用的Java安全框架,用于执行身份验证、授权、密码和会话管理。使用 Shiro 易于理解的…

Vue项目登录页实现获取短信验证码的功能

之前我们写过不需要调后端接口就获取验证码的方法,具体看《无需后端接口,用原生js轻松实现验证码》这个文章。现在我们管理后台有个需求,就是登录页面需要获取验证码,用户可以输入验证码后进行登录。效果如下,当我点击获取验证码后能获取短信验证码: 这里在用户点击获取…

手搓Docker-Image-Creator(DIC)工具(03):实现alpine+jre的镜像

此篇博客将介绍如何使用 Docker 创建一个alpine3.10-jre1.8.0_401 的 Docker 镜像&#xff0c;并使用 Docker 运行起来。将用到 Dockerfile 的 COPY 命令、RUN 命令、ENV 命令&#xff0c;最终实现基于单一应用的 Dockerfile 构建镜像和运行。 紧急修改&#xff1a;代码我是在m…

【机器学习300问】60、图像分类任务中,训练数据不足会带来什么问题?如何缓解图像数据不足带来的问题?

在机器学习中&#xff0c;绝大部分模型都需要大量的数据进行训练和学习&#xff08;包括有监督学习和无监督学习&#xff09;&#xff0c;然而在实际应用中经常会遇到训练数据不足的问题。就比如图像分类这样的计算机视觉任务&#xff0c;确实依赖于大规模且多样化的训练数据以…

【前缀和差分】详细使用方法

前缀和 前缀和的作用&#xff1a; 快速求出元素组中某段区间的和 为什么下标要从1 开始&#xff1a;为了方便后面的计算&#xff0c;避免下标转换&#xff0c;设为零&#xff0c;不影响结果 定义两个数组&#xff0c;第一个为原始数组(a[])&#xff0c;第二个为前缀和数组(s[…

如何编辑PDF文件?分享一个好用的PDF编辑器

如何编辑PDF文件呢?大家在日常中经常会使用PDF文件,难免在使用的过程中会发现文件出现的错误,更正错误地方最简单有效的方法就是直接在PDF文件上进行编辑,但大家都知道PDF文件不易改动,该如何编辑呢? 在这里推荐给大家一个好用的PDF编辑器 PDFPatcher是一款开源免费的多…

基于arkTS开发鸿蒙app应用案例——通讯录案例

1.项目所用技术栈 arkTS node.js express mongoDB 2.效果图 3.源码 Index.ets&#xff08;登录页&#xff09; 登陆时让前端访问数据库中已经存好的账号密码&#xff0c;如果可以查询到数据库中的数据&#xff0c;则账号密码正确&#xff0c;登录成功&#xff0c;否则登录…

美食分享(源码+文档)

美食分享系统&#xff08;小程序、ios、安卓都可部署&#xff09; 文件包含内容程序简要说明含有功能项目截图客户端主页注册界面美食详细及教程界面搜索菜谱分类美食制作上传我的资料登录界面 管理端登录界面关键词管理用户管理分类管理历史管理菜谱管理 文件包含内容 1、搭建…

LINUX笔记温习

目录 DAY1 DAY2 day3&#xff1a; day4 day5 day6 day7 day8 day9 day10 day11 day12 day13 day14 day15 20day DAY1 1、多层级文件夹创建要带-p&#xff1b; 2、创建多文件&#xff0c;要先到该目录下才能创建(第一个目录必须存在才能有效建立)&#xff1b; D…

【Leetcode每日一题】模拟 - 替换所有的问号(难度⭐⭐)(48)

1. 题目解析 题目链接&#xff1a;6. Z 字形变换 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 想要画出N字形&#xff0c;我们首先要观察并找出其中的规律。假设我们用“row”来代表行数&#xff0c;当row等于4时&…

C# 排序的多种实现方式(经典)

一、 对数组进行排序 最常见的排序是对一个数组排序&#xff0c;比如&#xff1a; int[] aArray new int[8] { 18, 17, 21, 23, 11, 31, 27, 38 }; 1、利用冒泡排序进行排序&#xff1a; &#xff08;即每个值都和它后面的数值比较&#xff0c;每次拿出最小值&#xff09; s…

手机照片误删了怎么恢复?如何从 iPhone 恢复已删除的照片

照片是最容易从 iPhone 中意外删除的项目之一。好消息是它们也是最容易恢复的数据类型之一。至少&#xff0c;如果您一开始没有特意删除它们的话&#xff0c;它们是这样的。 如果你忘记了它们&#xff0c;情况就会变得更加困难。不过&#xff0c;您仍然有其他选择&#xff0c;…