《深入理解java虚拟机》笔记(8)类的加载机制
一、类加载机制
类加载器将类的.class文件中的二进制数据读入到内存中,将其放在方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

二、类的生命周期

类的生命周期包括:加载、验证、准备、解析、初始化、使用、卸载7个阶段。其中加载、验证、准备、初始化、卸载5个阶段是按照这种顺序按部就班的开始,而解析阶段则不一定:某些情况下,可以在初始化之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)。
注意:这里写的是按部就班的开始,而不是按部就班地进行或完成,因为这些阶段通常都是互相交叉混合式进行的,通常会在一个阶段执行过程中调用、激活另外一个阶段。
1、加载
加载阶段会做3件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
 - 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
 - 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。
 
相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。
此处第一点并没指明要从哪里获取、怎样获取,因此这里给开发人员预留了创造力空间。许多Java技术就建立在此基础上,例如:
- 从ZIP包读取,如JAR、WAR。
 - 从网络中获取,这种场景最典型应用场景应用就是Applet。
 - 运行时计算生成,使用较多场景是动态代理技术,如spring AOP。
 
加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。
2、验证
确保被加载的类的正确性,分为4个验证阶段:
- 文件格式验证
 - 元数据验证
 - 字节码验证
 - 符号引用验证
 
验证阶段非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间
3、准备
- 为类的静态变量分配内存,并初始化默认值,这些内存是在方法区中分配,需要注意以下几点:
 - 此处内存分配的变量仅包含类变量(static),而不包括实例变量,实例变量会随着对象实例化被分配在java堆中。
 - 这里默认值是数据类型的默认值(如0、0L、null、false),而不是代码中被显示的赋予的值。
 - 如果类字段的字段属性表中存在ConstatntValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstValue属性所指定的值。
 
4、解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
5、初始化
为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。初始化阶段是执行类构造器<clinit>()方法的过程。
- <clinit>()方法是由编译器自动收集类中的所有类变量赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件出现的顺序所决定的。静态语句块中只能访问到定义在静态语句块之前的变量,定义在之后的变量可以赋值,但不能访问。如下所示:
 

- <clinit>()方法与类构造函数不一样,不需要显示调用父类构造函数,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已执行完毕。
 - 由于父类的<clinit>()方法首先执行,意味着父类中的静态语句块要优先于子类的变量赋值操作,如下所示,最终得出的值是2,而不是1
 
public class TestClassLoader {
    public static int A = 1;
    static {
        A = 2;
//        System.out.println(A);
    }
    static class Sub extends TestClassLoader {
        public static int B = A;
    }
    public static void main(String[] args) {
        System.out.println(Sub.B);
    }
}
- <clinit>()方法对于类和接口来说,并不是必须的,若类没有静态语句块,也没有对变量赋值操作,则不会生成<clinit>()方法。
 - 接口与类不同的是,接口不需要先执行父类的<clinit>()方法,只有父接口定义的变量使用时,父接口才会被初始化。另外接口的实现类也不会先执行接口的<clinit>()方法。
 - 虚拟机保证当多线程去初始化类时,只会有一个线程去执行<clinit>()方法,而其他线程则被阻塞。
 
<clinit>()方法和<linit>()方法区别
执行时机不同:init方法是对象构造器方法,在new一个对象并调用该对象的constructor方法时才会执行。clinit方法是类构造器方法,是在JVM加载期间的初始化阶段才会调用。
执行目的不同:init是对非静态变量解析初始化,而clinit是对静态变量,静态代码块进行初始化。
三、类加载器
虚拟机把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自助机决定如何去获取所需要的类。这个模块称为类加载器。
类加载器最初是为了满足Java Applet需求开发出来的,但却在类层次划分、OSGI、热部署、代码加密等领域大放异彩。
类加载器的层次关系图(这里父类加载器并不是通过继承关系来实现的,而是采用组合实现的):

类加载器大致可分为三类:
- 启动类加载器(Bootstrap ClassLoader),负责加载存放在$JAVA_HOME\jre\lib下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。
 - 扩展类加载器(Extension ClassLoader),该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载$JAVA_HOME\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。
 - 应用程序类加载器(Application ClassLoader),该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
 
应用程序是由这三种类加载器互相配合进行加载的,如果有必要,我们还可以加入自定义的类加载器。因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件。
1、双亲委派模型
工作流程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
- 当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
 - 当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
 - 如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
 - 若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。
 
意义:系统类防止内存中出现多份同样的字节码。
《深入理解java虚拟机》笔记(8)类的加载机制的更多相关文章
- 《深入理解java虚拟机》:类的初始化
		
深入理解java虚拟机>:类的初始化 类从被载入到虚拟机内存中開始.到卸载出内存为止,它的整个生命周期包含:载入.验证.准备.解析.初始化.使用和卸载七个阶段.当中验证.准备.解析3个部分统称为 ...
 - 深入理解java虚拟机笔记Chapter7
		
虚拟机类的加载机制 概述 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类的加载机制. 类加载的时机 J ...
 - Java虚拟机笔记(一):类加载机制
		
一.概述 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 二.类加载的生命周期 类从被加载到 ...
 - 深入java虚拟机学习 -- 类的加载机制
		
当看到"类的加载机制",肯定很多人都在想我平时也不接触啊,工作中无非就是写代码,不会了可以百度,至于类,jvm是怎么加载的我一点也不需要关心.在我刚开始工作的时候也觉得这些底层的内 ...
 - 深入java虚拟机学习 -- 类的加载机制(续)
		
昨晚写 深入java虚拟机学习 -- 类的加载机制 都到1点半了,由于第二天还要工作,没有将上篇文章中的demo讲解写出来,今天抽时间补上昨晚的例子讲解. 这里我先把昨天的两份代码贴过来,重新看下: ...
 - 深入java虚拟机学习 -- 类的加载机制(三)
		
类的初始化时机 在上篇文章中讲到了类的六种主动使用方式,反射是其中的一种(Class.forName("com.jack.test")),这里需要注意一点:当调用ClasLoade ...
 - jvm系列(一):java类的加载机制
		
java类的加载机制 1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装 ...
 - JVM(1):Java 类的加载机制
		
原文出处: 纯洁的微笑 java类的加载机制 1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang. ...
 - java中带继承类的加载顺序详解及实战
		
一.背景: 在面试中,在java基础方面,类的加载顺序经常被问及,很多时候我们是搞不清楚到底类的加载顺序是怎么样的,那么今天我们就来看看带有继承的类的加载顺序到底是怎么一回事?在此记下也方便以后复习巩 ...
 - JVM-01:类的加载机制
		
本文从 纯洁的微笑的博客 转载 原地址:http://www.ityouknow.com/jvm.html 类的加载机制 1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内 ...
 
随机推荐
- listen 66
			
Frog Species Found in Big Apple Scientists discover new species all the time—on the order of 15,000 ...
 - linux命令学习笔记(51):lsof命令
			
lsof(list open files)是一个列出当前系统打开文件的工具.在linux环境下,任何事物都以文件的形式存在, 通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件.所以如传输控制协 ...
 - 常用SASS封装
			
结合Compass库和工作总结,列出了项目中最为常用的SASS片段.内容收集于网络,我进行了简单整理并测试正常,可以根据实际项目情况进行取舍,值得学习或直接应用,感谢! //重置浏览器默认样式@imp ...
 - APIO2018爆零记
			
Day1 集合 7点和yyc他们在学校简单的集合了一下 在大通道看到了整个年级来上操 嘲讽了一番就大摇大摆的走出了校门 校门口看无迟到周的权益部长lzj同学满眼的羡慕 2333 然后到了裕龙酒店登记入 ...
 - WPF开发学习笔记(转)
			
总结下学习WPF的笔记,方便查阅 1 编译 添加程序集引用:WindowsBase.dll,PresentationCore.dll,PresentationFramework.dll 2 布局 ...
 - bzoj 4032 [ HEOI 2015 ] 最短不公共子串 —— 后缀自动机+序列自动机
			
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4032 序列自动机其实就是每个位置记录一下某字母后面第一个出现位置,为了子序列能尽量长. 对字 ...
 - 基于Html5的移动端APP开发框架
			
快速增长的APP应用软件市场,以及智能手机的普及,手机应用:Native(原生)APP快速占领了APP市场,成为了APP开发的主流,但其平台的不通用性,开发成本高,多版本开发等问题,一直困扰着专业AP ...
 - Windows窗体间的数据交互
			
轻松掌握Windows窗体间的数据交互 作者:郑佐 2004-04-05 Windows 窗体是用于 Microsoft Win ...
 - iterator与iterable的区别和联系
			
iterator与iterable 用Iterator模式实现遍历集合Iterator模式是用于遍历集合类的标准访问方法.它可以把访问逻辑从不同类型的集合类中抽象出来,从而避免向客户端暴露集合的内 ...
 - POJ-3617
			
Best Cow Line Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 25616 Accepted: 6984 De ...