7-类的加载、连接、初始化

来源:互联网 发布:衣服软件 编辑:程序博客网 时间:2024/06/16 04:22

Java虚拟机的生命周期

   在下面的集中情况下会结束java虚拟机

1、 执行了System.exit()

2、 程序正常结束

3、 程序在运行过程中遇到的异常或错误而异常终止

4、 由于操作系统出现错误

 

 

类的加载、连接、和初始化

加载:查找并加载的类二进制数据(加载到内存)

将类的class文件中的二进制数据读入到内存中,将其运行时数据区的方法区,然后在堆内存上创建一个java.lang.Class对象,用来封装在方法区的数据结构,且对应的class对象只有一个,是反射的入口

加载class文件的方式:

A、 从本地系统直接加载;

B、 从网络下载.class文件

C、 从zip,jar等归档文件中加载.class文件

D、 从专有数据库中提取.class文件

E、 将java源文件动态编译成.class文件

类加载的最终产物是位于堆区的class对象,class对象封装了类在方法区的数据结构,并向程序员提供了访问方法区内的数据结构的接口(通过反射)

连接:

A、验证:确保被加载的类的正确性(用户自己生成class文件);

B、准备:为类的静态变量分配内存、并将其初始化为默认值;

C、解析:把类中的符号引用转换为直接引用;

class Worker{private Car car;public void gotoWork() {car.run();}}


在Worker类中的二进制数据中包含了一个随Car类的run()方法的符号引用,它由run()方法的全名和相关描述符组成。在解析阶段,Java会把这个符号引用替换为一个指针,该指针指向Car类的run()方法在方法区的内存位置,这个指针就是直接引用。

 

初始化

为类的静态变量赋予正确的初始值

 

图一:类的加载、连接和初始化

 

 

Java程序对类的使用

Java程序对类的使用方式分两种:主动使用和被动使用

所有的java虚拟机实现都必须在每个类火灾借口被java程序“首次主动使用”时才初始化他们。

主动使用:

1、 创建类的实例;

2、 访问某个类或借口的静态变量,或者对静态变量赋值;

3、 调用类的静态方法;

4、 反射(如Class.forName(“edu.swust.Test”));

5、 初始化一个子类;

6、 Java虚拟机启动时被标明启动类的类(程序入口类);

其余全是被动使用  不会导致类的初始化

 

 

类初始化步骤:

1、 假如这个类还没有被加载的连接,那就先进行加载和连接;

2、 假如类存在直接的父类,并且这个父类还没有被初始化,那么就先初始化这个直接的父类,当一个父类被初始化,但是一个父类被   初始化不会导致子类的初始化。

3、 假如类中存在初始化语句,那么就执行这些初始化语句。

 

示例:

class Parent1{public static int number = 15;static {    System.out.println("parent1");}}class Son1 extends Parent1{public static int key = 5;    static {System.out.println("son1");}}public class Test1 {    static {    System.out.println("main");    }        public static void main(String[] args) {@SuppressWarnings("unused")Parent1 parent1;System.out.println("----------------------");parent1 = new Parent1();System.out.println(Parent1.number);System.out.println(Son1.key);/** * main         * ----------------------         * parent1         * 15         * son1         * 5 */}}


只有当程序访问的静态方法或者静态变量确实在当前类或者当前接口中定义时,才认为是对类或者接口的主动使用

示例:

class Parent2{public static int number = 15;public Parent2 parent2 = new Parent2(1);static {    System.out.println("parent");}    public Parent2() {        System.out.println("constructor");        parent2 = null;}        public Parent2(int key) {        System.out.println("constructor");        parent2 = null;}}class Son2 extends Parent2{public static int key = 5;    static {System.out.println("son");}public static void doSomething() {System.out.println("doSomething");}}public class Test2 {    public static void main(String[] args) {System.out.println(Son2.number);Son2.doSomething();new Parent2();/** * parent         * 15         * son         * doSomething */}}


Main方法中虽然访问了Son2.number但是number是Parent2中的静态变量,所以程序只会初始化Parent2,而不会初始化Son2,只有在调用了Son2的静态方法时,Son2才被初始化。

 

 

当java虚拟机初始化一个类时,要求它所有的父类都已经被初始化,但是这条规则不适用于接口:

1、 在初始化一个类时,并不会先去初始化它所有的实现接口;

2、 在初始化一个接口时,并不会初始化它的父接口;

因此,一个父接口并不会因为它的子接口或者实现类被初始化而初始化,只有当程序首次使用特定接口的静态变量时,才会导致接口的初始化。

 

 

类加载器分两种

         Java虚拟机自带的加载器

                  根加载器(Bootstrap)  C++编写 未开源

                  扩展类加载器(Extension) java编写

                  系统类加载器(System) 也叫应用类加载器 java编写

         用户自定义类加载器

                  继承java.lang.ClassLoader,用户自定义加载方式

 

类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载过程中遇到了.class文件缺失或者存在错误,类加载器必须在串行首次主动使用该类时才报告错误(LinkageError错误);换句话说:如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误

 

类的验证:

类被加载后,就进入连接阶段,连接就是把已经读入到内存的类的二进制数据合并(调用关系)到虚拟机的运行时环境中去。

         类验证的内容(保证安全性):

1、 类文件的结构检查;

2、 语义检查;

3、 字节码验证;

4、 二进制兼容性验证;

 

综合例子分析

public class MyTest {    public static void main(String[] args) {        Singleton singleton = Singleton.getInstance();        System.out.println(singleton);    }}class Singleton{//private static Singleton singleton = new Singleton(); //counter1 = 1   counter2 = 0    private static int counter1;    private static int counter2 = 0;    private static Singleton singleton = new Singleton(); //counter1 = 1   counter2 = 1    public Singleton() {        ++counter1;        ++counter2;    }    public static Singleton getInstance() {        return singleton;    }        @Override    public String toString() {        return "counter1 = " + counter1 + "   counter2 = " + counter2;    }}


对于这种情况

         privatestatic int counter1;

         privatestatic int counter2 = 0;

         privatestatic Singleton singleton = new Singleton();

在连接的准备阶段:为类的静态变量分配内存、并将其初始化为默认值;

     counter1=0  

  counter2 = 0(这个不是counter2 = 0赋值,而是初始值)

  singleton=null  这个是默认值

   初始化:

           counter1是没有初始化 值为0

           counter2 = 0 人为赋值过程 counter2 = 0

           singleton = new Singleton(); 进行实例化 在new的过程中 调用了构造函数counter1+1,且counter2+1 

           所以counter1 = 1   counter2 = 1

 

对于这种情况

         privatestatic Singleton singleton = new Singleton();

         privatestatic int counter1;

         privatestatic int counter2 = 0;

   

在连接的准备阶段:为类的静态变量分配内存、并将其初始化为默认值;

singleton=null  这个是默认值

counter1=0  

counter2 = 0(这个不是counter2 = 0赋值,而是初始值)

   初始化:

singleton = new Singleton(); 进行实例化 在new的过程中 调用了构造函数counter1+1,且counter2+1  counter1 = 1  counter2 = 1

           counter1是没有初始化 值为1

           counter2 = 0 人为赋值过程 counter2 = 0

          所以counter1 = 1   counter2 = 0