Java的类加载步骤

1、Java虚拟机(JVM)和类的关系

在Java中,启动某个Java程序时,会启动一个JVM进程,无论Java程序多么复杂,是单线程还是多线程,它们都是在JVM进程中运行的。在不同的JVM中运行的同一个类是不同的,两个JVM进程之间并不会共享数据,所有的JVM内存共享都是指同一个JVM内存共享。

public class JVMTest {

    public static void main(String[] args) {
        StaTest.anInt++;
        System.out.println(StaTest.anInt);
    }
}

class StaTest {
    public static int anInt = 1;
}

我们运行两次JVMTest中的主方法,发现两次输出的结果一致,都是2;这是因为两次运行时在两次JVM线程中,第一次JVM运行结束,它对anInt所做的修改就都失效,第二次运行运行时又重新加载该类,两个JVM线程之间的数据是不共享的。
那么虚拟机是什么时候加载类的呢?虚拟机可能在第一次使用某个类时加载该类,也可能采用预加载机制来加载某个类。
在运行程序的过程中,如果用到了某个类,虚拟机会查看该类是否已经加载,如果已经加载,则直接使用,如果该类还未被加载到内存中,则会加载该类。加载一个类需要以下步骤:
a 加载;
b 连接,连接阶段又包括验证、准备、解析;

c 初始化。


一般情况,JVM会连续完成如上三个步骤,因此这个三个步骤也可以统称为类加载。
2、类加载
在学习类加载之前,先了解一个概念。
我们日常开发程序时会定义一些类,如:
public class Aoo {
    public static void main(String[] args) {
        Boo boo = new Boo();
        System.out.println(boo);
    }
}
class Boo {

}
这是一个相当简单的类,在Java程序中创建Boo的实例描述这个类的一个实例的信息,但是你是否想过,我们如何来描述Boo这个类本身的信息呢?
在Java中,类指代一批有相同的特征的对象的抽象,但是类本身也是一种对象,在Java中类本身也是实例,只不过他们是java.lang.Class的实例。在JVM规范中,类加载指根据特定名称查找类或接口类型的二进制表示(即class文件),并由此二进制表示创建类或接口的过程。类的加载主要由类加载器负责完成,类加载器将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说,当程序使用到某个类时,都会先为之创建一个java.lang.Class对象。类加载器通常由JVM提供,类加载器是所有Java程序运行的基础,JVM提供的类加载器通常被称为系统加载器。此外,我们还可以通过继承ClassLoader来创建自己的类加载器。
可以通过不同的类加载器加载不同来源的class文件:
a 在本地文件系统中加载class文件,这是最常见的类加载方式,我们编写的Java代码编译后形成的class文件就保存在本地系统中,运行时按需加载。
b 从JAR包中加载class文件,这种方式也很常见,例如在创建项目时,通常会引入一些JAR包并在需要时加载JAR包中的类。
c 通过网络加载class文件。
d 把一个Java源文件动态编译并执行加载。

3、类连接
链接类或接口包括验证和准备类或接口、它的直接父类、它的直接父接口、它的元素类型(如果是一个数组类型)及其它必要的动作。而解析这个类或接口中的符号引用是链接过程中可选的部分。
a 验证(Verification)阶段用于确保类或接口的二进制表示结构上是正确的。
b 准备(Preparation)阶段的任务是为类或接口的静态字段分配空间,并用默认值初始化这些字段。这个阶段不会执行任何的虚拟机字节码指令。
c 解析阶段将类的二进制数据中的符号引用替换成直接引用。


4、类初始化
在类的初始化阶段,虚拟机负责对类进行初始化,主要是类变量的初始化。在Java中对类变量初始化主要有如下情况:
a 声明类变量时指定初始值;
b 使用静态初始化块为类变量指定初始化值;
c 如果不给类变量赋值,那么基本类型的类变量赋值为0或false(boolean),引用类型赋值为null。
类在如下几种情况下会被初始化:
a 创建类实例。创建类实例的方式又包括:使用new操作符创建实例,通过反射创建实例,通过反序列化的方式创建实例。
c 调用类的静态方法。
d 调用某个类或接口的类变量。
e 初始化某个类的子类;
f 使用反射创建某个类或接口对应java.lang.Class对象。
g 直接运行某个包含main方法的类。
有两种不会初始化类的情况:
a 如果一个类的变量被static final修饰(宏变量),并且该变量的值可以在编译器确定下来,那么在通过类名直接访问该变量时,该类不会被初始化。
b 当使用ClassLoader类的loadClass方法加载某个类时,只是加载类而不会初始化该类。
如下代码:
public class InitTest {
    public static void main(String[] args) throws ClassNotFoundException {
        // 直接调用static final变量,并且变量值在编译时可以确定
        System.out.println(NonInit.ASTR);
        // 通过ClassLoader调用loadClass方法加载类
        ClassLoader.getSystemClassLoader().loadClass("JVMTest.NonInit");
        // 直接调用static final变量,但是变量值需要在运行时确定
        System.out.println(Init.ASTR);
    }
}

class NonInit {
    static {
        System.out.println("NonInit类被初始化。");
    }
    public static final String ASTR = "hello world";
}

class Init {
    static {
        System.out.println("Init类被初始化。");
    }
    public static final String ASTR = System.currentTimeMillis() + "hello world";
}

运行结果

hello world
Init类被初始化。
1462442088508hello world

可以看出,访问一个在编译时可以确定值的宏变量或者使用ClassLoader类的loadClass方法加载某个类并不会让类初始化,而访问必须到运行时才能明确变量值的宏变量会引起类的初始化。

以上就是Java类初始化的三个阶段,接下来将讨论类加载器的相关知识。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值