虚拟机类加载机制(二)

简介: 虚拟机类加载机制

初始化的时机

创建类的实例

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

调用类的静态方法

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

初始化一个类的子类

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

JDK7开始提供的动态语言支持:java.lang.invoke.MethodHandle实例的解析结果REF_getStatic、REF putStatic、REF_invokeStatic句柄对应的类没有初始化,则初始化

除了以上七种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化,即不会执行初始化阶段(不会调用 clinit() 方法和 init() 方法)


在初始化阶段,则会根据程序员通 过程序编码制定的主观计划去初始化类变量和其他资源。我们也可以从另外一种更直接的形式来表 达:初始化阶段就是执行类构造器()方法的过程。


关于()方法

**()**并不是程序员在Java代码中直接编写 的方法,它是Javac编译器的自动生成物

()方法不需要定义。 他是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的 语句合并产生的。 如果类中不存在类变量的赋值动作和静态语句块(static{}块)。 那么也就不会产生() 方法

编译器收集的顺序是由语句在源文件中出现的顺序决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问

public class Test {
  static {
        i = 0; // 给变量复制可以正常编译通过
        System.out.print(i); // 这句编译器会提示“非法向前引用”
    }
    static int i = 1;
}


()方法与类的构造函数(即在虚拟机视角中的实例构造器()方法)不同,它不需要显 式地调用父类构造器,Java虚拟机会保证在子类的()方法执行前,父类的()方法已经执行 完毕。因此在Java虚拟机中第一个被执行的()方法的类型肯定是java.lang.Object。


由于父类的()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作


()方法对于类或接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的 赋值操作,那么编译器可以不为这个类生成()方法。


·Java虚拟机必须保证一个类的()方法在多线程环境中被正确地加锁同步,如果多个线程同 时去初始化一个类,那么只会有其中一个线程去执行这个类的()方法,其他线程都需要阻塞等 待,直到活动线程执行完毕()方法。


类加载器

JVM严格来讲支持两种类型的类加载器 。分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)


自定义类加载器是开发人员根据需求自定义的一种类加载器 ,Java虚拟机将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。

1a4e353c5e2bbd0a606cf7d621f79edd_image-20230906143604730.png

public class ClassLoaderTest {
    public static void main(String[] args) {
        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
        //获取其上层:扩展类加载器
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d
        //获取其上层:获取不到引导类加载器
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println(bootstrapClassLoader);//null
        //对于用户自定义类来说:默认使用系统类加载器进行加载
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
        //String类使用引导类加载器进行加载的。---> Java的核心类库都是使用引导类加载器进行加载的。
        ClassLoader classLoader1 = String.class.getClassLoader();
        System.out.println(classLoader1);//null
    }
}

虚拟机自带的类加载器

1. 启动类加载器(引导类加载器、Bootstrap ClassLoader)

这个类加载使用C/C++语言实现的,嵌套在JVM内部

它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类

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

加载扩展类和应用程序类加载器,并作为他们的父类加载器

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

2. 扩展类加载器(Extension ClassLoader)

Java语言编写,由sun.misc.Launcher$ExtClassLoader实现

派生于ClassLoader类

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

从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载

3. 应用程序类加载器(也称为系统类加载器,AppClassLoader)

Java语言编写,由sun.misc.LaunchersAppClassLoader实现

派生于ClassLoader类

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

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

该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载

通过classLoader.getSystemclassLoader()方法可以获取到该类加载器

用户自定义类加载器

什么时候需要自定义类加载器?

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


隔离加载类(比如说我假设现在Spring框架,和RocketMQ有包名路径完全一样的类,类名也一样,这个时候类就冲突了。不过一般的主流框架和中间件都会自定义类加载器,实现不同的框架,中间价之间是隔离的)

修改类加载的方式

扩展加载源(还可以考虑从数据库中加载类,路由器等等不同的地方)

防止源码泄漏(对字节码文件进行解密,自己用的时候通过自定义类加载器来对其进行解密)

如何定义

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

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

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

案例

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 {
                //defineClass和findClass搭配使用
                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();
        }
    }
}

双亲委派模型

工作原理(过程)

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加 载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的 加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请 求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。


优点

使用双亲委派模型来组织类加载器之间的关系,一个显而易见的好处就是Java中的类随着它的类 加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一 个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类 在程序的各种类加载器环境中都能够保证是同一个类。


双亲委派模型对于保证Java程序的稳定运作极为重要


案例:

package java.lang;
public class String {
    //
    static{
        System.out.println("我是自定义的String类的静态代码块");
    }
    //错误: 在类 java.lang.String 中找不到 main 方法
    public static void main(String[] args) {
        System.out.println("hello,String");
    }
}

由于双亲委派机制一直找父类, 而这个包是以Java开头的,所以最后找到了Bootstrap ClassLoader,Bootstrap ClassLoader找到的是 JDK 自带的 String 类,在那个String类中并没有 main() 方法,所以就报了上面的错误。


沙箱机制

是为了保证对Java核心源代码的保护而出现的机制

目录
相关文章
|
缓存 前端开发 中间件
DDD 领域驱动设计落地实践系列:工程结构分层设计
前面几篇文章中,笔者给大家阐述了 DDD 领域驱动设计的三大过程,重点围绕如何通过战略设计与战术设计进行 DDD 落地实践进行了详细的讨论,但是还没有涉及到工程层面的落地。实际上所有的这些架构理论到最后都是为了使得我们代码结构更加清晰,从而开发出 bug 少、扩展性强、逻辑清楚的应用。因此本文就是为了解决 DDD 领域驱动落地实践最后一公里问题,将我们分析出来的领域模型通过与工程结构的映射实现真正的落地。
DDD 领域驱动设计落地实践系列:工程结构分层设计
|
数据采集 存储 Web App开发
Python爬虫系列8-抓取快乐8、刮刮乐、双色球等中奖数据分析规律
人在绝大多数情况下,只要稍微有那么一点的退路,有那么一点余地,就可以成为逃避的借口和理由。努力装装样子,然后给自己一个台阶下,安慰自己说,“你看我已经很努力了,还是不行,那就算了吧”。 老话说得好:只有主动追求的东西才能到手;只要你想做,全世界都会帮你;只要你不想做,一只蚊子都能拦住你。虽说未来可期,但如果你连相信自己的勇气都没有,还有什么资格得到更好的呢。对吧!
2920 0
Python爬虫系列8-抓取快乐8、刮刮乐、双色球等中奖数据分析规律
|
SQL XML Java
mybatis 调用修改SQL时 出现了一个问题 没有修改成功也没有报错
mybatis 调用修改SQL时 出现了一个问题 没有修改成功也没有报错
221 0
|
消息中间件 人工智能 供应链
go-zero 微服务实战系列(二、服务拆分)
go-zero 微服务实战系列(二、服务拆分)
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
358 2
|
人工智能 弹性计算 监控
触手可及,函数计算玩转 AI 大模型解决方案
阿里云推出的“触手可及,函数计算玩转 AI 大模型”解决方案,利用无服务器架构,实现AI大模型的高效部署和弹性伸缩。本文从实践原理、部署体验、优势展现及应用场景等方面全面评估该方案,指出其在快速部署、成本优化和运维简化方面的显著优势,同时也提出在性能监控、资源管理和安全性等方面的改进建议。
328 5
|
JavaScript 前端开发 持续交付
构建现代Web应用:Vue.js与Node.js的完美结合
【10月更文挑战第22天】随着互联网技术的快速发展,Web应用已经成为了人们日常生活和工作的重要组成部分。前端技术和后端技术的不断创新,为Web应用的构建提供了更多可能。在本篇文章中,我们将探讨Vue.js和Node.js这两大热门技术如何完美结合,构建现代Web应用。
701 4
|
JSON 关系型数据库 API
Python 使用 FastAPI 和 PostgreSQL 构建简单 API
最近一年公司也在卷 LLM 的应用项目,所以我们也从 goper => Pythoner。 这一年使用最多的就是 Python 的 FastAPI 框架。下面一个简易项目让你快速玩转 Python API Web。 API代表应用程序编程接口,是软件开发中最重要的概念之一。它允许程序通过发送和接收数据与其他服务进行交互。API Web 通信最广泛使用的标准之一是 REST,它依赖于JSON 格式或键值对,类似于 Python 的字典。 如果想用 Python 构建一个,那么可以从几个框架中选择。Flask -RESTful、Django Rest Framework 和 FastAPI 是最受
|
前端开发 SEO
WordPress简约响应式个人博客Kratos主题
Kratos主题基于Bootstrap和Font Awesome的WordPress一个干净,简单且响应迅速的博客主题,Vtrois创建和维护,主题设计简约友好,并且支持响应式,自适应访问,简seo单大方的主页构造,使得博客能在臃肿杂乱的环境中脱颖而出,Kratos内置主题设置,可设置seo关键字及站点描述页面伪静态,自定义的顶部样式(背景图 ),支持图片轮播、侧边栏文章聚合、广告栏、点赞和社交化分享等功能,以及漂亮的博客订阅功能组件,让你的个人博客更加与众不同。
225 2
|
算法 数据安全/隐私保护 C++
超级好用的C++实用库之Des加解密
超级好用的C++实用库之Des加解密
391 0