类的加载、连接和初始化
一.JVM和类
package JVM; public class A { //定义该类的类变量 public static int a = 6; }
package JVM; public class ATest { public static void main(String[] args) { //创建A的实例 A a = new A(); //让a的实例变量a自增 a.a ++; System.out.println(a.a); } }
package JVM; public class BTest { public static void main(String[] args) { //创建A的实例 A b = new A(); //输出b实例的变量a的值 System.out.println(b.a); } }
以上程序引出以下认识:
/**
*一.同一个JVM的所有线程,所有变量都处在同一个进程里
* 它们都使用该JVM进程的内存区
*二.JVM进程终止情况
* 程序运行到最后正常结束
* 程序运行使用System.exit()或Runtime.getRuntime().exit()代码处结束程序
* 程序执行过程中遇到未捕捉的异常或错误而结束
* 程序所在平台强制结束了JVM进程
*三.这三个代码ATest和BTest是在两个不同的JVM进程中运行,所以ATest类修改会消失
* 说明了JVM进程结束,该进程在内存中的状态将会丢失
*/
二.类的加载
1.系统还未被加载到内存中、则系统会通过加载、连接、初始化三个步骤来对该类进行初始化
2.类加载:将类的class文件读入内容,并创建一个java.lang.Class对象
3.JVM提供类加载器(程序运行的基础)-->完成类加载
4.类加载器的种类
- 从本地文件系统加载class文件(绝大部分程序的类加载方式)
- 从JAR加载class文件
- 通过网路加载class文件
- 把一个java源文件动态编译,并执行加载
java虚拟机允许系统预先加载某些类
三.类的连接
类加载后,系统为止生成一个对应的class文件-->进入连接阶段
类的连接:类的二进制数据合并到JRE中,类连接的三个阶段
- 验证结构:被加载类内部结构是否正确
- 分配内存:为类的类变量分配内存,并设置默认初始值
- 解析符号:将类的二进制数据中的符号引用替换成直接引用
四.类的初始化
类的初始化:虚拟机负责对类进行初始化,主要就是对类变量进行初始化
类变量指定初始化值有两种方式:
- 声明类变量指定初始值
- 使用静态初始化块为类变量指定初始化值
package JVM; public class Test { //声明变量a时指定初始值 static int a = 5; static int b; static int c; { //使用静态初始化块为变量b指定初始值 b=6; } }
JVM初始化一个类包含如下几个步骤:
- 类还未被加载和连接
- 类的直接父类还没有被初始化,则先初始化其直接父类
- 假如类中有初始化语句,则系统依次执行这些初始化语句
当执行第二个步骤时,系统对直接父类的初始化步骤也遵循此步骤1~3
五.类初始化的时机
①创建类的实例
②调用某个类的类方法
③访问某个类或接口的类变量,或为该类变量赋值
④使用反射方式来强制创建某个类或接口对应的java.lang.class对象.例如代码:Class.forName("Person"),这行代码初始化还未初始化的类
⑤初始化某个类的子类
⑥直接用java.exe命令来运行某个主类
⑦特别情形:final型的类变量,如果该类变量的值在编译时就可以确定下来,该类变量相当于"宏变量".java编译器会在编译时直接把这个类变量出现的地方替换成它的值,因此即使程序使用该静态变量,也不会导致该类的初始化
package JVM; 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); //① } }
①处代码当做"宏变量"处理,不会初始化