JVM类加载、验证、准备、解析、初始化、卸载过程详解(下)

简介: JVM类加载、验证、准备、解析、初始化、卸载过程详解(下)

3 准备

完成两件事情

  • 为已在方法区中的类的静态成员变量分配内存
  • 为静态成员变量设置初始值
    初始值为0、false、null等
  • image.png
public static final int value = 123;

准备阶段后 a 的值为 0,而不是 123,要在初始化之后才变为 123,但若被final修饰的常量如果有初始值,那么在编译阶段就会将初始值存入constantValue属性中,在准备阶段就将constantValue的值赋给该字段(此处将value赋为123).

4 解析

把常量池中的符号引用转换成直接引用的过程。包括:

  • 符号引用
    以一组无歧义的符号来描述所引用的目标,与虚拟机的实现无关。
  • 直接引用
    直接指向目标的指针、相对偏移量、或是能间接定位到目标的句柄,是和虚拟机实现相关的。

主要针对:类、接口、字段、类方法、接口方法、方法类型、方法句柄、调用点限定符。

5 初始化

真正开始执行类中定义的Java程序代码(或说是字节码),类的初始化就是为类的静态变量赋初始值,初始化阶段就是执行类构造器<clinit>的过程。


如果类还没有加载和连接,就先加载和连接

如果类存在父类,且父类没有初始化,就先初始化父类

如果类中存在初始化语句,就依次执行这些初始化语句

如果是接口

初始化一个类时,并不会先初始化它实现的接口

初始化一个接口时,并不会初始化它的父接口

只有当程序首次使用接口里面的变量或者是调用接口方法的时候,才会导致接口初始化

调用Classloader类的loadClass方法来装载一个类,并不会初始化这个类,不属于对类的主动使用

clinit()方法由编译器自动产生,收集类中static{}代码块中的类变量赋值语句和类中静态成员变量的赋值语句。

在准备阶段,类中静态成员变量已经完成了默认初始化,而在初始化阶段,clinit()方法对静态成员变量进行显示初始化。

类的初始化时机

Java程序对类的使用方式分为:

  • 主动使用
  • 被动使用

JVM必须在每个类或接口“首次主动使用”时才初始化它们,被动使用类不会导致类的初始化。主动使用的场景:


创建类实例

访问某个类或接口的静态变量

如果是 final 常量,而常量在编译阶段就会在常量池,没有引用到定义该常量的类,因此不会触发定义该常量类的初始化

调用类的静态方法

反射某个类

初始化某个类的子类,而父类还没有初始化

JVM启动的时候运行的主类(等于第三条)

定义了 default 方法的接口,当接口实现类初始化时

初始化过程的注意点

  • clinit()方法是IDE自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的,IDE收集的顺序是由语句在源文件中出现的顺序所决定的.
  • 静态代码块只能访问到出现在静态代码块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问.
public class Test {
    static {
        i=0;
        System.out.println(i); //编译失败:"非法向前引用"
    }
    static int i = 1;
}

实例构造器init()需要显式调用父类构造器,而类的clinit()无需调用父类的类构造器,JVM会确保子类的clinit()方法执行前已执行完毕父类的clinit()方法。

因此在JVM中第一个被执行的clinit()方法的类肯定是java.lang.Object.

如果一个类/接口无static代码块,也无 static成员变量的赋值操作,则编译器不会为此类生成clinit()方法

接口也需要通过clinit()方法为接口中定义的static成员变量显示初始化。

接口中不能使用静态代码块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成clinit()方法.不同的是,执行接口的clinit()方法不需要先执行父接口的clinit()方法.只有当父接口中的静态成员变量被使用到时才会执行父接口的clinit()方法.

虚拟机会保证在多线程环境中一个类的clinit()方法别正确地加锁,同步.当多条线程同时去初始化一个类时,只会有一个线程去执行该类的clinit()方法,其它线程都被阻塞等待,直到活动线程执行clinit()方法完毕.

其他线程虽会被阻塞,只要有一个clinit()方法执行完,其它线程唤醒后不会再进入clinit()方法。同一个类加载器下,一个类型只会初始化一次。

6 类的卸载

当代表一个类的Class对象不再被引用,那么Class对象的生命周期就结束了,对应的在方法区中的数据也会被卸载。

Jvm自带的类加载器装载的类,是不会卸载的,由用户自定义的类加载器加载的类是可以卸载的。

参考

  • 《码到成功》
  • 《深入理解Java虚拟机第三版》
目录
相关文章
|
7月前
|
安全 Oracle Java
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
543 0
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
|
10月前
|
域名解析 网络协议 网络安全
SSL证书验证全攻略:DNS/HTTP/手动解析怎么选?
SSL证书在网络安全中至关重要,1Panel提供三种验证方式:DNS验证、HTTP验证和手动解析。DNS验证便捷,适合CDN网站;HTTP验证快速,需服务器在线;手动解析灵活,但操作复杂。根据需求选择合适确认方式,定期检查证书状态。
1036 2
|
存储 Java 开发者
浅析JVM方法解析、创建和链接
上一篇文章《你知道Java类是如何被加载的吗?》分析了HotSpot是如何加载Java类的,本文再来分析下Hotspot又是如何解析、创建和链接类方法的。
590 132
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
域名解析 网络协议 安全
反向DNS解析是从IP地址到域名的映射,主要作用于验证和识别,提高通信来源的可信度和可追溯性
在网络世界中,反向DNS解析是从IP地址到域名的映射,主要作用于验证和识别,提高通信来源的可信度和可追溯性。它在邮件服务器验证、网络安全等领域至关重要,帮助识别恶意行为,增强网络安全性。尽管存在配置错误等挑战,但正确管理下,反向DNS解析能显著提升网络环境的安全性和可靠性。
824 3
|
Java 编译器 API
深入解析:JDK与JVM的区别及联系
在Java开发和运行环境中,JDK(Java Development Kit)和JVM(Java Virtual Machine)是两个核心概念,它们在Java程序的开发、编译和运行过程中扮演着不同的角色。本文将深入解析JDK与JVM的区别及其内在联系,为Java开发者提供清晰的技术干货。
351 1
|
10月前
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
886 55
|
5月前
|
存储 缓存 Java
我们来说一说 JVM 的内存模型
我是小假 期待与你的下一次相遇 ~
427 5
|
11月前
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
855 6
|
5月前
|
存储 缓存 算法
深入理解JVM《JVM内存区域详解 - 世界的基石》
Java代码从编译到执行需经javac编译为.class字节码,再由JVM加载运行。JVM内存分为线程私有(程序计数器、虚拟机栈、本地方法栈)和线程共享(堆、方法区)区域,其中堆是GC主战场,方法区在JDK 8+演变为使用本地内存的元空间,直接内存则用于提升NIO性能,但可能引发OOM。

推荐镜像

更多
  • DNS