Java类加载机制(一)
类加载的过程
先从一个HelloWorld说起,对于一个HelloWorld.java文件,起初我们在dos命令行下面使用javac HelloWorld.java编译源程序,生成一个HelloWorld.class的字节码文件,然后我们使用javaHelloWorld就可以执行该程序,可是从我们硬盘上的.class文件是如何变成内存中的执行指令的呢?再细分的一点说,我们知道Java的宣传语:“一次编译,出处执行”,这是由于在应用程序和操作系统中间有一层是Java虚拟机,至于Java虚拟机和操作系统之间的交互,这里不深入讲解。那么我们的问题变成了:.class文件是先怎么被加载到Java虚拟机中的呢?
其实这主要通过Java类加载器完成的,JVM自带了3种类加载器,分别是根类加载器(Bootstrap)、扩展类加载器(Extension)、系统类加载器(SystemClassLoader/也叫做应用类加载器ApplicationClassLoader),另外用户也可以自定义自己的类加载器,如何自定义类加载器后面会说,它们的层次关系如下图所示。
类加载器的工作过程简单的说就是类加载器会将.class文件中的二进制数据读取到内存中,将其放入到JVM运行时数据区的方法区内,然后在java虚拟机的堆中创建一个java.lang.Class对象用来封装类在方法区内的数据结构。Java类加载的最终结果是生成了堆中的Class对象。补充一点:对于这个Class对象其实在编译时就已经存在,无论何时编译器在编译Java源文件的时候都会在编译后的字节码中嵌入一个public、static、final类型的字段class,这个字段表示java.lang.Class的一个实例,因为它是静态的public的,所以,很多时候我们可以通过类名.class来访问它。
详细过程分析:一个.class文件被加载到内存会进行如下的步骤:加载、连接、初始化
加载:就是类加载器ClassLoader查找并加载类的二进制数据到内存的方法区,并在虚拟机的堆中创建一个Class对象的实例,加载的方式可以是本地直接加载也可以是网络下载.class文件或者直接从jar、zip等归档文件中加载等等;
连接:具体可以分为验证、准备、解析三个步骤
a验证:确保被加载类的正确性,包括类的结构检查、语义检查和字节码检查等等;
b准备:为静态变量分配内存空间,并将其初始化,这里的初始化是赋予默认的初始值,如int型则赋予0,boolean型则赋予false;
c解析:将类中的符号引用转化为直接引用
初始化:为类的静态变量赋予正确的初始值,也就是赋予用户对其设置的初始值,和上面的初始化是不同的。
类被初始化时机:JVM只有等到一个类被主动使用时才会去初始化这个类,主动使用有以下6种情况:
1.创建类的实例(比如,通过new关键字创建实例)
2.访问某个类或者接口的静态变量。或者对其静态变量赋值;
3.调用类的静态方法
4.反射
5.初始化一个类的子类,也会初始化该类的父类
6.JVM启动时被标记为启动类的那些类
除了这6种,其他对类的使用都不是主动使用,不会导致类的初始化。
根据上述知识,来看一个类加载次序的例子:
public class TestClassLoader1 {
public static void main(String[] args) {
Count c = Count.getCount();
System.out.println(c.num1);
System.out.println(c.num2);
}
}
//定义一个内部类Count
class Count{
private static Count count = new Count(); //位置1
public static int num1;
public static int num2 = 0;
//private static Single single = new Single(); //位置2
Count(){
num1++;
num2++;
}
public static Count getCount(){
return count;
}
}
上述程序运行时,首先会执行main方法,在main方法中调用了Count类的静态方法getCount,所以会导致Count类被加载到内存,加载的时候在连接的准备阶段,静态变量就已经被赋予默认初始值(count=null,num1=0,num2=0),由于主动使用会导致类的初始化,所以还会将用户赋予的正确值赋予它们,程序顺序执行,首先给count变量复制,new关键字调用构造方法,num1和num2都自加了一次,都等于1,接着,num1用户没有对其赋值,不用初始化,num2用户对其赋值为0,所以num2又变为0。所以,程序最后输出的是1,0。上述程序,如果把位置1的语句移到位置2,程序的输出结果就将变成1,1,分析过程和上面是一致的。
对于上述主动使用类的第五种情况:初始化一个类的之类时,也会初始化它的父类,有下面的例子验证
import java.util.Random;
public class TestClassLoader2 {
public static void main(String[] args) {
System.out.println(T2.y);
}
}
class T1 {
public static int x = 1;
static{
System.out.println("T1 block");
}
}
class T2 extends T1{
public static int y = 1; //位置1
static{
System.out.println("T2 block");
}
}
分析:首先在初始化T2之前,会先初始化它的父类T1,输出“T1 block”,然后初始化T2,输出“T2 block”,最后输出main方法的值1,最终的输出结果就是:
T1 block
T2 block
1
对于含有final修饰的变量,如果在编译的时候可以确定其确切的值,则对其的访问不算对该类的主动使用,下面的例子可以验证这一点:
例1:还是上面的例子,把位置1的语句改为:
public static final int y = 1; //位置1
那么程序的输出结果为:1 .(分析:编译的时候y的值已经可以唯一的确定,不会改变的,实质就是一个常量,所以对其的访问不会导致T2类的初始化,也就不会导致 T1类的初始化)
例2:还是上面的例子,把位置1的语句改为:
public static final int y = new Random().nextInt(100); //位置1
那么程序的输出结果为:
T1 block
T2 block
76
原因就是对于静态变量y的值在编译期间不能确定,所以y然是一个静态变量,对其的访问会导致类T2、T1的的初始化。
例子3:当程序访问的静态变量和静态方法确实在当前类或接口中定义时,才可认为是对当前类或接口的主动使用.
public class TestClassLoader3 {
public static void main(String[] args) {
System.out.println(Child.x);
}
}
class Parent{
public static int x;
static{
System.out.println("Parent Block");
}
}
class Child extends Parent{
static{
System.out.println("Child Block");
}
}
程序的输出结果为:(原因是对Child的静态变量x的访问不是真正访问Child类的静态变量,而是其父类的静态变量,所以只会初始化其父类)
Parent Block
0
当JVM初始化一个类的时候,要求它的所有父类已经被初始化,但这条规则并不适用于接口。初始化一个类时,并不会初始化它所实现的接口;初始化一个接口时,也不会初始化它已实现的父接口,只有当程序首次使用特定接口的静态变量时,才会导致该接口的初始化。
分享到:
相关推荐
深入研究Java类加载机制 深入研究Java类加载机制 深入研究Java类加载机制 深入研究Java类加载机制
java 类加载机制,课程笔记。
1、JVM的内存管理 理解了这一点,所有和对象相关的问题统统都能解决 2、JVM 类加载 理解了这一点,所有和Java相关的配置问题,包括各种App Server的配置,...这是一份对JAVA 类加载机制整理、分析比较全面的文章。
java类加载机制原理与实现
该文件是JVM中关于类加载机制的知识整理的思维导图,包括类加载机制概述、类加载的生命周期、加载时机、加载过程、类加载、类的初始化和实例化等几个大方面进行了讲解,其中类加载中还对JVM三种预定义类加载器进行了...
Java类加载机制.pdf
Java的类加载机制:加载,连接,初始化。JAVA类加载器: Bootstrap ClassLoader : 根类加载器, Extension ClassLoader: 扩展类加载器, System ClassLoader : 系统类加载器, Java反射
Java类加载机制 PDF 下载
2022年初探Java类加载机制Java教程.docx
JAVA类加载机制:探索虚拟世界的大门
类加载机制与动态代理吗,讲得很好对得资源分。
Java类加载机制在E-learning平台功能模块更新中的应用
对java的类加载机制,进行了深入的解析,并详细的实现了客户化加载器。
为方便查看,建议下载此word文档,包括\"译 Java类加载机制(一、二)\" 博文链接:https://nonopo.iteye.com/blog/208012
主要介绍了JAVA类加载机制的相关知识,文中代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下
此外,我们还会探讨Java程序的类加载器和双亲委派机制,以及自定义类加载器和类卸载的实现原理和应用方法。 总的来说,本资源将为Java程序员提供全面的Java字节码和类加载原理和实践经验。通过学习本资源,开发人员将...