写在前面的话:本文是在观看尚硅谷JVM教程后,整理的学习笔记。其观看地址如下:尚硅谷2020最新版宋红康JVM教程

一、什么是类加载过程

(1)、概述

我们编写的类(.java文件)会被编译器(如javac编译器)编译成Class文件。Java虚拟机把Class文件加载到内存中的过程就称为类加载过程。

(2)、类的生命周期

  • 一个类从被加载到虚拟机内存中,到卸载出内存,共经历七个过程,即这个类的生命周期会经历加载、验证、准备、解析、初始化、使用、卸载七个阶段。其中,验证、准备、解析三个阶段又统称为连接。图示如下:



    下面我们将逐个介绍类生命周期每个阶段的执行过程。

二、加载阶段

加载阶段是整个类加载过程的第一个阶段。

在本阶段,Java虚拟机主要完成以下三件事:

(1)、 通过一个类的全限定名称获取定义此类的二进制字节流。

(2)、 将该字节流所代表的静态存储结构转化为方法区中数据结构。

(3)、 在内存中生成一个代表该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

Java虚拟机运行时数据区示意图如下:



总结概括就是,先将Class文件以字节流的形式加载到内存,再把这个字节流放到虚拟机运行时数据区的方法区中,以及在内存中生成一个代表该类的Class对象,作为方法区中该类的访问入口。

三、验证阶段

(1)、 验证阶段目的是确保Class文件中的字节流中包含的信息符合虚拟机规范为约束要求,保证被加载类的正确性不会威胁到虚拟机自身的安全。

(2)、 验证的内容有,

文件格式验证:字节流是否符合Class文件格式。

元数据验证:对字节码描述的信息进行语义分析。

字节码验证:确定程序语义是否合法、符合逻辑。

符号引用验证:该类是否缺少或禁止访问它依赖的外部资源,如其他类、方法等。

四、准备阶段

准备阶段是正式为类中定义的静态变量(static修饰的变量)分配内存并设置零值。

零值:虚拟机为基本数据类型设置的初始值,比如int类型的零值为0,boolean类型的零值为false。

比如, private static int a = 123,在准备阶段过后,a的初始值为0,而不是123。而 a = 123这个赋值动作是在初始化阶段进行的。

通常情况下,准备阶段的静态变量的初始值是零值。但是,如果静态变量被 final 修饰,则可能不会是零值。如 private final static int b = 123 ,则 b 的初始值是123,而不是零值。因为 final static修饰的 b 为常量,在编译时就已经被赋值了,即在被编译成字节码的时候就已经是 b = 123;

五、解析阶段

解析阶段是将常量池中的符号引用替换为直接引用的过程。

符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。

直接引用:是可以直接指向目标的指针、相对偏移量、或者句柄。

解析阶段主要涉及到符号引用和直接引用的转换,但不影响了解整个类加载过程,故先略过,以后再补充。

六、初始化阶段

初始化时类加载过程的最后一个阶段,在前面的几个类加载动作中,除了加载阶段用户编写的代码(应用程序)可以通过自定义的类加载器参与类加载外,其余阶段的类加载动作都是虚拟机完成的。直到初始化阶段,虚拟机才真正执行类中用户编写的Java代码。

类加载器:前面写到,在加载阶段虚拟机需要完成“通过一个类的全限定名称获取定义此类的二进制字节流”这个动作,虚拟机将这个动作交给应用程序,让其自行去决定怎么获取所需的类。而实现这个动作的代码就被称为类加载器。

初始化阶段就是执行类构造器 < clinit >()方法的过程。

< clinit >()方法不是我们直接编写的Java代码,而是javac编译器的自动生成物。

关于< clinit >()方法,需要知道一下几点:

(1)、 此方法由javac编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并而成(即static修饰的变量和static{}),按语句的顺序执行。

如下代码所示:

public class HelloClinit {

    //num = 1 和 static{} 的过程就是 <clinit>()的过程
private static int num = 1; static{
num = 2;
a = 2; //可以为之后定义的变量赋值,但不能访问
//System.out.println(a); //报错
} private static int a; public static void main(String[] args) {
System.out.println(num+","+a);
}
}

输出结果为:2,2

(2)、 < clinit >()方法不需要我们自己去调用父类的< clinit >()方法。因为在子类的< clinit >()执行前,虚拟机会保证父类的< clinit >()已执行完毕

代码如下:

/**
* Java虚拟机会保证在子类的<clinit>()执行前,父类的<clinit>()先执行,
* 这也意味着父类中定义的静态代码块要优于子类中的变量赋值操作
*/
public class HelloClinit {
static class father{
//父类的静态变量负责语句a = 1和静态代码块static{}就是父类的<clinit>()
public static int a = 1;
static {
a = 2;
}
} static class son extends father{
public static int b = 0;
static {
b = a; //故子类在调用的父类的变量a时,实际上a = 2
}
} public static void main(String[] args) {
System.out.println(son.b); //2
}
}

输出结果为:2

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

下面的代码模拟一条线程在死循环状态下操作,而另一条线程无限阻塞等待的过程。

public class ClinitThread {
public static void main(String[] args) {
/**
* 下面的代码创建两个线程,让他们都去创建DeadThread类的对象,
* 但实际上只有线程1会执行<clinit>()方法,即执行static{}语句块,并陷入死循环
* 而线程2则会阻塞等待
*/
Runnable r = () ->{
System.out.println(Thread.currentThread().getName()+"开始");
DeadThread deadThread = new DeadThread();
System.out.println(Thread.currentThread().getName()+"结束");
}; Thread t1 = new Thread(r,"线程1");
Thread t2 = new Thread(r,"线程2"); t1.start();
t2.start();
}
} class DeadThread{
static {
if (true){
System.out.println(Thread.currentThread().getName()+"初始化当前类");
while(true); //陷入死循环
}
}
}

以上几个阶段就是类加载的大致过程

七、拓展补充

类加载的时机

  • (1)、在生命周期图中,加载、验证、准备、初始化、卸载。这五个阶段的顺序是确定的,即只有前一个阶段开始,后一个阶段才能开始。但是解析阶段是不确定的,因为它在一些情况下可以在初始化过程之后再开始。注意这个说的是阶段开始,而并不是阶段完成,因为这些阶段通常都是交叉混合进行的。比如在加载阶段已开始但尚未完成时,验证阶段可能已经开始了。
  • (2)、关于什么时候开始类加载的第一阶段加载,具体由虚拟机自身来把握实现,没有明确的规定。但是,对于类加载中的初始化阶段,有且只有以下六种情况必须立即对类进行初始化操作。这也意味着,在初始化操作前,加载,验证、准备也已经开始了。

    具体的六种情况如下:

    ① 遇到new、getstatic、putstatic或invokestatic这四条字节码指令的时候,如果类没有进行初始化,则需要先触发其初始化.

    生成这四条指令的最常见的java代码场景是:

1.使用new关键字实例化对象的时候

2.读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候

3.调用一个类的静态方法的时候

② 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化

③ 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化

④ 当虚拟机启动的时候,用户需要制定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个主类

⑤ 当使用jdk7新加入的动态语言支持的时候,如果一个java,lang.invoke.MethodHandler实例的最后解析结果是REF_getStatic,REF_putStatic,REF_invokeStatic,REF_newInvokeSpecial四种类的方法句柄,并且这个方法句柄对应的类没有进行过初始化,那么需要先触发其初始化.

⑥ (新)当一个接口中定义了JDK8新加入的默认方法(被default关键字修饰的接口方法)时,如果这个接口的实现类发生了初始化,那么该接口要在其之前初始化

以上几种使用类的情况称为类的主动使用,除了以上6种外,其他使用类的方式都称为类的被动使用,即不会导致类的初始化。

【JVM第一篇--类加载机制】类加载过程的更多相关文章

  1. 深度分析:Java虚拟机类加载机制、过程与类加载器

    虚拟机类加载机制是把描述类的数据从 Class 文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型. ​ 需要注意的是 Java 语言与其他编译时需要进 ...

  2. JVM类加载机制---类加载的过程

    一.类加载的时机 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载.验证.准备.解析.初始化.使用.卸载 7个阶段,其中验证.准备.解析 3个部分统称为 连接. 二.具体步骤 ...

  3. 面试之三:JVM类加载机制-类加载各阶段说明和类加载器

    一.类生命周期:共7个阶段 类从被加载到虚拟机内存中开始,到卸载出内存.整个生命周期包括:加载.验证.准备.解析.初始化.使用和卸载7个阶段. 其中验证.准备.解析3个部分统称为连接. 类加载的过程: ...

  4. JVM类加载机制---类加载器

    一.概念 "通过一个类的全限定名来获取描述此类的二进制字节流",实现这个动作的代码模块成为 类加载器. 二.分类 从java开发人员的角度出发,系统提供的类加载器大致分为如下3类: ...

  5. 初步了解JVM第一篇

    大家都知道,Java中JVM的重要性,学习了JVM你对Java的运行机制.编译过程和如何对Java程序进行调优相信都会有一个很好的认知. 废话不多说,直接带大家来初步认识一下JVM. 什么是JVM? ...

  6. JVM 第一篇:编译 OpenJdk14 ,我行你也行

    本文内容过于硬核,建议有 Java 相关经验人士阅读. 1 引言 从上周开始一直在看周志明的 「深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)」 ,好多年之前看过第二版的,绝对算的上是国内 ...

  7. JVM学习——类加载机制(学习过程)

    JVM--类加载机制 2020年02月07日14:49:19-开始学习JVM(Class Loader) 类加载机制 类加载器深入解析与阶段分解 在Java代码中,类型的加载.连接与初始化过程中都是在 ...

  8. JVM,Tomcat与OSGi类加载机制比较

    首先一个思维导图来看下Tomcat的类加载机制和JVM类加载机制的过程 类加载 在JVM中并不是一次性把所有的文件都加载到,而是一步一步的,按照需要来加载. 比如JVM启动时,会通过不同的类加载器加载 ...

  9. Java 类加载机制 ClassLoader Class.forName 内存管理 垃圾回收GC

    [转载] :http://my.oschina.net/rouchongzi/blog/171046 Java之类加载机制 类加载是Java程序运行的第一步,研究类的加载有助于了解JVM执行过程,并指 ...

随机推荐

  1. Java 8 有多牛逼?打破一切你对接口的认知!

    前段时间面试了一个 39 岁的程序员,结果不是很理想,没看过的点击这里阅读. 最近也面试一些 Java 程序员,不乏工作 4.5 年经验的,当我问他一些 Java 8 的新特性时,大多却答不上来. 比 ...

  2. 三门峡6378.7939(薇)xiaojie:三门峡哪里有xiaomei

    三门峡哪里有小姐服务大保健[微信:6378.7939倩儿小妹[三门峡叫小姐服务√o服务微信:6378.7939倩儿小妹[三门峡叫小姐服务][十微信:6378.7939倩儿小妹][三门峡叫小姐包夜服务] ...

  3. vmware 安装tools

    kali linux 更换成国内源后 安装tools命令 apt install open-vm-tools-desktop fuse -y 需重启  reboot

  4. Shell Scripting 笔记

    Shell Scripting Tutorial Variables in the Bourne shell do not have to be declared, as they do in lan ...

  5. 【应用服务 App Service】当遇见某些域名在Azure App Service中无法解析的错误,可以通过设置指定DNS解析服务器来解决

    问题情形 当访问部署在Azure App Service中的应用返回 "The remote name could not be resolved: ''xxxxxx.com'" ...

  6. centos mysql5.7安装

    1. 安装 1 wget http://repo.mysql.com//mysql57-community-release-el7-11.noarch.rpm 2 rpm -ivh mysql57-c ...

  7. 基于.Net Core开发的物联网平台 IoTSharp V1.5 发布

    很高兴的宣布新版本的发布, 这次更新我们带来了大量新特性, 最值得关注的是, 我们逐步开始支持分布式, 这意味着你可以通过多台服务器共同处理数据, 而不是原来的单机处理, 我们也将遥测数据进行分开存储 ...

  8. js 日期格式、内容合法、比较大小、表单提交验证

    1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"/> 5 &l ...

  9. Navicat连接远程MySQL8.0数据库

    前言: 如果你有一台服务器,并且安装了Mysql8.0及以上版本数据库.此时想通过本地Navicat软件连接远程服务器上的mysql数据库.那么接下来你就要完成以下准备工作: 登录远程服务器上的数据库 ...

  10. Windows2008R2+ IIS7.5+php+mysql 搭建教程

    Windows2008R2+ IIS7.5+php+mysql 搭建教程 1. IIS7.5安装安装角色时候因为 Fastcgi 的需要, aspnet 和 asp 都要选装. 我为了方便,所有的除 ...