一. 类的加载过程

1. 类的加载过程大致是个什么过程?

我们编写产生.java文件,这些.java文件经过Java编译器编译成拓展名为.class的文件,.class文件中保存着Java代码经转换后的虚拟机指令,我们需要将类的.class文件通过类加载器加载成为二进制流进入内存,即JVM运行时数据区中的方法区中成为一种数据结构。然后,在运行时数据区中(没有明确规定是在堆中)生成一个java.lang.Class对象,这个对象用来封装加载到方法区的类的数据结构,便于向Java开发者提供用来访问方法区中类的数据结构的接口。

再来先放一张刻在Java程序员DNA里的图便于直观的讨论

2. 类的具体加载过程

2.1 加载(Loading)

  • 通过一个类的全限定名来获取定义此类的二进制字节流(并没有指明要从一个Class文件中获取,可以从其他渠道,譬如:网络、动态生成、数据库等)
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中(对于HotSpot虚拟就而言就是方法区)生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

2.2 验证(Verification)

验证是连接阶段的第一步,这一过程的目的是为了确保.Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 验证阶段大致会完成4个阶段的检验动作:

  • 文件格式验证:验证字节流是否符合.Class文件格式的规范(例如,是否以魔术0xCAFEBABE开头(cafe babe咖啡宝贝,就问你够不够骚气)、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型)
  • 元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求(例如:这个类是否有父类,除了java.lang.Object之外)
  • 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的
  • 符号引用验证:确保解析动作能正确执行

一个正儿八经的.class文件字节码格式要求如下,以下数据项的数量和顺序都是严格限制死的
u2,u4,u8分别对应2字节,4字节,8字节无符号数
_info结尾表明该数据是表形式

类型 名称 数量
u4 magic 1
u2 minor_version 1
u2 major_version 1
u2 constant_pool_count 1
cp_info constant_pool constant_pool_count-1
u2 access_flags 1
u2 this_class 1
u2 super_class 1
u2 interfaces_count 1
u2 interfaces interface_count
u2 fields_count 1
field_info fields fields_count
u2 methods_count 1
method_info methods method_count
u2 attributes_count 1
attribute_info attributes attributes_count

2.3 准备(Preparation)

准备阶段是正式为类变量(static 成员变量)分配内存并设置类变量初始值(零值)的阶段,是连接阶段的第二步,这些变量所使用的内存都将在方法区中进行分配。这时候进行内存分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在堆中。其次,这里所说的初始值“通常情况”下是数据类型的零值。
假设一个类变量的定义为: public static int value = 123;
那么,变量value在准备阶段过后的值为0而不是123
因为这时候尚未开始执行任何Java方法,而把value赋值为123的 public static指令是在程序编译后,存放于类构造器<clinit>()方法之中的,所以把value赋值为123的动作将在初始化阶段才会执行。

注意:如果是static final类型的常量属性,它会被直接赋予所给定的初始值
假设一个常量定义为:public static final int constValue = 123;
那么,变量constValue准备阶段后的值就为123

原因是在加载生成.class字节码文件时,在attributes属性表中有一个ConstantValue属性类型,如果类变量是static final修饰的常量,就会生成一个ConstantValue属性来进行初始化。

2.4 解析(Resolution)

解析阶段连接的最后一部,也是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。

2.5 初始化(Initialization)

类初始化阶段是类加载过程的最后一步。在前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码(字节码)。
在准备阶段,变量已经赋过一次系统要求的初始值(零值);而在初始化阶段,则根据开发者通过程序制定的主观计划去初始化类变量和其他资源,或者更直接地说:初始化阶段是执行类构造器<clinit>()方法的过程。<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块static{}中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块只能访问到定义在静态语句块之前的变量。
(PS:类构造器使用<clinit>()进行初始化,而实例构造器使用的是<init>()方法

注意:如果该类的直接父类还没有被初始化,那么先初始化其直接父类,也就是先执行父类的<clinit>方法

3.类的加载器

在虚拟机提供了三种类加载器:

  • 启动(Bootstrap)类加载器
  • 扩展(Extension)类加载器
  • 系统(System)类加载器(也称应用类加载器)

3.1 启动类加载器(Bootstrap)

当我们每天打开自己的电脑时,第一个运行的就是我们的引导程序,即Bootstrap,所以看这加载器的名字就知道,是一个非常底层的类加载器,它主要加载的是JVM自身需要的类,这个类是由C++实现的,是虚拟机自身的一部分。

它负责将 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,注意必由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录下也是没有作用的,出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类。

启动类加载器是无法被Java程序直接引用的。

3.2 扩展类加载器(Extension)

该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载 JDK\jre\lib\ext目录中,或者由 java.ext.dirs系统变量指定的路径中的所有类库(如javax.开头的类),开发者可以直接使用扩展类加载器。

3.3 系统类加载器(System/Application)

该类加载器由 sun.misc.Launcher$AppClassLoader来实现,它负责加载系统类路径java -classpath-D java.class.path指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

注意:
应用程序都是由这三种类加载器互相配合进行加载的,如果有必要,我们还可以加入自定义的类加载器。
Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象,而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式即把请求交由父类处理,它一种任务委派模式,下面我们进一步了解它。

3.4 双亲委派模型

双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

注意不同层的类加载器不是继承关系,而是通过组合实现类加载器调用上一级加载功能。

双亲委派机制:

1、当 AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。

2、当 ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。

3、如果 BootStrapClassLoader加载失败(例如在 $JAVA_HOME/jre/lib里未查找到该class),会使用 ExtClassLoader来尝试加载;

4、若ExtClassLoader也加载失败,则会使用 AppClassLoader来加载,如果 AppClassLoader也加载失败,则会报出异常 ClassNotFoundException

实现代码:

protected sychronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{//检查请求的类是否已经被加载Class c = findLoadedClass(name);if(c == null){try{if(parent != null){c = parent.loadClass(name, false);}else{c = findBootstrapClassOrNull(name);}}catch(ClassNotFoundException e){//父类无法完成加载}if(c == null){//父类无法加载,调用本身的findClass方法来进行类加载c = findClass(name);}}if(resolve){resolveClass(c);}return c;
}

二. 创建对象的过程

当一个对象被创建时,虚拟机就会为其在中分配内存来存放对象自己的实例变量及其从父类继承过来的实例变量(即使这些从超类继承过来的实例变量有可能被隐藏但也会被分配空间)。在为这些实例变量分配内存的同时,这些实例变量也会被赋予默认值(零值)。在内存分配完成之后,Java虚拟机就会开始对新创建的对象按照程序猿的意志进行初始化。在Java对象初始化过程中,主要涉及三种执行对象初始化的结构,分别是 实例变量初始化、实例代码块初始化 以及 构造函数初始化。(从父类到子类)
一个对象创建过程的伪代码,instance = new Singleton();

memory = allocate(); //1.分配内存空间
ctorInstance(memory); //2.初始化对象
instance = memory; //3.设置instance指向刚分配的内存地址

由于JVM的重排序,又可能步骤2和步骤3的过程互换。

  • Java要求在实例化类之前,必须先实例化其超类,以保证所创建实例的完整性。事实上,这一点是在构造函数中保证的:Java强制要求Object对象(Object是Java的顶层对象,没有超类)之外的所有对象构造函数的第一条语句必须是超类构造函数的调用语句或者是类中定义的其他的构造函数,如果我们既没有调用其他的构造函数,也没有显式调用超类的构造函数,那么编译器会为我们自动生成一个对超类构造函数的调用

  • 我们在定义(声明)实例变量的同时,还可以直接对实例变量进行赋值或者使用普通代码块对其进行赋值。如果我们以这两种方式为实例变量进行初始化,那么它们将在构造函数执行之前完成这些初始化操作。
    实际上,如果我们对实例变量直接赋值或者使用实例代码块赋值,那么编译器会将其中的代码放到类的构造函数中去,并且这些代码会被放在对超类构造函数的调用语句之后,构造函数本身的代码之前。所以从最终的初始化顺序来看,这些初始化操作在构造器之前完成。

  • 类构造器<clinit>()与实例构造器<init>()不同,它不需要程序员进行显式调用,虚拟机会保证在子类类构造器<clinit>()执行之前,父类的类构造<clinit>()执行完毕。由于父类的构造器<clinit>()先执行,也就意味着父类中定义的静态代码块/静态变量的初始化要优先于子类的静态代码块/静态变量的初始化执行。
    特别地,类构造器<clinit>()对于类或者接口来说并不是必需的,如果一个类中没有静态代码块,也没有对类变量的赋值操作,那么编译器可以不为这个类生产类构造器<clinit>()。此外,在同一个类加载器下,一个类只会被初始化一次,但是一个类可以任意地实例化对象。也就是说,在一个类的生命周期中,类构造器<clinit>()最多会被虚拟机调用一次,而实例构造器<init>()则会被虚拟机调用多次,只要程序员还在创建对象。

  • 一个实例变量在对象初始化的过程中会被赋值几次?
    我们知道,JVM在为一个对象在堆中分配完内存之后,会给每一个实例变量赋予默认零值,这个时候实例变量被第一次赋值,这个赋值过程是没有办法避免的。如果我们在声明实例变量x的同时对其进行了赋值操作,那么这个时候,这个实例变量就被第二次赋值了。如果我们在实例代码块中,又对变量x做了初始化操作,那么这个时候,这个实例变量就被第三次赋值了。如果我们在构造函数中,也对变量x做了初始化操作,那么这个时候,变量x就被第四次赋值。也就是说,在Java的对象初始化过程中,一个实例变量最多可以被初始化4次。当然了,一般不会这样做,除非特殊情况。

总的来说就是:父类的类构造器<clinit>() ->子类的类构造器<clinit>() -> 父类的成员变量和实例代码块 -> 父类的构造函数 -> 子类的成员变量和实例代码块 -> 子类的构造函数

有关Java静态域、块非静态域、块构造函数的初始化顺序?
对于静态变量、静态初始化块、变量、初始化块、构造器,它们的初始化顺序以此是(静态变量、静态初始化块)>(实例变量、初始化块)> 构造器。静态代码块是在类加载时自动执行的,非静态代码块是在创建对象时自动执行的代码,不创建对象不执行该类的非静态代码块。

静态代码块 与 静态方法?
一般情况下,如果有些代码必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的;需要在项目启动的时候就初始化,在不创建对象的情况下,其他程序来调用的时候,需要使用静态方法,这种代码是被动执行的。
两者的区别就是:静态代码块是自动执行的;静态方法是被调用的时候才执行的。
作用:静态代码块可用来初始化一些项目最常用的变量或对象;静态方法可用作不创建对象也可能需要执行的代码;

从JVM看类的加载过程与对象实例化过程相关推荐

  1. 欧尼酱讲JVM(02)——类的加载过程

    我们知道,在代码编译后,就会生成JVM(Java虚拟机)能够识别的二进制字节流文件(*.class).而JVM把Class文件中的类描述数据从文件加载到内存,并对数据进行校验.转换解析.初始化,使这些 ...

  2. java的连接 初始化_java类从加载、连接到初始化过程详解

    Java代码在编译后会转化成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化成汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令. 类加载 ...

  3. JVM简笔—类的加载

    什么是JVM? JVM基本概念 JVM是Java虚拟机(Java Virtual Machine)的缩写,是java运行环境的一部分 是可运行java代码的假想计算机,包括一套字节码指令集(用来解释字 ...

  4. JVM 1.类的加载、连接、初始化

    Java类的加载是由类加载器来完成的,过程如下: 首先,加载是把硬盘.网络.数据库等的class文件中的二进制数据加载到内存的过程,然后会在Java虚拟机的运行时数据区的堆区创建一个Class对象,用 ...

  5. jvm对类的加载、链接、初始化

    package com.jmdf.redis.project;/*** 对于静态字段,只有直接定义了该字段的类才会被初始化* 当一个子类初始化时要求其全部父类已经初始化完毕.* -XX:+TraceC ...

  6. 类的加载顺序和对象的实例化

    直接就在JDK8中用代码来验证吧. import lombok.extern.slf4j.Slf4j;@Slf4j public class Printer {private String s;pub ...

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

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

  8. 尚硅谷 宋红康 JVM教程_02_字节码与类的加载篇

    本系列相关链接 尚硅谷 宋红康 JVM教程_01_内存与垃圾回收篇--01 (20210103-20210110) https://blog.csdn.net/wei198621/article/de ...

  9. JVM源码阅读-Dalvik类的加载

    前言 本文主要研究Android dalvik虚拟机加载类的流程和机制.目的是了解Android中DEX文件结构,虚拟机如何从DEX文件中加载一个Java Class,以及到最终如何初始化这个类直至可 ...

最新文章

  1. html5 文字定义线宽,html 5画布线宽
  2. var s=+newDate();
  3. LinkServer--服务器选项
  4. 【Kick Algorithm】十大排序算法及其Python实现
  5. java 8 永久代_Java8内存结构—永久代(PermGen)和元空间(Metaspace)
  6. Java经典实例:处理单个字符串
  7. UVA10882 Koerner's Pub【数学】
  8. [转载] 卷积神经网络做mnist数据集识别
  9. 7.24实习培训日志-Docker-Compose
  10. 【产品】保险业务收付管理系统概要
  11. python分析鸢尾花数据_iris鸢尾花数据集最全数据分析
  12. Java并发包源码学习系列:AQS共享式与独占式获取与释放资源的区别
  13. mysql数据库character_关于MySQL如何修改character
  14. 零成本学arduino教程—— hc - sr04 超声波距离传感器
  15. 数字逻辑电路(1)--逻辑代数基础
  16. Leetcode个人题解714
  17. 非常规方法彻底删除System Volume Information.exe
  18. 使用dreamweaver制作采用DIV+CSS进行布局——美食甜品店铺加盟企业HTML静态网页 ——学生美食网页设计作品静态HTML网页模板源码
  19. ASCII码中可打印字符和不可打印字符
  20. ftp服务器匿名用户文件夹,ftp服务器匿名用户文件夹

热门文章

  1. vue中如何使用mockjs摸拟接口的各种数据
  2. 内存泄露问题改进(转自vczh)
  3. Intger To Roman
  4. python (3.5)字符串 持续更新中………………
  5. 1.Tomcat配置
  6. Activity动画效果笔记
  7. JavaScript的DOM操作
  8. 编写高质量代码改善C#程序的157个建议——建议127:用形容词组给接口命名
  9. android 地图服务开发 INSTALL_FAILED_MISSING_SHARED_LIBRARY 错误解决
  10. TP5 使用IN查询时如何限制条数