一、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 相同,因为可能不是同一个类加载器。