ClassLoader
.
JVM结束生命周期
1. System.exit(0) 正常退出
System.exit(-1) 非0异常退出
2. 正常运行结束
3. 发现异常或错误退出
4. 操作系统退出
class二进制文件的加载过程
1. class二进制文件加载到内存
2. 连接
A. 验证文件的正确性,防止恶意修改
B. 类的静态成员设为默认值,即int(0) , boolean(false), Object(null)
C. 解析,即把类中的符号引用转换成直接的引用(指针,内存地址)
3. 静态初始化: 静态变量初始化为正确的值(即静态初始化代码段里的语句)
主动使用
1. Test t = new Test() ;
2. 使用了静态变量: Test.A ;
3. 使用了静态方法 Test.do() ;
4. 反射出一个对象 Object t = Class.forName(“Test”);
5. New 了Test的子类
6. 程序的启动类
除此6种处,其它的都是被动使用,被动使用不会导致类的重新加载,也就不会导致类的初始化,即不会执行
static { ..... }
类的加载
指的是将类的class文件中的二进制数据读到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来描述该类的数据结构(如方法Method, 属性Field)
1. 本地class文件加载
2. Zip,jar文件中加载
3. 网络加载(java.lang.URLClassLoader)
4. 专用数据库中加载
5. Java源文件动态加载
类加载器
系统自带的ClassLoader
A. 根类加载器 Bootstrap (C++实现)
B. 扩展类加载器 Extension (java实现)
C. 系统(应用)类加载器 (java实现, 如AppClassLoader)
用户自定义的ClassLoader
继承java.lang.ClassLoader类,重写findClass方法
如:
public class MyClassLoader extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] buf = getClassData(""); return this.defineClass(name, buf, 0,buf.length); } //从某处把.class文件的内容读取成为一个byte[] private byte[] getClassData(String name){ return new byte[100]; } }
接受ClassLoader作为参数的例子:
invocationHandler. newProxyInstance( classloader, interfaces................);
JVM加载类加载器
类加载器不需要某个类被“首次主动使用”的时候加载它。JVM允许类加载器在预料到某个类将在被使用时加载。
当加载出现错误时,先隐藏,直到主动使用该类的时候才报告。如果一直不主动使用该类,则错误一直不报告.
类初始化步骤
1. 如果类还没加载和连接,则先加载和连接
2. 如果存在父类,则先加载父类
3. 如果有静态初始化,则从上到下初始化
4. 注意事项:
a) 编译时常量(即编译的时候就能确定它的值的常量)
如public static final int A =1,主动使用时不会对类进行初始化
b) 运行时的常量(即在运行的时候才能确定它的值的常量)
如public static final int A = new Ramdon().nextInt(10);主动使用时会对类进行初始化
c) 初始化类时,不会初始化它所实现的接口。初始化接口时,不会初始它的父接口,当首次使用接口的静态变量时,才会初始化
d) 只有当访问的静态变量(方法)确定在当前类(接口)中定义时,才可以认为是对该类或接口的主动使用
即 Test.A 或 Test.do()时,只有当A和do()是在Test类定义的,才算是主动使用
classLoader. loadClass(“java.lang.String”) 这样加载的类不是主动使用
父亲委托机制
总是委托父加载器去加载一个类,能加载则加载,不能加载则用子类加载。父子加载器之间不一定是继承关系,很多都是组合关系。每个加载器有且只有一个父加载器。
子加载器加载的类能看到父加载器所加载的类,反之则不行
运行时包:类名,包名相同,并且加载器也要相同。同一个运行包里的类才能随意访问,但通过反射可突破这一限制