类的加载、连接和初始化
- 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");
}
}