类的加载、连接和初始化
1.JVM和类
(1)当调用java命令运行某个Java程序时,该命令会启动一个Java虚拟机进程,不管该Java程序有多么复杂,该程序启动了多少线程,他们都处于该Java虚拟机进程里。
(2)同一个JVM的所有线程、所有变量都处于同一个进程里,他们都使用该JVM进程的内存区。
下面的ATast1和ATest2的输出结果分别是7和6,因为这两个不是位于同一个JVM中的。
public class A { //定义该类的类变量 public static int a = 6; }
public class ATest1 { public static void main(String args[]){ //创建A的实例 A a = new A(); //让a实例的类变量a的值自增 a.a++; System.out.println(a.a); } }
public class ATest2 { public static void main(String[] args) { A b = new A(); System.out.println(b.a); } }
(3)JVM进程被终止的情况:
①程序运行到最后正常结束;
②程序运行到使用System.exit()或者Runtime.getRuntime().exit()代码处结束程序;
③程序执行中遇到未捕获的异常或错误而结束;
④程序所在平台强制结束了JVM进程。
2.类的加载
(1)当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三个步骤来对该类进行初始化。JVM将会连续完成这三个步骤,所以有时也吧这三个步骤统称为类加载或类初始化;
(2)类加载指的是将类的class文件读入内存,并为之创建一个Class对象,当程序使用任何类时,系统都会为之创建一个Class对象;
(3)类的加载由类加载器完成,类加载器通常由JVM提供,同时,开发人员也可以通过继承ClassLoader类来创建自己的类加载器;
(4)通过使用不同的类加载器可以从不同来源加载类的二进制数据,类的二进制数据通常有如下几个来源:
1)从本地文件系统加载class文件;
2)从jar包加载class文件;
3)通过网络加载class文件;
4)把一个java源文件动态编译,并执行加载。
(5)类加载器无须等到首次使用类时才加载该类,Java虚拟机允许系统预先加载某些类。
3.类的连接
(1)当类被加载后,系统为之生成一个对应的Class对象,接着进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。
(2)类连接的三个阶段:
1)验证:验证被加载的类是否有正确的内部结构,并和其他类协调一致;
2)准备:类的准备阶段负责为类的类变量分配内存,并设置默认初始值;
3)解析:将类的二进制数据中的符号引用替换成直接引用。
4.类的初始化
(1)类初始化阶段,虚拟机负责对类进行初始化,主要是对类变量进行初始化。
(2)对类变量指定初始值的两种方式:
1)声明类变量时指定初始值;
2)使用静态代码块为类变量指定初始值。
public class Test { //声明变量a时指定初始值; static int a =5; static int b; //没有指定初始值,也没有用静态代码块指定初始值,其默认为0 static int c; //静态代码块中为b设置初始值 { b = 6; } }
(3)声明变量时指定初始值,静态初始化块都被当成类的初始化语句,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); } }
(4)JVM初始化一个类的步骤:
1)若该类未被加载和连接,则先加载、连接该类;
2)若该类的直接父类未被初始化,则先初始化该类的直接父类;
3)若类中有初始化语句,则依次执行这些初始化语句。
5.类初始化的时机
(1)当Java程序首次通过下面六种方式来使用某个类或者接口时,系统会初始化该类或接口:
1)创建类的实例(包括使用new关键字、通过反射创建、通过反序列化创建);
2)调用某个类的静态方法;
3)访问某个类的类变量,或为该类变量赋值;
4)初始化某个类的子类;
5)使用反射强制创建某个类或接口对应的Class对象;
6)适应java.exe命令来运行某个主类;
(2)对于一个final型的类变量,如果该类变量的值在编译时就可以确定下来,那么这个类变量相当于一个常量。Java编译器会在编译时直接把这个类变量出现的地方替换成它的值,因此程序使用该静态类变量(final)不会导致该类的初始化;
(3)当使用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("Test4.Tester"); System.out.println("系统加载Tester类"); //这里才会初始化Tester类 Class.forName("Test4.Tester"); } }