java类的加载过程以及类加载器的分析

我们知道,我们写的java代码保存的格式是 .java, java文件被编译后会转换为字节码,字节码可以在任何平台通过java虚拟机来运行,这也是java能够跨平台的原因。

那JVM是如何来让我们写的java文件运行的呢? 这个问题通常的问法好像是:类是如何被加载的。

记得第一次遇见这个问题的时候,同学给我的回答是:

1.虚拟机会加载JDK里类的核心包

2.虚拟机会加载JDK里类的扩展包

3.虚拟机会加载JDK里类的系统包

4.虚拟机再会加载我们写好的java类。

初学的时候,大家都这么说,好像也没发现什么错。 最近在浏览一些博客时看到一些更为详细的讲解,如java类加载全过程,该博文有一万多的点击,但感觉还是讲得不够详细,说了类的加载过程有哪些,但没有详细的展开,说了一些类初始化的细节。 在翻读《深入理解Java虚拟机》209-235页后,总结了其内容,谈谈自己对该部分的理解吧。

希望大家看了之后更能理解JVM的工作原理和java类的生产过程(类加载的过程);

类从被加载到虚拟机类存中开始,到被卸载出内存为止,它的整个生命周期包括

加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载 7个部分、

下面我就来详细的说说每个部分的详细过程,再补充一下双亲委派模型。

再次之前我想补充一个名词解释,类加载器:虚拟机把 实现 类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流” 这个过程的代码称为类加载器

1. 加载

加载只是类加载过程的一个阶段而已,但往往被大家弄成了这就是类的加载过程,所以才有了博文开头时同学给我的那个回答;

希望大家不要混淆出这个很相似的名词,从而对类加载有所误读。

1.JDK在执行程序运行命令时会去JRE目录中找到jvm.dll , 并初始化JVM

这时会产生一个Bootstrap Loader(启动类加载器)

2.Bootstrap Loader 自动加载 Extended Loader(标准扩展类加载器)

3.Bootstrap Loader 自动加载 AppClass Loader(系统类加载器)

4.最后由 AppClass Loader 加载 我们指定(想要运行)的 java 类

这里可以提一下双亲委派模型加载类的方式:

实现双亲委派的代码都集中在java.lang.ClassLoader的 loadClass()方法中, 源码我就不贴出来了;

其源码大概意思如下:

1.先检查此类是否被加载过,若没有加载则调用父加载器的loadClass()方法,

2.若父加载器为空,则默认使用启动类加载器作为父加载器,

3.若父类加载失败,会抛出一个异常,然后再调用自己的findClass()方法来进行加载;

结合第一步加载可以这么理解,

1.首先要启动→ 启动类加载器,这时会调用启动类加载器的父加载器,但由于启动类加载器时所有类的父加载器,
所以其父加载器为空(相当于Object是所有类的父类,这种感脚~),然后它就会调用自己的findClass方法来自启动加载 ;

2.标准扩展类加载器启动时就会借助其父类 启动类加载器 作为父加载器 来启动了;

3.系统类加载器启动时就会借助其父类 标准扩展类加载器 作为父加载器 来启动了;

4.最后我们编写的普通类就会借助其父类 系统类加载器 作为父加载器 来启动了;

2.验证

验证主要分为以下几个步骤:文件格式验证->元数据验证->字节码验证->符号引用验证

1.文件格式验证:主要是检查字节码的字节流是否符合Class文件格式的规范,验证该文件是否能被当前的 jvm 所处理,
如果没问题,字节里就可以进入方法区进行保存了;

2.元数据验证:对字节码描述的信息进行语义分析,保证其描述的内容符合java语言的语法规范,能被java虚拟机识别;

3.字节码验证:该部分最为复杂,对方法体内的内容进行验证,保证代码在运行时不会做出什么危害虚拟机安全的事件;

4.符号引用验证:来验证一些引用的真实性与可行性,比如代码里面引了其他类(符号中通过字符串描述的全限定名是否能找到对应的类),这里就要去检测一下那些来究竟是否存在;或者说代码中访问了其他类的一些属性,这里就对那些属性的可以访问行进行了检验。(这一步将为后面的解析工作打下基础)

多说两句。。。 我觉得这个验证就是看class文件符不符合 JVM 的胃口 , 如果不符合 JVM 的胃口的话,无法完成加载,说明你写的代码 有毒.... 偷笑偷笑

3.准备

准备阶段会为类变量(指的是静态变量,这就是我们常说的,静态变量/方法 在类加载的时候就执行了,通过类名.静态**来调用)分配内存并设置类的初始值; 值得一提的是 如果有以下语句:

public static int i = 123 ;

在准备阶段的初始值是 0 ,而不是 123 , 是因为此时 只是分配内存空间而已, 并没有对 i 进行初始化, 真正的对 i 赋值是在 初始化 阶段;

4.解析

1.类或接口的解析;

2.字段解析;

3.类方法解析;

4.接口方法解析;

此部分内容涉及 invokedynamic指令,静态、动态语音调用 不做展开

如果解析到代码内容有问题,解析不通过将会抛出异常!

5.初始化

类初始化阶段是类加载过程中的最后一步,这才是执行类中定义的java程序代码(也可以说是字节码)。
在准备阶段,已经为变量赋过一次系统要求的初始值,到了初始化阶段会根据程序员的要求出初始化变量赋值。

Java虚拟机没有严格约束什么时候开始类加载过程的第一阶段,但严格规定了有且只有5钟情况必须立即马上光速对类进行 初始化

当然加载、验证、准备需要在次之前,(解析也可以在初始化以后再开始~)

1.遇到new,get static,put static,invoke static这4条字节码指令时,假如类还没进行初始化,则马上对其进行初始化工作。
也就是三种情况:用new实例化一个对象时、读取或设置一个雷的静态字段时、执行静态方法时;

2.使用java.lang.reflect.*的方法对类进行反射调用时,如果类还没有进行过初始化,立即马上光速对其进行初始化!!!

3.初始化一个类的时候,如果其父类还没有被初始化,那么会先去初始化其父类;

4.当 JVM 启动时,用户需要指定一个要执行的主类(包含static void main(String 【】args)的那个类),则JVM会先去初始化这个类;

5.当使用JDK1.7 的动态语言支持时,如果一个java.lang.invoke.MethodHandle实力最后的解析结果为 get static,put static,invoke static 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先初始化;

小结:

介绍了类加载过程的 加载、验证、准备、解析、初始化、等5个阶段,以及虚拟机进行了哪些动作,简单叙述了类加载器的工作原理,如果有说得不妥当的地方,还以请大家批评指正,多多交流。

(0)

相关推荐

  • Java中ClassLoader类加载学习总结

    双亲委派模型 类加载这个概念应该算是Java语言的一种创新,目的是为了将类的加载过程与虚拟机解耦,达到"通过类的全限定名来获取描述此类的二进制字节流"的目的.实现这个功能的代码模块就是类加载器.类加载器的基本模型就是大名鼎鼎的双亲委派模型(Parents Delegation Model).听上去很牛掰,其实逻辑很简单,在需要加载一个类的时候,我们首先判断该类是否已被加载,如果没有就判断是否已被父加载器加载,如果还没有再调用自己的findClass方法尝试加载.基本的模型就是这样(盗图

  • Java的ThreadContext类加载器的实现

    疑惑 以前在看源码的时候,总是会遇到框架里的代码使用Thread.currentThread.getContextClassLoader()获取当前线程的Context类加载器,通过这个Context类加载器去加载类. 我们平时在程序中写代码的时候,遇到要动态加载类的时候,一般使用Class.forName()的方式加载我们需要的类.比如最常见的,当我们进行JDBC编程的时候,我们通过Class.forName()去加载JDBC的驱动. try { return Class.forName("o

  • java自定义类加载器代码示例

    如果要使用自定义类加载器加载class文件,就需要继承java.lang.ClassLoader类. ClassLoader有几个重要的方法: protectedClassLoader(ClassLoaderparent):使用指定的.用于委托操作的父类加载器创建新的类加载器. protectedfinalClass<?>defineClass(Stringname,byte[]b,intoff,intlen):将一个byte数组转换为Class类的实例. protectedClass<

  • Java 动态加载jar和class文件实例解析

    本文研究的主要是Java 动态加载jar和class文件的相关内容,具体如下. JAVA中类文件加载是动态的.也就是说当我们用到的时候才会去加载,如果不用的话,就不会去加载我们的类. JAVA为我们提供了两种动态机制.第一种是隐式机制.第二种是显示机制.如下: 两种方法: 隐式机制 :new一个对象 + 调用类的静态方法 显式机制 :由 java.lang.Class的forName()方法加载 由 java.lang.ClassLoader的loadClass()方法加载 1.Class.fo

  • Java自带定时任务ScheduledThreadPoolExecutor实现定时器和延时加载功能

    java.util.concurrent.ScheduledThreadPoolExecutor 是JDK1 .6之后自带的包,功能强大,能实现定时器和延时加载的功能 各类功能和处理方面优于Timer 1.定时器: ScheduledThreadPoolExecutor  有个scheduleAtFixedRate(command, initialDelay, period, unit) ;方法 command: 执行的线程(可自己New一个) initialDelay:初始化执行的延时时间 p

  • java用类加载器的5种方式读取.properties文件

    用类加载器的5中形式读取.properties文件(这个.properties文件一般放在src的下面) 用类加载器进行读取:这里采取先向大家讲读取类加载器的几种方法:然后写一个例子把几种方法融进去,让大家直观感受.最后分析原理.(主要是结合所牵涉的方法的源代码的角度进行分析) 这里先介绍用类加载器读取的几种方法: 1.任意类名.class.getResourceAsStream("/文件所在的位置");[文件所在的位置从包名开始写] 2.和.properties文件在同一个目录下的类

  • Java中类加载过程全面解析

    类文件加载的顺序 1.先加载执行父类的静态变量及静态初始化块(执行先后顺序按排列的先后顺序) 2.再加载执行本类的静态变量及静态初始化块 只要类没有被销毁,静态变量及静态初始化块只会执行1次,后续再对该类进行其他操作也不会再执行这两个步骤. 类实例创建过程 只有在调用new方法时才会创建类的实例 1.按照上面类文件加载的顺序(类已被加载则跳过此步) 2.父类的非静态变量及非静态初始化块 3.父类的构造方法 4.本类的非静态变量及非静态初始化块 5.本类的构造方法 4.类实例销毁时候,首先销毁子类

  • Java类加载初始化的过程及顺序

    Java类的加载说明 Java类的编译代码都存在于它自己的独立文件中(class),该文件只在需要使用程序代码时才会被加载. 类加载在创建类的第一个对象时发生,但当访问static域或static方法时,也会发生加载. 构造器也是static方法,尽管static关键字没有显式写出,故可进一步说,类是在任何static成员被访问时加载的. 示例说明加载过程 示例源于<Java编程思想> //父类 public class SuperClass { protected int super_a;

  • JAVA提高第七篇 类加载器解析

    今天我们学习类加载器,关于类加载器其实和JVM有很大关系,在这里这篇文章只是简单的介绍下类加载器,后面学习到JVM的时候还会详细讲到类加载器,本文分为下面几个小节讲解: 一.认识类加载器 1.什么是类加载器? 所谓的类加载器可以从其作用来理解,其功能就是将classpath目录下.class文件,加载到内存中来进行一些处理,处理完的结果就是一些字节码.那是谁把这些class类加载到内存中来的呢?就是类加载器. 2.JVM中默认的类加载器有哪些? java虚拟机中可以安装多个类加载器,系统默认三个

  • 详解Java中log4j.properties配置与加载应用

    log4j.properties总结: 一.介绍 Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台.文件.GUI组件.甚至是套接口服务 器.NT的事件记录器.UNIX Syslog守护进程等:我们也可以控制每一条日志的输出格式:通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程. Log4j由三个重要的组件构成:日志信息的优先级,日志信息的输出目的地,日志信息的输出格式.日志信息的优先级从高到低有ERROR.WARN. I

随机推荐