笔记:Java面向对象编程 第10章 类的生命周期

一、JVM 的生命周期

JVM 结束的时机:

程序结束;程序因为异常或错误终止;System.exit();操作系统终止JVM


二、类生命周期的开始

步骤:

(1)、加载 

(2)、连接:包括验证(即确保其正确性)、准备(即为静态变量分配内存、初始化默认值)、解析(将符号引用转换为直接引用)

(3)、初始化:为静态变量赋予初始值


2.1  类的加载

将class文件的二进制数据读入方法区,在堆区创建一个Class对象。

注意:Class的数据结构在方法区,Class对象在堆区


类加载器在加载过程中,如果遇到class缺失或损坏,不会马上报错而是等到首次主动使用该class时候才报错,如果一直不使用该class就不报错。


2.2 类的验证

验证的内容:检查结构、语义、字节码、二进制兼容


2.3 类的准备

为静态变量分配内存,赋予初值。例子:

 

private static int a=1;
private static long b;
int c=3;

static
{
    b=3;
}


此时为a分配4个字节的内存空间,赋默认值0,为b分配8个字节空间,赋默认值0;而c是实例变量,此时不会分配空间

 

 

2.4、类的解析

符号引用解析为直接引用


2.5、类的初始化

在静态变量声明处或者静态代码块中初始化。

注意:(1)、初始化一个类的时候,会先初始化其父类,但不会初始化其接口。初始化一个接口,不会初始化其父接口。

(2)、声明时候不会初始化,使用到的时候才初始化

 

MyObject  obj;   // 不会初始化MyObject
obj  = new MyObject :  // 此时才初始化MyObject

 

 

2.6、类初始化的时机


主动使用的时候初始化:访问静态变量;访问静态方法;创建类的实例(包括new语句、反射、clone、反序列化);调用反射方法;初始化子类;JVM启动类。


不会初始化的情况:

(1)、使用编译时常量不会初始化

对于final static 变量,如果编译时候就能算出其值,则为编译时常量。

 

public final static int a=2*3+4;                 //编译时常量,首次使用不会导致类的初始化
public final static int b=(int)Math.random();    //不是编译时时常量,首次使用会导致类的初始化

 


(2)、接口:使用类不会初始化其实现的接口,使用子接口不会初始化其父接口


interface IAction {}

class Action implements IAction {}

interface IAction2 extends IAction{}


调用 Action 、IAction2 的静态变量、静态方法都不会初始化 IAction

 


(3)、程序访问的静态变量或静态方法的确在本类时候,才会初始化,如果在父类,则只初始化父类

 

class Father
{
      public static int a=9;
      public static int hehe() {}
}

class Son extents Father
{
}


如果调用 Son.a 和 Son.hehe(), 不会初始化Son类,只会初始化Father 类

 


(4)、ClassLoader.loadClass() 是加载,不会初始化;Class.forName() 是初始化

Class<?>  clazz = myClassLoader.loadClass("className")   //不会初始化,

Class<?>  clazz = Class.forName("className")    // 会初始化



三、类加载器

1、父亲委托机制

类加载器classLorderA加载类 时候,会先委托父加载器classLorderB去加载,父加载器classLorderB 又去委托其父加载器classLorderC 。。。直到根加载器,根加载器一层一层往下选,找到某个能加载的classLoader,则加载之。

加载器委托关系:



Bootstrap加载器  null:(加载JDK核心类库)

Extension加载器 sun.misc.Launcher$ExtClassLoader:(加载 lib/ext/ 下类库)

System 加载器 sun.misc.Launcher$AppClassLoader:(加载classpath中其他类库)



注意:加载器之间的父子关系是包装关系,而不是继承关系,代码如下:

 

ClassLoader fatherLoader = new MyClassLoader();
ClassLoader sonrLoader = new ClassLoader(fatherLoader);

 


父亲委托机制的好处:

(1)、防止不可靠的代码替换由父加载器加载的可靠代码,例子:java.lang.Object 总是由Bootstrap加载器加载,用户定义的加载器不能加载 java.lang.Object  类来进行不安全的操作。

(2)、同一个类文件,由两个不同的类加载器分别加载为实例classA、classB,此时classA 和 classB 仍然被看做不同的class,因为其类加载器不一样。

(3)、假如用户定义一个类java.lang.MyObject,企图用java.lang,MyObject 去访问JDK核心包java.lang.*下面的包可见的成员(类、变量、方法),这是不可能成功的。因为核心类java.lang.* 由Bootstrap加载器加载,而java.lang.MyObject 由System加载器或者用户自定义加载器加载,加载器不同则属于不同的运行时包,只有属于同一运行时包的成员才可以相互访问包可见成员。

 

2、创建自定义加载器

继承 ClassLoader 类,覆盖 findClass(String name) 方法。

类加载器之间的委托关系是包装关系,而不是继承关系,代码如下:

 

ClassLoader fatherLoader = new MyClassLoader();
ClassLoader sonrLoader = new ClassLoader(fatherLoader);

 


多个加载器之间的命名空间关系如下:

(1)、同一命名空间之间的类相互可见

(2)、子加载器的加载的类可以看到父加载器的加载的类,父加载器的加载的类不能看到子加载器的加载的类

(3)、若两个类加载器之间没有直接或间接的父子关系,则它们各自加载的类相互不可见

(4)、两个不同命名空间的类相互不可见的时候,可以通过反射访问对方实例的属性和方法。


3、java.net.URLClassLoader 

java.net.URLClassLoader 属于用户加载器,而不是System加载器,虽然其包含在核心包中,通过URL从本地或远程加载类


四、类的卸载

Bootstrap、Extension、System加载器所加载的类,永远不会被卸载,因为JVM始终引用这些类加载器,而这些类加载器又始终引用其加载的Class对象

用户自定义加载器所加载的类,在Class对象不再被引用时,会被卸载。注意:

(1)、一个类的实例总是引用代表这个类的Class对象,如instance.getClass() 总能返回其Class 对象。故要卸载Class,其所有的instance 必须也不再被引用。

(2)、类加载内部有一个集合保全了其所加载的所有Class的引用,故要卸载Class,其类加载器也必须被卸载。

(3)、以下三个变量c1、c2、c3是同一个Class 对象

 

Class<?>  c1 = mypackage.MyClass.class;
Class<?>  c2 = (new mypackage.MyClass()).getClass();
Class<?>  c3 = Class.forName("mypackage.MyClass");

 

 

Class<?> c4 = myClassLoader,loadClass("mypackage.MyClass") 


则不一定与c1、c2、c3 相同,因为可能不是同一个类加载器

 








 

posted @ 2015-01-18 22:45  lihui1625  阅读(213)  评论(0编辑  收藏  举报