【底层原理之旅—JVM类加载机制的重塑分析】|Java 刷题打卡

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 【底层原理之旅—JVM类加载机制的重塑分析】|Java 刷题打卡

题目


JVM类加载机制的重塑分析





知识点


📕 编译阶段


编译器将源码文件编译成class文件

class文件是.java文件的二进制字节流表示,在class文件中,包含了对应的类或接口的定义信息等常量池数据。



  • 内部存放的数据有:元数据常量池,访问标志,当前类索引、父类索引和接口索引的集合,字段表集合(类中声明的变量),方法表集合等,他们共同描述了一个类的信息
  • 每个class文件一定对应一个类,但反过来未必成立例如,动态生成的类信息,直接生成二进制字节流送入类加载器完成类加载
  • 因此广义上来讲,class并不一定要是一个class文件,也可以仅仅就是一串二进制字节流


image.png




类加载阶段


  • class文件本质上是对某个类的静态描述,他需要被加载到内存,转化成运行时数据,才能被虚拟机执行,这个加载到内存的过程就是类加载过程


  • 类加载完成之后,在方法区内存放了类的类型信息、常量、静态变量(jdk8之后随类对象存储在堆内)等信息,在堆中存放了与class文件对应的Class对象。通过Class对象,可以获取到类的字段、方法、构造器等信息,它是反射的基础。


image.png


类加载的作用


类加载在程序执行的过程中起到了承上启下的作用将静态的二进制字节流数据转化为了运行时数据供执行引擎去操作数据

如图,加载-验证-准备-解析-初始化这五个阶段都属于类加载过程。

image.png



类加载的时机


虚拟机规范并没有严格规定什么时候开始类加载。但是,规定了6种必须对类进行初始化的情况,它们被称为主动引用

!!!由于初始化类对象需要在加载、验证、准备之后进行,因此这三步必然要在这之前完成。这里前4种是非常常见的,需要深刻掌握




遇到new, getstatic, putstatic, invokestatic这四条字节码指令的时候,如果类型还没有被初始化,则需要初始化



  • new :实例化对象(对象实例调用表达式所创建的对象)
  • getstatic/putstatic: 读取/设置类的静态字段(被final修饰的静态常量除外)
  • invokestatic: 调用类的静态方法



📕 其他的初始化条件

  • unsafe方法进行调用对象操作
  • clone方法进行操作,进行申请
  • 通过文件IO的ObjectInputStream/ObjectOutputStream进行处理构造
  • 通过反射对类进行调用的时候,需要确保类已经被初始化过。也好理解,反射的核心是Class对象
  • 当前类被初始化时,要先确保其父类已被初始化
  • 虚拟机启动时,要执行的主类(包含main方法的那个类)要先被初始化
  • 接口中定义了默认方法(被default修饰,可以有方法体的方法,比较少见),当该接口的实现类初始化时,该接口需要先被初始化



除了以上的情况之外,所有其他对类的引用都不会触发类的初始化,他们被称为被动引用。




📕类加载的过程


加载
  • 加载是类加载过程的第一步,在类加载器的控制下,将二进制字节流转化为运行时数据。加载阶段需要完成3件事。
  1. 根据类的全限定名获取对应的二进制字节流/定义对应的二进制数据流。
  2. 将二进制字节流转化成方法区的运行时数据结构。
  3. 在堆中创建代表这个类的java.lang.Class对象 ,作为方法区内数据的访问入口



  • 这里二进制字节流的获取,有多种方式,源文件也可以有多种形式。比较常见的形式有
  • 从压缩包中获取,如jar包,war包等。
  • 在程序运行时,动态计算产生。应用场景:动态代理。
  • 最常见的,编译.java文件生成.class文件




验证

验证的作用是确保Class文件内的信息符合虚拟机规范的要求,保证程序运行过程中的安全。



准备

为类变量(即静态变量)分配内存,并设初始值。(0, null, false ...)。为常量直接进行赋值操作(定制化)



解析

将运行时常量池中符号引用替换成直接引用。



举个例子,在解析完成之前,被引用的目标还没有被加载到内存中,只能先用一个符号来表示,如"java.lang.Object"。



  • 解析的作用就是,在引用的对象被加载到内存中以后,将引用替换成指向该对象的指针或句柄需要被解析的引用有:类或接口的解析、字段解析、方法解析、接口方法解析。
  • 解析的发生时间并没有严格规定,它并非一定发生在准备和初始化之间动态链接或者动态加载、动态分派等功能实现的场景会延迟到运行阶段)。




初始化


  • 在初始化之前,加载-验证-准备这3步必然是完成了,部分的解析工作也可能完成了。
  • 准备阶段:对类对象中的类变量都是系统默认的初始值(常量会直接赋值,不会等到运行阶段)。
  • 初始化阶段:对类变量赋予我们在代码中指定的值





在初始化阶段,需要执行类构造器(与实例对象的构造器区分开来)。类构造器并非我们直接编写的方法,而是编译器收集类变量的赋值语句和static代码块的产物。

初始化阶段就是对静态变量赋值和执行静态代码块的过程(父类会在前,子类会在后)

实例化阶段就是执行实例构造器和实例代码块(构造代码块)的过程





需要注意的点:


  • 从概念上讲,应当在方法区内为静态变量赋初值(常量会执行定制化赋值,不是单纯的默认值),但实际上jdk8以后,静态变量随着类对象一起存放在堆内存中
  • 准备阶段并不会为非静态变量(即实例变量)分配内存,实例变量会在对象实例化的时候,分配内存并赋初始






拓展延伸


类加载器


把类加载阶段的 "通过一个类的全限定名来获取描述此类的二进制字节流" 这个动作交给虚拟机之外的类加载器来完成。这样的好处在于,我们可以自行实现类加载器来加载其他格式的类,只要是二进制字节流就行,这就大大增强了加载器灵活性。




系统自带的类加载器分为三种:

启动类加载器。其它的类加载器都是 java.lang.ClassLoader 的子类,启动类加载器是由 C++ 实现的,没有对应的 Java 对象,因此在 Java 中只能用 null 代替。启动类加载器加载最为基础,最为重要的类,如 JRE 的 lib 目录下 jar 包中的类;可以采用指定-Xbootclasspath:指定相关的路径。


扩展类加载器的父类是启动类加载器,它负责加载相对次要,但又通用的类,如 JRE 的 lib/ext 目录下jar包中的类


扩展类加载器。Java核心类库提供,负责加载java的扩展库(加载 JAVA_HOME/jre/ext/*.jar 中的类),开发者可以直接使用扩展类加载器。

应用程序类加载器。Java核心类库提供。应用类加载器的父类加载器则是扩展类加载器,它负责加载应用程序路径下的类。





相关文章
|
16天前
|
存储 Java 关系型数据库
高效连接之道:Java连接池原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。频繁创建和关闭连接会消耗大量资源,导致性能瓶颈。为此,Java连接池技术通过复用连接,实现高效、稳定的数据库连接管理。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接池的基本操作、配置和使用方法,以及在电商应用中的具体应用示例。
33 5
|
6天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
6天前
|
Java
Java之CountDownLatch原理浅析
本文介绍了Java并发工具类`CountDownLatch`的使用方法、原理及其与`Thread.join()`的区别。`CountDownLatch`通过构造函数接收一个整数参数作为计数器,调用`countDown`方法减少计数,`await`方法会阻塞当前线程,直到计数为零。文章还详细解析了其内部机制,包括初始化、`countDown`和`await`方法的工作原理,并给出了一个游戏加载场景的示例代码。
Java之CountDownLatch原理浅析
|
8天前
|
Java 索引 容器
Java ArrayList扩容的原理
Java 的 `ArrayList` 是基于数组实现的动态集合。初始时,`ArrayList` 底层创建一个空数组 `elementData`,并设置 `size` 为 0。当首次添加元素时,会调用 `grow` 方法将数组扩容至默认容量 10。之后每次添加元素时,如果当前数组已满,则会再次调用 `grow` 方法进行扩容。扩容规则为:首次扩容至 10,后续扩容至原数组长度的 1.5 倍或根据实际需求扩容。例如,当需要一次性添加 100 个元素时,会直接扩容至 110 而不是 15。
Java ArrayList扩容的原理
|
14天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
31 2
|
14天前
|
算法 Java 数据库连接
Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性
本文详细介绍了Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性。连接池通过复用数据库连接,显著提升了应用的性能和稳定性。文章还展示了使用HikariCP连接池的示例代码,帮助读者更好地理解和应用这一技术。
29 1
|
15天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
10 2
|
6天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
2天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
15 9
|
5天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####