类加载机制与反射1——类的加载、连接和初始化

本文详细介绍了Java中的类加载、连接和初始化过程。从JVM和类的关系开始,阐述了类加载的触发条件,包括从本地文件系统、JAR包、网络等不同来源加载类。接着讲解了类连接的验证、准备、解析三个阶段,以及类初始化时对类变量的处理。最后,总结了触发类初始化的6种情况,并特别讨论了final型类变量的初始化特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

类的加载、连接和初始化

  • JVM和类
  • 类的加载
  • 类的连接
  • 类的初始化
  • 类初始化的时机

系统可能在第一次使用某个类时候加载该类,也可能采用预加载机制来加载某个类。

1.JVM和类

    当调用java命令运行某个Java程序时,该命令将会启动一个Java虚拟机进程,不管该Java程序有多么复杂,该程序启动了多少个线程,它们都处于该Java虚拟机进程里。同一个JVM的所有线程、所有变量都处于同一个进程里,它们都使用该JVM进程的内存区。

    当系统出现以下几种情况时,JVM进程将被终止。

                ①程序运行到最后正常结束;

                ②程序运行到使用System.exit()或者Runtime.getRuntime().exit()代码处结束程序; 

                ③程序执行过程中遇到为捕获的异常或错误而结束;

                ④程序所在平台强制结束了JVM进程。

    当Java程序运行结束时,JVM进程结束,该进程在内存中的状态将会丢失。

2.类的加载

        当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三个步骤来对该类进行初始化。
类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说,当程序中适用任何类时,系统都会为之建立一个java.lang.Class对象。
类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。
开发者可以通过继承ClassLoader基类来创建自己的类加载器。通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源。
①从本地文件系统加载class文件;
②从JAR包加载class文件;
③通过网络加载class文件
④把一个Java源文件动态编译,并执行加载;

类加载器通常无须等到"首次使用"该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。

3.类的连接

       当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接主要分为验证、准备、解析三个阶段;

4.类的初始化

        在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对类变量进行初始化。在Java类中对类变量指定初始值有两种方式:①声明类变量是指定初始值;②使用静态初始化块为类变量指定初始值。

public class Test
{
	// 声明变量a时指定初始值
	static int a = 5;
	static int b = 9;         // ①
	static int c;
	static
	{
		// 使用静态初始化块为变量b指定出初始值
		b = 6;
		System.out.println("----------");
	}
	public static void main(String[] args)
	{
		System.out.println(Test.b);
	}
}

        声明变量时指定初始值,静态初始化块都将被当成类的初始化语句,JVM会按这些语句在程序中的排列顺序依次执行它们。

public class Test
{
	static
	{
		// 使用静态初始化块为变量b指定出初始值
		b = 6;
		System.out.println("----------");
	}
	// 声明变量a时指定初始值
	static int a = 5;
	static int b = 9;         // ①
	static int c;
	public static void main(String[] args)
	{
		System.out.println(Test.b);
	}
}
JVM初始化一个类包含如下几个步骤:
①假如这个类还没有被加载和连接,则程序先加载并连接该类;
②假如该类的直接父类还没有被初始化,则初始化其直接父类;
③假如类中有初始化语句,则系统依次执行这些初始化语句;

当执行第2个步骤时,系统对直接父类的初始化步骤也遵循此步骤1~3;依次类推,所以JVM最先初始化的总是java.lang.Object类。

5.类初始化的时机

    当Java程序首次通过下面6中方式来使用某个类或接口时,系统就会初始化该类或者接口。
①创建类的实例。(使用new操作符来创建实例,通过反射来创建实例,通过反序列化的方式来创建实例)
②调用某个类的类方法(静态方法)
③访问某个类或接口的类变量,或为该类变量赋值;
④使用反射方式来强制创建某个类或接口对应的java.lang.Class对象;eg:Class.forName("Person");
⑤初始化某个类的子类;

⑥直接使用jva.exe命令来运行某个主类;

    下面几种情形需要特别指出:

        ①对于一个final型的类变量,如果该类变量的值在编译时就可以确定下来,那么这个类变量相当于"宏变量"。Java编译器会在编译时直接把这个类变量出现的地方替换成它的值,因此即使程序使用该静态类变量,也不会导致该类的初始化:

class MyTest
{
	static
	{
		System.out.println("静态初始化块...");
	}
	// 使用一个字符串直接量为static final的类变量赋值
	static final String compileConstant = "疯狂Java讲义";
}
public class CompileConstantTest
{
	public static void main(String[] args)
	{
		// 访问、输出MyTest中的compileConstant类变量
		System.out.println(MyTest.compileConstant);   // ①
	}
}

        ②反之,如果final修饰的类变量的值不能在编译时确定下来,则必须等到运行时才可以确定该类变量的值,如果通过该类来访问他的类变量,则会导致该类被初始化:

class MyTest
{
	static
	{
		System.out.println("静态初始化块...");
	}
	//采用系统当前时间为static final类变量赋值
	static final String compileConstant = System.currentTimeMillis()+"";
}
public class CompileConstantTest
{
	public static void main(String[] args)
	{
		// 访问、输出MyTest中的compileConstant类变量
		System.out.println(MyTest.compileConstant);   // ①
	}
}

        ③当使用ClassLoader类的loadClass()方法来加载某个类时,该方法只是加载该类,并不会执行该类的初始化。使用Class的forName()静态方法才会导致强制初始化该类;

class Tester
{
	static
	{
		System.out.println("Tester类的静态初始化块...");
	}
}
public class ClassLoaderTest
{
	public static void main(String[] args)
		throws ClassNotFoundException
	{
		ClassLoader cl = ClassLoader.getSystemClassLoader();
		// 下面语句仅仅是加载Tester类
		cl.loadClass("Tester");
		System.out.println("系统加载Tester类");
		// 下面语句才会初始化Tester类
		Class.forName("Tester");
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值