Java中什么是类加载?类加载的过程?
类加载指的是把类加载到 JVM 中。把二进制流存储到内存中,之后经过一番解析、处理转化成可用的 class 类
二进制流可以来源于 class 文件,或通过字节码工具生成的字节码或来自于网络。只要符合格式的二进制流,JVM 来者不拒。
虚拟机遇到⼀条 new 指令时,⾸先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引⽤,并且检查这个符号引⽤代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执⾏相应的类加载过程。类加载过程包括了加载、连接、初始化三个阶段,其中连接还可以分为验证、准备、解析

加载
将二进制流读入内存中,生成一个 Class 对象
在加载阶段,虚拟机需要完成以下三件事情:
通过一个类的全限定名来获取其定义的二进制字节流。
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

这个阶段既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。
验证
确保Class文件的字节流中包含的信息符合JVM规范,保证在运行后不会危害虚拟机自身的安全。即安全性检查,主要包括四种验证:
文件格式验证: 验证字节流是否符合Class文件格式的规范;例如: 是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
元数据验证:: 对字节码描述的信息进行语义分析(注意: 对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如: 这个类是否有父类,除了java.lang.Object之外。
字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
符号引用验证:确保解析动作能正确执行
验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
准备
准备阶段是正式为static 变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。
static变量在分配空间和赋值是在两个阶段完成的。分配空间在准备阶段完成,赋值在初始化阶段完成。也就是说这里给类变量设置初始值,设置的是数据类型默认的零值(如0、0L、null、false等)
如果 static 变量是 final 的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成
如果 static 变量是 final 的,但属于引用类型,那么赋值会在初始化阶段完成
解析
将常量池内的符号引用替换为直接引用的过程。符号引用用于描述目标,直接引用直接指向目标的地址。
未解析时,常量池中的看到的对象仅是符号,未真正的存在于内存中
解析以后,会将常量池中的符号引用解析为直接引用
初始化
初始化阶段会执行cinit方法来为 类变量static变量 赋上定义的值并执行类中的静态代码块;这里的赋值才是代码里面的赋值,准备阶段只是设置初始值占个坑。
在Java中对类变量进行初始值设定有两种方式:
声明类变量是指定初始值
使用静态代码块为类变量指定初始值
何时进行类加载?
定义了main的类,启动main方法时该类会被加载
创建类的实例,即new对象的时候
访问类的静态方法
访问类的静态变量
反射 Class.forName()
JVM初始化步骤?
假如这个类还没有被加载和连接,则程序先加载并连接该类
假如该类的直接父类还没有被初始化,则先初始化其直接父类
假如类中有初始化语句,则系统依次执行这些初始化语句
初始化发生的时机?
概括得说,类初始化是【懒惰的】,只有当对类的主动使用的时候才会导致类的初始化
main 方法所在的类,总会被首先初始化
首次访问这个类的静态变量或静态方法时
子类初始化,如果父类还没初始化,会引发父类初始化
子类访问父类的静态变量,只会触发父类的初始化
Class.forName new 会导致初始化
不会导致类初始化的情况?
访问类的 static final 静态常量(基本类型和字符串)不会触发初始化
类对象.class 不会触发初始化
创建该类的数组不会触发初始化
类加载器的 loadClass 方法
Class.forName 的参数 2 为 false 时
cinit方法如果执行失败了怎么办,这个类还能用吗?
在Java类加载的过程中,cinit 方法实际上指的是类的静态初始化方法,也就是类的静态代码块或者静态变量的初始化代码。如果类的静态初始化方法执行失败,通常会导致类的初始化失败,这意味着这个类不能被正常使用。会抛出异常,如 ExceptionInInitializerError
在Java中,类的静态初始化方法只会执行一次,无论类被加载多少次,静态初始化方法只会在首次加载类的时候执行。因此,cinit 方法不会多次执行。一旦类的静态初始化方法执行过,后续对同一个类的加载都不会再次触发静态初始化方法的执行。这种机制确保了类的静态初始化只会在需要的时候执行一次,避免了不必要的开销和重复操作。
分配内存
在类加载后,接下来虚拟机将为新⽣对象分配内存。
分配在哪?
主要就是根据JVM的分配机制:对象优先分配Eden
- 先TLAB分配
- 再通过CAS在Eden区分配
- 大对象直接分配到老年代
TLAB:线程本地分配缓冲区,为每⼀个线程预先在 Eden 区分配⼀块⼉私有的缓存区域,JVM 在给线程中的对象分配内存时,⾸先在 TLAB 分配,当对象⼤于 TLAB 中的剩余内存或 TLAB 的内存已⽤尽时(或者未开启TLAB),再采⽤上述的 CAS 进⾏内存分配。默认情况TLAB仅占每个Eden区域的1%。它的主要目的是在多线程并发环境下需要进行内存分配的时候,减少线程之间对于内存分配区域的竞争,加速内存分配的速度。
为什么要CAS分配内存?
多个并发执行的线程需要创建对象、申请分配内存的时候,有可能在 Java 堆的同一个位置申请,这时就需要对拟分配的内存区域进行加锁或者采用 CAS 等操作,保证这个区域只能分配给一个线程。
JVM对象分配内存如何保证线程安全
在JVM中,为对象分配内存的过程需要确保线程安全,因为在多线程环境下,多个线程可能会同时尝试创建对象。为了保证内存分配的线程安全性,JVM采用了以下几种机制和技术:
TLAB(Thread Local Allocation Buffer):
- 当一个线程需要分配对象时,首先会尝试在TLAB中进行分配。如果TLAB有足够的空间,分配过程就是线程安全的,因为没有其他线程访问这个内存块。
- 不足:当TLAB空间不足时,线程需要请求一个新的TLAB或者直接从共享堆中分配,这个过程需要一定的同步机制。
CAS(Compare-And-Swap)机制: 当TLAB耗尽或在涉及到跨线程的堆内存分配时,CAS有效避免了竞争条件。
分代收集: 虽然不是直接用于线程安全,但分代收集(年轻代、老年代、永久代/元空间)使得内存管理更高效,减少了直接竞争的机会。
结合:TLAB一般对年轻代的内存分配进行优化,更加局部化的内存管理有助于线程安全。
通过运用这些机制,JVM能够在多线程环境下高效而安全地进行内存分配,并最大限度地减少同步操作带来的性能损耗。这样设计不仅提升了性能,也保证了对象内存分配的安全性和一致性。
说说对象分配规则
在Java中,对象分配规则是关于如何为新对象分配内存的一套规则,以确保内存的有效使用和对象的正确初始化。以下是关于对象分配的主要规则:
- 内存分配:新对象通常在堆内存中分配内存空间。
- 对象头:在为对象分配内存空间后,Java虚拟机会为对象分配一个对象头。对象头包含了一些关于对象的元信息,如对象的哈希码、锁状态、垃圾回收信息等。
- 零值初始化:在对象内存分配后,所有的成员变量会被初始化为零值。具体的零值取决于变量的数据类型。例如,整数类型会初始化为0,布尔类型会初始化为false,对象引用会初始化为null。
- 构造函数调用:一旦对象内存分配和零值初始化完成,Java虚拟机会调用对象的构造函数。
- 对象引用:最后,new 关键字会返回对象的引用,将这个引用分配给一个变量,以便后续可以通过该变量访问对象的属性和方法。
- 垃圾回收管理:Java虚拟机会自动管理对象的内存。如果对象不再被引用,它会被标记为垃圾,并在适当的时机由垃圾回收器回收,释放占用的内存。

这些规则确保了对象在创建时的正确初始化和内存管理。对于程序员来说,最重要的是编写好构造函数以确保对象在创建后具有合适的初始状态,并且不忘记在不再需要对象时将引用置为null,以便垃圾回收器能够回收不再使用的对象。
何时进行类卸载?
类的卸载条件很多,需要满足以下三个条件,并且满足了也不一定会被卸载:
该类所有的实例都已经被回收,也就是堆中不存在该类的任何实例。
加载该类的 ClassLoader 已经被回收。
该类对应的 Class 对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。
可以通过 -Xnoclassgc 参数来控制是否对类进行卸载。
Java虚拟机将结束生命周期的几种情况?(什么情况会导致JVM退出)
- 正常程序终止: 当程序执行完main方法,包括所有非守护线程都终止时,JVM将正常退出。
- 调用System.exit(int status): 显式调用System.exit()方法,以指定的状态码终止当前运行的Java虚拟机。
- 未捕获的异常或错误: 如果某个线程抛出的异常没有被捕获,并且此异常传播到了主线程,JVM可能会终止。
- Runtime.halt(int)或崩溃:
- 直接调用Runtime.halt()会立即停止Java进程,类似于突然终止程序而不调用任何钩子。
- JVM的致命错误(如内存访问违规)也可能导致崩溃并退出。
- 外部命令强制关闭: 例如通过操作系统的任务管理器或者控制台命令,如kill命令。或者操作系统出现错误而导致Java虚拟机进程终止
Java中什么是类加载?类加载的过程?的更多相关文章
- 关于java中的类加载器
什么是类加载器? 类加载器是专门负责加载类的命令或者说工具 ClassLoader java中的3个类加载器 JDK中自带了3个类加载器 启动类加载器 扩展类加载器 应用类加载器 假设有这样一段代码 ...
- 【java虚拟机系列】JVM类加载器与ClassNotFoundException和NoClassDefFoundError
在我们日常的项目开发中,会经常碰到ClassNotFoundException和NoClassDefFoundError这两种异常,对于经验足够的工程师而言,可能很轻松的就可以解决,但是却不一定明白为 ...
- Java内存管理-掌握自定义类加载器的实现(七)
勿在流沙筑高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 上一篇分析了ClassLoader的类加载相关的核心源码,也简单介绍了ClassLoa ...
- 从一道面试题来认识java类加载时机与过程
说明:本文的内容是看了<深入理解Java虚拟机:JVM高级特性与最佳实践>后为加印象和理解,便记录了重要的内容. 1 开门见山 以前曾经看到过一个java的面试题,当时觉得此题很简单,可 ...
- [读书笔记]java中的类加载器
以下内容大多来自周志明的<深入理解Java虚拟机>. 类加载器是java的一项创新,也是java流行的重要原因之一,它最初是为了满足java applet的需求而开发出来. 什么是appl ...
- java类加载时机与过程
转自:http://www.tuicool.com/articles/QZnENv 说明:本文的内容是看了<深入理解Java虚拟机:JVM高级特性与最佳实践>后为加印象和理解,便记录了重要 ...
- Java中的类加载器以及Tomcat的类加载机制
在加载阶段,虚拟机需要完成以下三件事情: 1.通过一个类的全限定名来获取其定义的二进制字节流. 2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构. 3.在Java堆中生成一个代表这个类 ...
- 【深入Java虚拟机】一 JVM类加载过程
首先Throws(抛出)几个自己学习过程中一直疑惑的问题: 1.什么是类加载?什么时候进行类加载? 2.什么是类初始化?什么时候进行类初始化? 3.什么时候会为变量分配内存? 4.什么时候会为变量赋默 ...
- 从一道面试题来认识java类加载时机与过程【转】
说明:本文的内容是看了<深入理解Java虚拟机:JVM高级特性与最佳实践>后为加印象和理解,便记录了重要的内容. 1 开门见山 以前曾经看到过一个java的面试题,当时觉得此题很简单,可 ...
- java中的类加载器ClassLoader和类初始化
每个类编译后产生一个Class对象,存储在.class文件中,JVM使用类加载器(Class Loader)来加载类的字节码文件(.class),类加载器实质上是一条类加载器链,一般的,我们只会用到一 ...
随机推荐
- 查看SELinux状态:
1./usr/sbin/sestatus -v ##如果SELinux status参数为enabled即为开启状态SELinux status: enabl ...
- 几款ZooKeeper可视化工具,最后一个美炸了~
首发于公众号:BiggerBoy 欢迎关注,查看更多技术文章 ZooKeeper是我们工作中常用一个开源的分布式协调服务,提供分布式数据一致性解决方案,分布式应用程序可以实现数据发布订阅.负载均衡.命 ...
- some notes
.displaynone { display: none } https://voce.chat/zh-CN 一个开源的迷你的国产开源聊天软件,服务端非常小,只有 15MB. 4 分钟前 虽然没有办法 ...
- [源码系列:手写spring] IOC第二节:BeanDefinition和BeanDefinitionRegistry
主要内容 BeanDefinition:顾名思义,就是类定义信息,包含类的class类型.属性值.方法等信息. BeanDefinitionRegistry:添加BeanDefinitionRegis ...
- String类的三种常见构造方法
1.根据构造方法创建字符串对象 1.public String() 创建一个空字符串,里面不包含任何内容 2.public String(char[] chs) 创建一个字符数组,将其拼接成字符串对象 ...
- MySQL 创建数据库并指定字符集编码
备忘 CREATE DATABASE mydb CHARACTER SET utf8 COLLATE utf8_general_ci; GRANT ALL ON mydb.* TO "use ...
- DRG,医改分水岭!
2020-11-04 (2021年政府推出2.0版DRG.增加MCC和CC,各自政府的医保支付中增加了人性化的支付倍率的处理) 假设某疾病病组支付标准10000元,患者自付自费比例40%,分三种情况, ...
- 关于TFDMemtable的使用场景【3】处理数据
原因很多: 1.通过TFDMemtable处理数据时,避免影响数据感知 2.处理速度很快. ------------------------------ 从Tdataset读取数据: Procedur ...
- 大模型 Token 究竟是啥:图解大模型Token
前几天,一个朋友问我:"大模型中的 Token 究竟是什么?" 这确实是一个很有代表性的问题.许多人听说过 Token 这个概念,但未必真正理解它的作用和意义.思考之后,我决定写篇 ...
- gitlab tortoisegit puttyGen
使用puttyGen生成公私秘钥注意: 生成后的public key有时会在gitlab识别不出,要多重新生成才行 将puttyGen框中的内容复制进gitlab就行 生成时无需设置密码 选择rsa就 ...