Java JVM类加载机制解读

目录
  • 1.什么是类加载
  • 2.类加载的过程
    • 2.1加载
    • 2.2验证
    • 2.3准备
    • 2.4解析
    • 2.5初始化【重中之重之重中重】
      • 第一段代码:
      • 第二段代码:
      • 第三段代码:
      • 最后一段代码:
  • 总结

1.什么是类加载

首先你要知道一个类的从被加载到虚拟机内存中开始,到被初始化为止,是为类加载的整个过程。下图就是类加载的整个过程:

一个类只有经历了加载、验证、准备、解析、初始化这五个关卡才能被认为是实现了类加载。这,就是类加载。

注意一点:上面五个过程并不是按部就班地“完成”,而是按部就班地“执行”(除解析过程外)。执行时一定是先开始加载,再开始验证,但加载过程中也可能会直接开始验证。

2.类加载的过程

2.1加载

“加载”只是是“类加载”过程的第一个阶段,关于在什么时候开始,规范并没有进行强制约束,可以让虚拟机自行把握。在这个阶段中,Java虚拟机需要完成以下三件事:

1)通过一个类的全限定名来获取这个类的二进制字节流

2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

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

可以用一句话概括:加载是一个读取Class文件,将其转化为某种静态数据结构存储在方法区内,并在堆中生成一个便于用户调用的java.lang.Class类型的对象的过程

2.2验证

验证是连接阶段的第一步,这个阶段的目的是确保Class文件的字节流中包含的信息符合约束要求,,保证这些信息被当做代码运行后不会危害虚拟机自身的安全。

这一过程了解即可。

2.3准备

准备阶段是正式为类中定义的变量(这里说的是静态变量,也就是被static修饰的变量)分配内存,并设置类变量初始值的阶段。

这里有两点需要强调:

1)首先这里进行内存分配的仅仅是类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。

2)其次这里设置的初始值“通常情况”下是数据的零值,而不是用户本身对它赋的初值。

如下代码:

public static int a = 10;

变量a在准备阶段后的初始值是0,而不是10,因为现在只是在类加载过程中,还没有执行任何方法。

上面说到“通常情况”,那就说明还有特殊情况咯,加修饰词final时:

public static final int a = 10;

这时在准备阶段虚拟机就会将a设置为10。其实也不难理解:我们将它设置为常量,那就肯定在任何时候都不能修改啊,天子犯法与庶民同罪!

2.4解析

解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程,这一过程也可能在初始化后进行,并不一定和流程图的执行顺序一样。

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

直接引用:直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。

这一过程比较复杂,有兴趣可以参考《深入理解Java虚拟机》

2.5初始化【重中之重之重中重】

类的初始化阶段是类加载过程的最后一个阶段。在这个阶段Java虚拟机才开始真正执行类中编写的Java程序代码。

初始化阶段有以下六种情况必须立即对类进行“初始化”:

  • 1)使用new关键字实例化对象的时候
  • 2)读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候
  • 3)调用一个类的静态方法的时候
  • 4)使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
  • 5)当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
  • 6)当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

光说不行,主要看

第一段代码:

package com.bit.JVMTest;

class Father {

    public  static int a = 10;

    static {
        System.out.println("爸爸静态代码块");
    }
}

class Son extends Father{

    public static int b = 20;

    static {
        System.out.println("儿子静态代码块");
    }
}

public class ClassLoaderTest {
    public static void main(String[] args) {
        System.out.println(Son.b);
    }
}

运行结果:
爸爸静态代码块
儿子静态代码块
20

首先Son.b是在读取Son类自己的静态字段,这点符合上面六中情况的第二种:读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候需要进行初始化。

其次Son类继承Father类,也就符合第五条:当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化,所以我们先初始化的应该是Father类,然后是Son类。

因此,打印的内容首先是爸爸静态代码块(父类先初始化),然后是儿子静态代码块(子类再初始化),最后是我们想要打印的b(20)本身。

再看

第二段代码:

package com.bit.JVMTest;

class  grandFather{
    static{
        System.out.println("爷爷静态代码块");
    }
}

class Father extends grandFather{

    public  static int a = 10;

    static {
        System.out.println("爸爸静态代码块");
    }
}

class Son extends Father{

    public static int b = 20;

    static {
        System.out.println("儿子静态代码块");
    }
}

public class ClassLoaderTest {
    public static void main(String[] args) {
        System.out.println(Son.a);
    }
}

运行结果:
爷爷静态代码块
爸爸静态代码块
10

首先要明确:Son.a是在读取父类Father类的静态字段(注意a字段在Son类的父类中),而不是读取Son类本身的静态字段

因此这次不会初始化Son类本身。

因此这次不会初始化Son类本身。

因此这次不会初始化Son类本身。

其它的和第一段代码很相似:JVM在初始化Father类的时候,发现这个类还有一个父类没有被初始化,那就先初始化它的父类:grandFather

因此,打印的内容首先是爷爷静态代码块(Father类的父类先初始化),然后是爸爸静态代码块(Father类再初始化),最后是我们想要打印的a(10)本身。

第三段代码:

package com.bit.JVMTest;

class  grandFather{
    static{
        System.out.println("爷爷静态代码块");
    }
}

class Father extends grandFather{

    public final static int a = 10;

    static {
        System.out.println("爸爸静态代码块");
    }
}

class Son extends Father{

    public static int b = 20;

    static {
        System.out.println("儿子静态代码块");
    }
}

public class ClassLoaderTest {
    public static void main(String[] args) {
        System.out.println(Son.a);
    }
}

运行结果:10

看到这里是不是想说卧**你*个*。

别急别急,这里的主函数调用虽然和第二段代码一样,但是注意!!!我们给a这个静态字段加了一个final修饰符

再看六条中的第(2)条:读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候会触发类加载。

也就是说我们读取的a是被final修饰的,读取这种静态字段并不会引起任何类的初始化,所以就直接打印a(10)了。

再看

最后一段代码:

package com.bit.JVMTest;

class Father {

   public Father(){
       System.out.println("爸爸构造方法");
   }

    static {
        System.out.println("爸爸静态代码块");
    }

    {
        System.out.println("爸爸普通代码块");
    }
}

class Son extends Father{
    public Son(){
        System.out.println("儿子构造方法");
    }

    static {
        System.out.println("儿子静态代码块");
    }

    {
        System.out.println("儿子普通代码块");
    }
}

public class ClassLoaderTest extends Son{
    public static void main(String[] args) {
        System.out.println("开始");
        new Son();//这里实例化一个Son类的对象
        System.out.println("结束");
    }
}

运行结果:
 爸爸静态代码块
 儿子静态代码块
 开始
 爸爸普通代码块
 爸爸构造方法
 儿子普通代码块
 儿子构造方法
 结束

看到这里是不是欲哭无泪,我**不学了我。别急先听我细细分析一波~
这里有一个细节:主类继承了Son类!,这貌似没什么啊,但是还有一个细节:我们的main()方法是主类中的静态方法!看到这里是不是明白了些什么?

没错!当我们调用main()方法的时候,就引起了主类的初始化,主类继承Son类,Son类继承Father类,所以就先进行Father类的初始化:打印爸爸静态代码块,接着Son类初始化:打印儿子静态代码块,最后该终于我主类初始化了:代码中没什么可以初始化的…(尴尬)。

接下来是第二阶段:执行main()方法:

1.先打印:开始字样。

2.接着是构造 Son()实例,那么就会先构造它的父类Father()的实例:构造实例时按照先执行代码块,再执行构造方法的顺序来。所以就先打印了:爸爸普通代码块、爸爸构造方法 这几个大字。然后再执行构造Son()的实例,构造顺序一样,所以就后打印了:儿子普通代码块、儿子构造方法 这几个大字。

3.最后打印:结束字样。

此时main()才方法真正结束。

总结

我们平常所说的类加载体现在代码上就是初始化这一阶段,我这里结束的也仅限于此,想了解详细的类加载可以参考《深入理解Java虚拟机》这本书,也可以看其他博主的知识总结。感谢你能看到这里!

到此这篇关于Java JVM类加载机制解读的文章就介绍到这了,更多相关Java JVM 类加载机制内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 面试必时必问的JVM 类加载机制详解

    目录 前言 正文 1.类加载的过程. 1)加载 2)验证 3)准备 4)解析 5)初始化 2.Java 虚拟机中有哪些类加载器? 1)启动类加载器(Bootstrap ClassLoader): 2)扩展类加载器(Extension ClassLoader): 3)应用程序类加载器(Application ClassLoader): 3.什么是双亲委派模型? 4.为什么使用双亲委派模式? 5.有哪些场景破坏了双亲委派模型? 6.为什么要破坏双亲委派模型? 7.如何破坏双亲委派模型? 8.Tomc

  • Java虚拟机JVM类加载机制(从类文件到虚拟机)

    一.类加载机制简介 什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构.类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口. 类加载机制:所谓的类加载机制就是虚拟机将class文件加载到内存,并对数据进行验证,转换解析和初始化,形成虚拟机可以直接使用的jav

  • java虚拟机JVM类加载机制原理(面试必问)

    目录 1.类加载的过程. 1)加载 2)验证 3)准备 4)解析 5)初始化 2.Java 虚拟机中有哪些类加载器? 1)启动类加载器(Bootstrap ClassLoader): 2)扩展类加载器(Extension ClassLoader): 3)应用程序类加载器(Application ClassLoader): 3.什么是双亲委派模型? 4.为什么使用双亲委派模式? 5.有哪些场景破坏了双亲委派模型? 1)线程上下文类加载器 2)Tomcat 的多 Web 应用程序 3)OSGI 实现

  • jvm之java类加载机制和类加载器(ClassLoader)的用法

    当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载.连接.初始化3个步骤来对该类进行初始化.如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化. 一.类加载过程 1.加载 加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象. 类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础

  • JVM类加载机制原理及用法解析

    一.JVM 类加载机制 JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程. 1. 加载: 加载是类加载过程中的第一个阶段,这个阶段会在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的入口.注意这里不一定非得要从一个 Class 文件获取,这里既 可以从 ZIP 包中读取(比如从 jar 包和 war 包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将 JSP 文件转换成对应的

  • 通俗讲解JVM的类加载机制

    前言 我们很多小伙伴平时都是做JAVA开发的,那么作为一名合格的工程师,你是否有仔细的思考过JVM的运行原理呢. 如果懂得了JVM的运行原理和内存模型,像是一些JVM调优.垃圾回收机制等等的问题我们才能有一个更清晰的概念. 为了走进JVM,深入了解底层,王子打算写一个JVM的专题,留下自己对JVM探索的足迹,同时也希望能帮到小伙伴们更好的理解JVM. 那我们开始吧. JAVA代码的运行流程 首先我们就来聊一聊JAVA代码是怎么运行起来的,这部分比较基础相信大家都知道,就当成是个复习吧. 我们编写

  • Java JVM类加载机制解读

    目录 1.什么是类加载 2.类加载的过程 2.1加载 2.2验证 2.3准备 2.4解析 2.5初始化[重中之重之重中重] 第一段代码: 第二段代码: 第三段代码: 最后一段代码: 总结 1.什么是类加载 首先你要知道一个类的从被加载到虚拟机内存中开始,到被初始化为止,是为类加载的整个过程.下图就是类加载的整个过程: 一个类只有经历了加载.验证.准备.解析.初始化这五个关卡才能被认为是实现了类加载.这,就是类加载. 注意一点:上面五个过程并不是按部就班地"完成",而是按部就班地&quo

  • 详解Java的类加载机制及热部署的原理

    一.什么是类加载 类的加载指的是将类的.class文件的二进制数据读入到内存中,将其放在运行数据区的方法去,然后再堆区创建一个java.lang.Class对象,用来封装类在方法区的数据结构.类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区的数据结构,并且向Java程序员提供了访问方法区的数据结构的接口. 类加载器并不需要等到某个类被"首次主动使用"时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.cla

  • Java之类加载机制案例讲解

    1.类加载 <1>.父子类执行的顺序 1.父类的静态变量和静态代码块(书写顺序) 2.子类的静态变量和静态代码块(书写顺序) 3.父类的实例代码块(书写顺序) 4.父类的成员变量和构造方法 5.子类的实例代码块 6.子类的成员变量和构造方法 <2>类加载的时机 如果类没有进行初始化,则需要先进行初始化,虚拟机规范则是严格规定有且只有5种情况必须先对类进行初始化(而加载,验证,准备要在这个之前开始) 1.创建类的实例(new的方式),访问某个类的静态变量,或者对该静态变量赋值,调用类

  • 详细分析JVM类加载机制

    目录 前言 1. jvm 的组成 2. 类加载 1. 加载 2. 链接 3. 初始化 3. 类加载器 引导类加载器(启动类加载器) 扩展类加载器 应用程序类加载器 4. 双亲委派机制 5. 类的主动/被动使用 结语 前言 ladies and gentleman , 你们好 ,我是羡羡 , 这节我们进入jvm的学习 , 我们知道 , jvm是java虚拟机, java代码的执行与 jvm 息息相关, 接下来我们来依次介绍 , 首先这节先来介绍 jvm 中的类加载部分 1. jvm 的组成 jvm

  • 一篇文章弄懂JVM类加载机制过程以及原理

    目录 一.做一个小测试 二.类的初始化步骤: 三.看看你写对了没? 四.类的加载过程 五.类加载器的分类 1.启动类加载器(引导类加载器) 2.扩展类加载器 3.应用程序类加载器(系统类加载器) 六.类加载器子系统的作用 七.总结 一.做一个小测试 通过注释,标注出下面两个类中每个方法的执行顺序,并写出studentId的最终值. package com.nezha.javase; public class Person1 { private int personId; public Perso

  • JVM类加载机制详解

    一.先看看编写出的代码的执行过程: 二.研究类加载机制的意义 从上图可以看出,类加载是Java程序运行的第一步,研究类的加载有助于了解JVM执行过程,并指导开发者采取更有效的措施配合程序执行. 研究类加载机制的第二个目的是让程序能动态的控制类加载,比如热部署等,提高程序的灵活性和适应性. 三.类加载的一般过程 原理:双亲委托模式 1.寻找jre目录,寻找jvm.dll,并初始化JVM: 2.产生一个Bootstrap Loader(启动类加载器): 3.Bootstrap Loader自动加载E

  • 详解JVM类加载机制及类缓存问题的处理方法

    前言 大家应该都知道,当一个Java项目启动的时候,JVM会找到main方法,根据对象之间的调用来对class文件和所引用的jar包中的class文件进行加载(其步骤分为加载.验证.准备.解析.初始化.使用和卸载),方法区中开辟内存来存储类的运行时数据结构(包括静态变量.静态方法.常量池.类结构等),同时在堆中生成相应的Class对象指向方法区中对应的类运行时数据结构. 用最简单的一句话来概括,类加载的过程就是JVM根据所需的class文件的路径,通过IO流的方式来读取class字节码文件,并通

随机推荐