本片文章内容如下:
首先来看一下java程序的执行过程。如下图:
在这个框架图很容易大体上了解Java程序工作原理。首先当程序员写好.java文件后,需要先运行(假设该文件为demo.java)
javac demo.java
此时,你的Java代码就被编译成字节码(.class),如果你是在IDE开发工具中,你保存代码的时候,开发工具已经帮你完成了上述的编译工作,因此你可以在对应的目录下看到class文件。此时class文件依然保存在硬盘中,因此,当你在命令行中运行
java demo
就完成了上面红色框中的工作,JRE的加载器从硬盘中读取class文件,载入到系统分配给JVM的内存区域——运行时数据区(Runtime Data Areas),然后执行引擎解释或者编译类文件,转化为特定的CPU机器码,CPU执行机器码,至此完成整个过程。
下面就来研究一下类加载器是什么东西?又是如何工作的?
类加载器被组织成一种层级结构关系,也就是父子关系。其中Bootstrap是所有类加载器的父亲,如下图:
我们先来简单介绍下上面涉及到的几个class Loader
请参考Android插件化基础1-----加载SD上APK中的"双亲委托"
类从被加载到虚拟机内存中开始,直到卸载出内存为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用和卸载7个阶段,其中验证、准备和解析这是三个部分统称为连接(linking)。如下图:
其中,加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班的"开始"(仅仅指的是开始,而非执行或者结束,因为这些阶段通常都是相互交叉的混合进行,通常会在一个阶段执行的过程中调用或者激活另一个阶段),而解析阶段则不一定(它在某些情况下可以在初始化阶段之后再开始),这是为了支持Java语言的运行时绑定
通过上面的内容我们知道,一个类的加载过程被分为5个阶段:加载、验证、准备、解析、初始化。如下图
下面我们就详细讲解下
类的装载指的是将类的.class文件中的二进制数据读到内存中,将其放在运行时数据区的方法区内,让后在Java堆创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于Java堆中的Class对象,Class对象封装了类在方法区内的数据结构,并向Java程序员提供了访问方法区内的数据结构的接口。
"加载(loading)"阶段是"类加载(Class Loading)"过程的第一个阶段,在此阶段,虚拟机需要完成以下三件事:
加载阶段即可以使用系统提供的类加载器来完成,也可以由用户自定义的类加载器来完成。加载阶段与连接阶段的部分内容(如一部分字节码格式验证动作)是交叉进行的,加载阶段尚未完成,连接可能已经开始。
加载.class文件的方式有:
加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
Java语言本身是相对安全的语言,使用Java编码是无法做到如访问数组边界意外的数据、将一个对象转型为它并且未实现的类型等,如果这样做了,编译器将拒绝编译。但是,Class文件并不一定是由Java源码编译而来,可以使用任何途径,包括16进制编译器(如 UItraEdit)直接编写。如果直接编写了有害的"代码"(字节流),而虚拟机在加载该Class时不进行检查的话,就有可能危害到虚拟机或者程序的安全。
不同的虚拟机,对类验证的实现可能有所不同,但大致都会完成下面的四个阶段的验证:
那么下面我们就来一一介绍
是要验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。
更详细的关于.class文件的格式的内容,请参考Java字节码(.class文件)格式详解(一)
对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求,可能包括的验证如:这个类是否有父类;这个类的父类是否继承了不允许被继承的类;如果这个类不是抽象类,是否实现了其父类或者接口中要求的所有方法等。
主要工作是进行数据流和控制流分析,宝贝被校验类的方法在运行时不会做出危害虚拟机安全的行为。如果一个类方法体的字节码没有通过字节码验证,那肯定是有问题的;但如果一个方法体通过了字节码验证,也不能说明其一定就是安全的。
发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在"解析阶段"中发生。验证符号引用通过字符串描述的权限定名是否能找到对应的类;在指定类中是否存在符合方法字段的描述符及简单名称所描述的方法和字段;符号引用中的类、字段和方法的访问性(private、protected、public、default)是否可被当前类访问。
如果无法通过符号引用验证,将抛出一个java.lang.IncompatibleClassChangeError的子类
验证阶段对于虚拟机的类加载机制来说,不一定是必须要的阶段。如果所运行的全部代码确认是安全的。
可以使用-Xverify:none参数来关闭大部分的类验证措施,以缩短虚拟机类加载时间。
准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配到Java堆中。
比如:
public static int value=100
在"准备"阶段的时候value初始值为0,"初始化"阶段才会变为100
但是,对于static final field,此阶段是直接赋值的。
比如:
private static final int value=100;
在"准备"阶段的时候value初始值为100
Java虚拟机中各种类型的默认初始值。
数据类型 | 默认初始值 |
---|---|
int | 0 |
long | 0L |
short | (short)0 |
byte | (byte)0 |
short | '\u0000' |
boolean | false |
float | 0.0f |
double | 0.0d |
reference | null |
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
可能有同学不了解符号引用和直接引用,我们就在这里简单介绍下:
上面说的东西有点"空",不好理解,那我们举例说明:
在java中,一个java类将会编译成一个class文件。在编译时,java类并不知道引用类的实际内存地址,因此只能使用符号引用来代替。比如com.demo.People类引用com.demo.Tool类,在编译时People类并不知道Tool类的实际内存地址,因此只能使用符号com.demo.Tool(假设)来表示Tool类的地址。而在类装载器装载People类时,此时可以通过虚拟机获取Tool类 的实际内存地址,因此便可以既将符号com.demo.Tool替换为Tool类的实际内存地址,及直接引用地址。
解析类或者接口2.png
类初始化时类加载过程的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与外,其余动作完全由虚拟机主导和控制,到了初始化阶段,才真正开始执行类中定义的Java程序代码。在准备阶段,类变量已经被赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序指定的主观计划去初始化类变量和其他资源,或者可以从另一个角度来表达:初始化阶段是执行类构造器<clinit>()方法的过程。
Java在编译生成.class文件时,会自动产生两个方法,一个是类的初始化方法<clinit>。另一个是实例的初始化方法<init>。
<clinit>与<init>的区别
所以Java编译器为它的每一个类都至少生成一个实例初始化方法。一个用于初始化静态的类变量,一个是初始化实例变量。
在准备阶段,变量已经赋过一次系统要去的初始值,在初始化阶段,则是根据程序员通过程序的主管计划区初始化类变量和其他资源。Java虚拟机规范了4种情况必须立即对类进行初始化(加载、验证、准备必须在此之前完成)
除了上面这4种方式,所有引用类的方法都不会触动初始化,称为被动引用。上面这4种方式是主动引用。如:通过子类引用父类的静态字段,不会导致子类初始化;通过数组定义来引用类,不会触发此类的初始化;引用类的静态扁郎不会触发定义常量类的初始化,因为常量在编译阶段已经被放入到常量池中了。
上面就是一个类加载兵可以使用的整个过程,Java的类加载这种只有需要的时候才加载进来的做法为内存节省了很大的空间。
总体流程如下:
在以下情况的时候,Java虚拟会结束生命周期
上面的基本上都是理论知识,但是怎么把理论知识转化为实战经验,是需要大家平时工作中日益积累的。所以很多招聘者会在面试中增加一个问题——“请描述Java中 new一个对象的过程”类似的面试题,这道题其实说深也深,说浅也浅。那我们就结合我们上面的理论知识,来是描述一下"Java中 new一个对象的过程"
我们将整个过程划分为两个部分:
下面我们就详细跟踪下:
我们先来看下Java程序的执行流程图:
再来看下JVM的大致物理结构图
Java虚拟机把描述类的数据从Class文件加载到内存中,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的加载机制。Class文件由该类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允许用户借用这个Class相关的元信息对象简介调用Class对象的功能,这里就是我们经常能见到的Class类。
类从被加载到虚拟机存在开始,到卸载出内存位置,它的整个生命周期包括了:"加载(Loading)"、"验证(Verification)"、"准备(Preparation)"、"解析(Resolution)"、"初始化(Initialization)"、"使用(using)"和"卸载(Unloading)"七个阶段。其中验证、准备和解析三个部分统称为"连接(Linking)",这七个阶段的发生顺序如下:
大家喜欢就点赞,您的每一次点赞,都是我努力和进步的动力!您可能想不到:您的小小一按,可能就会对另外一个人产生翻天覆地的影响。!最后谢谢您的支持与厚爱
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有