java类加载机制

来源:互联网 发布:网络连环夺宝 编辑:程序博客网 时间:2024/05/17 22:05

类的加载、连接、初始化



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

 JVM和类



当使用java命令运行某个java程序的时候,该命令将会启动一个java虚拟机进程,不管该java程序有多么复杂,该程序内部启动了多少个线程,他们都处在java虚拟机进程里,同一个JVM的所有线程、所有的变量都处于同一个进程里,他们都使用该JVM进程的内存区。当系统出现以下情况时,JVM进程将会被终止。
  • 程序运行到最后正常结束。
  • 程序运行到使用System.exit()或Runtime.getRuntime().exit()代码处结束程序
  • 程序运行过程中遇到未捕获的异常或错误而结束。
  • 程序所在的平台强制结束了JVM进程
  • ### 类的加载
  • 当程序主动使用某个类时,如果该类还未被加载到内存,则系统会通过加载、连接、初始化三个步骤对该类进行初始化。如果没有意外,JVM将会连续完成这三个步骤,所以也可以把这三个步骤统称为类加载或者类初始化

类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说,当程序使用任何类时,系统都会为之建立一个java.lang.Class对象。
类的加载是由类加载完成,类加载器通常是由JVM提供,JVM提供的这些类加载器通常称为系统类加载器,除此之外,我们仍然可以通过继承基类ClassLoader来创建自己的类加载器。

  • 通过使用不同的类加载器,可以通过不同的来源来加载类的二进制数据。
  • 从本地文件系统加载class文件
  • 从jar中加载class文件
  • 通过网络加载class文件
  • 把一个java源文件动态编译,并执行加载
  • 类加载通常无需等到“首次使用”该类加载该类,java虚拟机规范允许系统预先加某些类。

 类的连接

当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接可分为如下几个阶段。

验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。 

准备:类的准备阶段则负责为类的类变量分配内存,并设置默认初始值。

解析:将类的二进制数据中的符号引用替换成直接引用。

类的初始化

在类的初始化阶段,虚拟机负责对类进行初始化,主要是对类变量进行初始化。
在java类中对类变量初始化的方式有两种:

  1.  声明类变量时指定初始值。
  2. 使用静态初始化块为类变量指定初始值。

```
public class Test {
// 声明变量a时指定初始值
static int a = 5;
static int b;
static int c;
static {
// 使用静态初始化块为变量b指定初始值
b = 6;
}
...
}
```
对于上面代码,程序为类变量a、b都显式的指定了初始值,所以这两个类变量的值为别为5、6
但是类变量c没有指定初始值,他将使用默认的初始值0。
声明变量指定初始化值和静态初始化块都将被当成类的初始化语句,JVM会按照这些语句在程序中的排列顺序依次执行他们。


```
public class Test {


static {
// 使用静态初始化块为变量b指定初始值
b = 6;
System.out.println("-----------");
}
// 声明变量a、b时指定初始值
static int a = 5;
static int b = 9;
static int c;


public static void main(String[] args) {
System.out.println(Test.b);
}
}
```




上面代码经过类加载-->类连接之后(类连接的准备阶段为类变量分配内存空间,设置默认初始值)
那么a、b、c的初始值都为0,然后进行类的初始化阶段,主要是类变量的初始化,按照上面所说的初始化顺序,先执行静态代码块,为b赋值6,然后接着执行定义时候的指定初始化,为a赋值5,又为b赋值9,这时候b已经变为9了,c当然还是0.
JVM初始化一个类包含如下几个步骤:
1. 假如这个类还没有加载和连接,则程序先加载和连接该类。
2. 假如该类的直接父类还没有初始化,则先初始化其直接父类
3. 假如该类中有初始化语句,则系统依次执行这些初始化语句。


注意:系统对直接父类的初始化步骤也遵循步骤1——3。所以最终JVM最先初始化的总是Object类
当程序主动使用任何一个类时,系统会保证该类以及所有的父类都会被初始化。
### 类初始化时机
- 创建类的实例(new 操作符、反射、反序列化)
- 调用某个类的类方法(静态方法)
- 访问某个类或接口的类变量或者为该类变量赋值
- 使用反射的方式来强制创建某个类或者接口对应的java.lang.Class对象,例如(Class.forName("Person")),如果系统还未初始化Person类,这段代码将会导致Person类被初始化,并返回Person类的java.lang.Class对象
- 初始化某个类的子类时,该子类的所有父类都会被初始化
- 直接使用java.exe命令来运行某个主类时,程序会先初始化该主类


注意:对于一个final型的类变量,如果该值在编译的时候就能确定下来,那么这个类变量也就相当于“宏变量”,java编译器会在编译的时候直接把这个类变量出现的地方替换成他的值,即使程序中使用该静态变量,也不会导致该类的初始化。例如下面代码


```
class MyTest{
static{
System.out.println("静态初始化块。。。");
}
//使用一个字符串直接量为static final 的类变量赋值
static final String compileConstant="Android";
}
public class Test {


public static void main(String[] args) {
//访问输出MyTest中的compileConstant类变量
System.out.println(MyTest.compileConstant);
}
}
```
程序的输出结果:Android,所以可见MyTest并没有进行初始化
所以总结出:当某个类变量(静态变量)使用了final修饰,而且它的值在编译的时候就可以确定下来
那么程序其他地方使用该变量时,实际上并没有使用该类变量,而是相当于使用了一个常量.

0 0
原创粉丝点击