java程序在对某个类进行引用、使用时,就会开始对该类进行加载,比如直接使用类加载器进行显式加载、创建该类的对象、使用该类的类变量等情况。类的加载是通过java虚拟机的类加载子系统完成的。类的加载主要分为三个阶段。

类的加载步骤

  1. 类加载子系统负责从文件系统或者网络上加载class文件,class文件在文件开头会有特定的文件标志。
  2. ClassLoader只负责class文件的加载,至于他是否可以运行,则有执行引擎决定。
  3. 加载的类信息存放在一块称为方法区的内存空间中,除了类信息外,方法区还会存放运行时常量池信息,还可能包括字符串字面量和数字字面量(这部分常量信息是Class文件中常量池部分的内存映射)。

加载Load阶段

这个阶段主要分为三步,由类加载器ClassLoader负责执行。

  1. 通过一个类的全限定名或者定义此类的二进制字节流。
  2. 将这些字节流中所代表的静态存储结构转化为方法区中的运行时数据结构。
  3. 在内存中生成一个代表这个字节码文件的java.lang.Class对象,作为方法区这个类的各种数据访问入口。

字节码文件的来源:

  • 从本地文件系统中直接加载。
  • 通过网络获取,比如Web Applet应用。
  • 从打包中获取。jar、war等。
  • 运行时计算生成,比如动态代理技术。

链接阶段Linking

链接阶段又分为三个阶段。

验证

目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害到虚拟机自身安全。主要包括4种验证:文件格式验证、元数据验证、字节码验证、符号引用验证。

准备阶段

该阶段为类变量分配内存并设置该类变量的默认初始值,比如对象引入设置为null,int型设置为0。这里不会包含使用final修饰的static,因为final在编译时就会分配内存了,准备阶段会显示初始化。这里也不会为实例变量分配内存以及初始化,类变量会分配在方法区中,而实例变量会随着对象一起分配在java堆中。

解析阶段

将常量池内的符号引用转换为直接引用的过程。
关于符号引用和直接引用可以参考:
https://blog.csdn.net/mp252119282/article/details/82988504

事实上,解析操作往往会伴随着类在执行初始化完成之后再执行。

初始化阶段

初始化阶段执行类初始化方法clinit方法的过程。此方法不需要定义,是javac编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并而来。
也就是说这个阶段会对类变量进行初始化和对静态代码块内的代码执行。

demo

public class TestInit {private static int name = 5;static {name = 6;}public static void main(String[] args) {System.out.println(name);}
}

使用Idea的jclasslib插件对该文件反编译后的结构进行查看如下图。

clinit方法中字节码指令的意思是先把静态属性name赋值5,此时对应的是类中的name定义时赋值的代码。然后在把静态属性name赋值6,此时对应的是类中静态代码块的代码。

构造器方法中的指令是按照语句在源文件中出现的位置顺序执行。

public class TestInit {private static int name = 5;static {name = 6;value = 7;}private static int value = 6;public static void main(String[] args) {System.out.println(value);}
}

程序结果输出value的值为6,原因是value的内存分配以及默认值初始化在准备阶段已经执行了,而静态代码块是在初始化阶段执行,所以就算value定义的位置在静态代码块之后,程序依然不会报错,此时clinit方法的顺序是先把name赋值5、把name赋值6、把value赋值7、把value赋值6,按顺序由上往下执行,所以最终结果为6。

jclasslib反编译后如下图:

若该类具有父类,JVM会保证父类的clinit方法会在子类的clinit方法执行之前完全执行结束。
验证:

public class Father {static {System.out.println("Father执行开始");for (int i = 0;i==0;){}System.out.println("Father执行结束");}
}public class Son extends  Father{private static int i = 0;static {System.out.println("Son执行开始");System.out.println("Son执行结束");}public static void main(String[] args) {System.out.println(i);}
}

上面的代码,在父类的静态代码块里面添加了一个死循环,不然父类初始化执行结束。
执行结果:

只输出了一个,子类clinit一值没有执行。因为父类clinit没有执行完成。

JVM保证一个类的clinit方法在多线程环境下会被同步加锁,也就是同一时间只有一个线程能够执行该方法,并且该clinit方法只会被执行一次。

验证:

public class TestInit {private static int name = 5;static {name = 6;value = 7;}private static int value = 6;public static void main(String[] args) {Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {Inner inner = new Inner();}},"t1");Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {Inner inner = new Inner();}},"t2");t1.start();t2.start();}static class Inner{static {System.out.println(Thread.currentThread().getName() + "执行Inner类的初始化");//死循环for (int i = 0;i == 0;);}}
}

上面代码使用两个线程来加载Inner类。
结果:

只有线程t1进入了初始化,线程t2被同步锁阻塞在外面,实际上线程t2执行Inner类的初始化,因为类只会加载一次,所以也只会初始化一次。

当类中没有要初始化的代码,也就是说类中没有定义静态代码块,并且静态变量都是使用默认初始化,不显式初始化或者根本没有静态变量时,编译器不会生成clinit方法。


public class TestInit1 {private static int i;
}

结果:clinit不存在。

类加载完成。

特别说明

init方法实际上是我们类的对象构造方法,如果类中没有显示定义,JVM就会为我们默认生成一个无参构造方法,这也就你可以看到上面的截图中都是由一个init方法。

public class TestInit1 {private int i;public TestInit1(int i){this.i = i;}public TestInit1(){i = 5;}
}

上面代码定义了两个构造方法。

两个init方法。

Java虚拟机(JVM)之类的加载过程详解相关推荐

  1. Java类的加载过程详解 面试高频!!!值得收藏!!!

    受多种情况的影响,又开始看JVM 方面的知识. 1.Java 实在过于内卷,没法不往深了学. 2.面试题问的多,被迫学习. 3.纯粹的好奇. 很喜欢一句话: 八小时内谋生活,八小时外谋发展. 望别日与 ...

  2. 中yeti不能加载_第二十章_类的加载过程详解

    类的加载过程详解 概述 在 Java 中数据类型分为基本数据类型和引用数据类型.基本数据类型由虚拟机预先定义,引用数据类型则需要进行类的加载 按照 Java 虚拟机规范,从 Class 文件到加载到内 ...

  3. 类加载顺序及加载过程详解

    转自: 类加载顺序及加载过程详解 下文笔者讲述类的加载顺序及加载过程的详解说明,如下所示 java创建对象的方式分为以下四种 new 反射克隆反序列化 class对象获取的方式分享 //没有完成初始化 ...

  4. Trembling ! Java类的加载过程详解(加载验证准备解析初始化使用卸载)

    [1]类的生命周期 一个类从加载进内存到卸载出内存为止,一共经历7个阶段: 加载->验证->准备->解析->初始化->使用->卸载 其中,类加载包括5个阶段: 加载 ...

  5. Java虚拟机中 类的加载过程

    Java中 类的加载过程 例如下面的一段简单的代码 public class HelloWorld {public static void main(String[] args) {System.ou ...

  6. 类的加载过程详解:加载、验证、准备、解析、初始化

    想要弄明白的知识点: 类加载的过程,加载.验证.准备.解析.初始化.每个部分详细描述. 加载阶段读入.class文件,class文件时二进制吗,为什么需要使用二进制的方式? 验证过程是防止什么问题?验 ...

  7. jboss之启动加载过程详解(-)

    今天看了看jboss的boot.log和server.log日志,结合自己的理解和其他的资料,现对jboss的启动和加载过程做出如下总结: 本文以JBoss Application Server 4. ...

  8. 深入java虚拟机学习 -- 类的加载机制(续)

    昨晚写 深入java虚拟机学习 -- 类的加载机制 都到1点半了,由于第二天还要工作,没有将上篇文章中的demo讲解写出来,今天抽时间补上昨晚的例子讲解. 这里我先把昨天的两份代码贴过来,重新看下: ...

  9. JVM--类加载器详解

    42. JVM--类加载器详解 ● 类加载器子系统作用: 1. 类加载器子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识. 2. ClassLoader只负责 ...

最新文章

  1. 小型电商服务器平台搭建(一)
  2. 1.18 static的常见问题和使用误区
  3. java基础---try后小括号(1.7后IO流的关闭方式)
  4. 前端学习(3251):样式的模块化
  5. mysql和springboot对照_SpringBoot(六) SpirngBoot与Mysql关系型数据库
  6. kafka实现异步发送_Kafka Producer 异步发送消息居然也会阻塞?
  7. Swift - 触摸事件(点击,移动,抬起等)说明及用例
  8. 自考专科计算机信息管理专业好,计算机信息管理(专科)专业介绍
  9. 5去掉button按钮的点击样式_各种好看的小按钮合集,纯css编写,最近在学习时遇到的,记录成为笔记...
  10. java系列4:数组初始化(省略格式)
  11. 2019一级计算机等级考试试题,2019年全国计算机等级考试一级练习试题及答案(一)...
  12. 华米OV坐不住了!九大手机厂商围剿微信小程序
  13. Tomcat安装与优化
  14. 【数据库/数据挖掘/内容检索】 2019年-中国计算机学会推荐国际学术会议和期刊目录(五)
  15. PHP+Swoole 搭建 Websocket 聊天室
  16. python平稳性检验程序_用 Python 检验时间序列的平稳性
  17. Radon 变换原理和应用
  18. 1219v网卡驱动+linux,黑苹果Intel板载网卡驱动-IntelMausiEthernet.kext下载 V2.5.0d0-PC6苹果网...
  19. 阿里云mysql导出表,mysql导出数据库表数据
  20. react中使用swiper

热门文章

  1. Spring Boot基础学习笔记18:Spring Boot整合Redis缓存实现
  2. Java Web应用小案例:查询城市天气信息
  3. 数据库笔记10:创建与管理视图
  4. 【codevs1078】最小生成树,prim算法
  5. 人称代词和Be动词的现在简单肯定式_1
  6. pygame为游戏添加背景_用 Python 制作飞机大战小游戏
  7. 2 环境设置_VS Code 虚拟环境设置
  8. Rock8247 bsp-Tornado-VXWorks Build up
  9. centos编译安装LNMP
  10. https跳转到http session丢失问题