Class文件的加载过程

ClassLoader的工作模式

类的热加载


1 Class文件的装载流程

只有被java虚拟机装载的Class类型才能在程序中使用(注意装载和加载的区别

1.1 类装载的条件

Class只有在必须要使用的时候才会被装载,Java虚拟机不会无条件的装载Class类型。Java虚拟机规定:一个类或者接口在初次使用时,必须进行初始化。这里的使用指的是主动使用,主动使用有以下几种情况:

  • 当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化。
  • 当调用类的静态方法时,即当使用了字节码invokestatic指令
  • 当使用类或者接口的静态字段时(final常量除外),即使用getstatic或者putstatic指令
  • 当使用java.lang.reflect包中的方法反射类的方法时
  • 当初始化子类时,必须先初始化父类
  • 作为启动虚拟机、含有main方法的那个类

除了以上情况属于主动使用外,其他情况均属于被动使用,被动使用不会引起类的初始化

例1:主动使用

public class Parent{

  static{

    System.out.println("Parent init");

  }

}

public class Child{

  static{

    System.out.println("Child init");

  }

}

public class InitMain{

  public static void main(String[] args){

    Child c = new Child();

  }

}

以上声明了3个类:Parent Child InitMain,Child类为Parent类的子类。若Parent被初始化,根据代码中的static块可知,将会打印"Parent init",若Child被初始化,则会打印"Child init"。执行InitMain,结果为:

Parent init

Child init

由此可知,系统首先装载Parent类,接着装载Child类。符合主动装载中的两个条件,使用new关键字创建类的实例会装载相关的类,以及在初始化子类时,必须先初始化父类。

例2 :被动装载

public class Parent{

  static{

    System.out.println("Parent init ");

  }

  public static int v = 100; //静态字段

}

public class Child extends Parent{

  static{

    System.out.println("Child init");

  }

}

public class UserParent{

  public static void main(String[] args){

    System.out.println(Child.v);

  }

}

Parent中有静态变量v,并且在UserParent中,使用其子类Child去调用父类中的变量。

运行代码:

Parent init

100

虽然在UserParent中,直接访问了子类对象,但是Child子类并未初始化,只有Parent父类进行初始化。所以,在引用一个字段时,只有直接定义该字段的类,才会被初始化。

注意:虽然Child类没有被初始化,但是,此时Child类已经被系统加载,只是没有进入初始化阶段。

可以使用-XX:+ThraceClassLoading 参数运行这段代码,查看日志,便可以看到Child类确实被加载了,只是初始化没有进行

例3 :引用final常量

public class FinalFieldClass{

  public static final String constString = "CONST";

  static{

    System.out.println("FinalFieldClass init");

  }

}

public class UseFinalField{

  public static void main(String[] args){

    System.out.println(FinalFieldClass.constString);

  }

}

运行代码:CONST

FinalFieldClass类没有因为其常量字段constString被引用而初始化,这是因为在Class文件生成时,final常量由于其不变性,做了适当的优化。

分析UseFinalField类生成的Class文件,可以看到main函数的字节码为:

在字节码偏移3的位置,通过Idc将常量池第22项入栈,在此Class文件中常量池第22项为:

#22 = String        #23     //CONST

#23 = UTF8         CONST

由此可以看出,编译后的UseFinalField.class中,并没有引用FinalFieldClass类,而是将其final常量直接存放在常量池中,因此,FinalFiledClass类自然不会被加载。(javac在编译时,将常量直接植入目标类,不再使用被引用类)通过捕获类加载日志(部分日志)可以看出:

注意:并不是在代码中出现的类,就一定会被加载或者初始化,如果不符合主动使用的条件,类就不会被初始化。


1.2 类装载的整个过程

1)加载类

加载类处于类装载的第一个阶段。

加载类时,JVM必须完成:

  • 通过类的全名,获取类的二进制数据流
  • 解析类的二进制数据流为方法区内的数据结构
  • 创建java.lang.Class类的实例,表示该类型

2)连接

1 验证类:

当类被加载到系统后,就开始连接操作,验证是连接的第一步。

主要目的是保证加载的字节码是符合规范的。验证的步骤如图:

2 准备

当一个类验证通过后,虚拟机就会进入准备阶段,在这个阶段,虚拟机会为这个类分配相应的内存空间,并设置初始值。

java虚拟机为各种类型变量默认的初始值如表:

类型 默认初始值
int 0
long 0L
short (short)0
char \u0000
boolean false
reference null
float 0f
double 0f

注意:java并不支持boolean类型,对于boolean类型,内部实现是Int,由于int的默认值是0,故对应的,boolean的默认值是false

如果类属于常量字段,那么常量字段也会在准备阶段被附上正确的值,这个赋值属于java虚拟机的行为,属于变量的初始化。事实上,在准备阶段,不会有任何java代码被执行。

3 解析类

在准备阶段完成后,就进入了解析阶段。

解析阶段的任务就是将类、接口、字段和方法的符号引用转为直接引用。

符号引用就是一些字面量的引用,和虚拟机的内部数据结构和内存布局无关。比较容易理解的就是在Class类文件中,通过常量池进行大量的符号引用。

具体可以使用JclassLib软件查看Class文件的结构:::

3)初始化

初始化时类装载的最后一个阶段。如果前面的步骤没有出现问题,那么表示类可以顺利装载到系统中。此时,类才会开始执行java字节码。

初始化阶段的重要工作是执行类的初始化方法<clinit>。方法<clinit>是由编译器自动生成的,它是由类静态成员的赋值语句以及static语句块合并产生的。

例如:

public class SimpleStatic{

  public static int id = 1;

  public static int number;

  static{

    number = 4;

  }

}

java编译器为这段代码生成如下的<clinit>:

0 iconst_1
1 putstatic #2 <Demo.id>
4 iconst_4
5 putstatic #3 <Demo.number>
8 return

可以看出,生成的<clinit>函数中,整合了SimpleStatic类中的static赋值语句以及static语句块,先后对id和number两个成员变量进行赋值

由于在加载一个类之前,虚拟机总是会试图加载该类的父类,因此父类的<clinit>总是在子类<clinit>之前被调用。也就是说,子类的static块优先级高于父类。

public class ChildStatic extends Demo{
  static{
    number = 2;
  }
  public static void main(String[] args){
    System.out.println(number);
  }
}

运行可知:

2

说明父类的<clinit>总是在子类<clinit>之前被调用。

注意:java编译器并不是为所有的类都产生<clinit>初始化函数,如果一个类既没有赋值语句,也没有static语句块,那么生成的<clinit>函数就应该为空,因此,编译器就不会为该类插入<clinit>函数

例如:

public class StaticFinalClass{

  public static final int i=1;

  public static final int j=2;

}

由于StaticFinalClass只有final常量,而final常量在准备阶段初始化,而不在初始化阶段处理,因此对于StaticFinalClass类来说,<clinit>就无事可做,因此,在产生的class文件中没有该函数存在。

看懂Class文件的装载流程的更多相关文章

  1. 看懂class文件 转

    前言 现在周六公司进行一系列的java培训,刚上来就给我看class文件,比较头疼,不过感觉还是学到了一些东西,毕竟像老大说的,想要变得牛逼,是需要多学习多看的.好了,闲话不多说,我整理了一下思路,记 ...

  2. gitbook 入门教程之小白都能看懂的 Gitbook 插件开发全流程

    什么是插件 Gitbook 插件是扩展 GitBook 功能(电子书和网站)的最佳方式. 只要是 Gitbook 默认没有提供的功能,基于插件机制都可以自行扩展,是插件让 Gitbook 变得更加强大 ...

  3. 一个故事看懂Linux文件权限管理

    前情回顾: 我通过open这个系统调用虫洞来到了内核空间,又在老爷爷的指点下来到了sys_open的地盘,即将开始打开文件的工作. 详情参见:内核地址空间大冒险:系统调用 open系统调用链 我是一个 ...

  4. 追源索骥:透过源码看懂Flink核心框架的执行流程

    li,ol.inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-bottom:20px}dt, ...

  5. 透过源码看懂Flink核心框架的执行流程

    前言 Flink是大数据处理领域最近很火的一个开源的分布式.高性能的流式处理框架,其对数据的处理可以达到毫秒级别.本文以一个来自官网的WordCount例子为引,全面阐述flink的核心架构及执行流程 ...

  6. 一图看懂hadoop分布式文件存储系统HDFS工作原理

    一图看懂hadoop分布式文件存储系统HDFS工作原理

  7. 一文搞懂 Netty 发送数据全流程 | 你想知道的细节全在这里

    欢迎关注公众号:bin的技术小屋,如果大家在看文章的时候发现图片加载不了,可以到公众号查看原文 本系列Netty源码解析文章基于 4.1.56.Final版本 在<Netty如何高效接收网络数据 ...

  8. 小白也能看懂的插件化DroidPlugin原理(三)-- 如何拦截startActivity方法

    前言:在前两篇文章中分别介绍了动态代理.反射机制和Hook机制,如果对这些还不太了解的童鞋建议先去参考一下前两篇文章.经过了前面两篇文章的铺垫,终于可以玩点真刀实弹的了,本篇将会通过 Hook 掉 s ...

  9. 【 全干货 】5 分钟带你看懂 Docker !

    欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~ 作者丨唐文广:腾讯工程师,负责无线研发部地图测试. 导语:Docker,近两年才流行起来的超轻量级虚拟机,它可以让你轻松完成持续集成.自动交付 ...

随机推荐

  1. 『Collections』Counter计数

    Counter()方法 计数器,返回字典,会同时去重,文本处理常用 from collections import Counter co = Counter(list('abcdefgad')) co ...

  2. 51nod-1534-博弈

    1534 棋子游戏  题目来源: CodeForces 基准时间限制:1 秒 空间限制:131072 KB 分值: 20 难度:3级算法题  收藏  关注 波雷卡普和瓦西里喜欢简单的逻辑游戏.今天他们 ...

  3. hpu积分赛(回溯法)

    问题 : 不开心的小明① 时间限制: 1 Sec 内存限制: 128 MB 提交: 2 解决: 1 题目描述 一天, 小明很不开心,先是向女神表白被拒, 数学又考了0分, 回家的路上又丢了钥匙, 他非 ...

  4. c# 如何将字符串中用","分开的数字分别存入数组中

    string[] str="1,2,3,11,12,13".Split(',');

  5. sql server数据库中char、nchar、varchar、nvarchar的选择

    在数据库中,字符型的数据是最多的,可以占到整个数据库的80%以上.为此正确处理字符型的数据,对于提高数据库的性能有很大的作用. 在字符型数据中,用的最多的就是Char与Varchar两种类型.前面的是 ...

  6. ubuntu下安装go语言;sublime+gocode搭建;go的卸载和环境变量配个人.bashrc;2空位3个网

    https://blog.csdn.net/needkane/article/details/36891949 https://www.jianshu.com/p/4f79ae4f081c http: ...

  7. Oracle sqlloader

    一.SQL*LOADER简介 SQL*Loader是oracle提供的可以从多种平面文件中向数据库中加载数据的工具,使用sqlldr工具可以在很短的时间内向数据库中加载大量的数据,像把制作好的exce ...

  8. maven install deploy

    1.安装到本地仓库 install jar to local fs mvn .jar -DgroupId=com.bonc -DartifactId=licenseVerify-.jar -Dvers ...

  9. windows 开发者神器 tc – total command和替代品

    作为开发者,windows上开发时,会运用一些神器,比如:total commander,搜索目录.查看文件.批量重命名等非常方便. tc是收费版的,有个免费的替代版本Just manager:htt ...

  10. BitDefender(比特梵德)特惠活动 免费获取9个月激活码

    Bitdefender为了庆祝自己获得2014年最佳杀毒软件,送出9个月的Internet security免费激活码.