预定义类加载器(三种):
启动 (Bootstrap) 类加载器:
是用本地代码实现的类装入器, 它负责将 < Java_Runtime_Home>/lib 下面的类库加载到内存中(比如 rt.jar)
由于引导类加载器涉及到虚拟机本地实现细节, 开发者无法直接获取到启动类加载器的引用, 所以不允许直接通过引用进行操作
扩展扩展 (Extension) 类加载器:
是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的它负责将< Java_Runtime_Home >/lib/ext 或者由系统变量 java.ext.dir 指定位置中的类库加载到内存中开发者可以直接使用标准扩展类加载器
系统 (System) 类加载器:
是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的它负责将系统类路径 (CLASSPATH) 中指定的类库加载到内存中开发者可以直接使用系统类加载器
双亲委派机制:
某个特定的类加载器在接到加载类的请求时, 首先将加载任务委托给父类加载器, 依次递归, 如果父类加载器可以完成类加载任务, 就成功返回; 只有父类加载器无法完成此加载任务时, 才自己去加载这是一种代理方式
双亲委派意义:
系统类防止内存中出现多份同样的字节码
保证 Java 程序安全稳定运行
线程上下文类加载器(特殊):
破坏了双亲委派模型, 可以在执行线程中抛弃双亲委派加载链模式, 使程序可以逆向使用类加载器
类 java.lang.Thread 中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器
如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话, 线程将继承其父线程的上下文类加载器 Java 应用运行的初始线程的上下文类加载器是系统类加载器在线程中运行的代码可以通过此类加载器来加载类和资源
1. 当高层提供了统一接口让低层去实现, 同时又要是在高层加载 (或实例化) 低层的类时, 必须通过线程上下文类加载器来帮助高层的 ClassLoader 找到并加载该类
2. 当使用本类托管类加载, 然而加载本类的 ClassLoader 未知时, 为了隔离不同的调用者, 可以取调用者各自的线程上下文类加载器代为托管
几点问题:
启动 (Bootstrap) 类加载器它用来加载 Java 的核心库, 是用原生代码来实现的, 并不继承自 java.lang.ClassLoader
Java 虚拟机是如何判定两个 Java 类是相同的?
Java 虚拟机不仅要看类的全名是否相同, 还要看加载此类的类加载器是否一样只有两者都相同的情况, 才认为两个类是相同的即便是同样的字节代码, 被不同的类加载器加载之后所得到的类, 也是不同的
- eg:
- public void testClassIdentity() {
- String classDataRootPath = "C:\\workspace\\Classloader\\classData";
- FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);
- FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);
- String className = "com.example.Sample";
- try {
- Class<?> class1 = fscl1.loadClass(className);
- Object obj1 = class1.newInstance();
- Class<?> class2 = fscl2.loadClass(className);
- Object obj2 = class2.newInstance();
- Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class);
- setSampleMethod.invoke(obj1, obj2);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
代码中使用了类 FileSystemClassLoader 的两个不同实例来分别加载类 com.example.Sample, 得到了两个不同的 java.lang.Class 的实例, 接着通过 newInstance()方法分别生成了两个类的对象 obj1 和 obj2, 最后通过 Java 的反射 API 在对象 obj1 上调用方法 setSample, 试图把对象 obj2 赋值给 obj1 内部的 instance 对象
运行结果
- java.lang.reflect.InvocationTargetException
- at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
- at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
- at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
- at java.lang.reflect.Method.invoke(Method.java:597)
- at classloader.ClassIdentity.testClassIdentity(ClassIdentity.java:26)
- at classloader.ClassIdentity.main(ClassIdentity.java:9)
- Caused by: java.lang.ClassCastException: com.example.Sample
- cannot be cast to com.example.Sample
- at com.example.Sample.setSample(Sample.java:7)
- ... 6 more
给出的运行结果可以看到, 运行时抛出了 java.lang.ClassCastException 异常虽然两个对象 obj1 和 obj2 的类的名字相同, 但是这两个类是由不同的类加载器实例来加载的, 因此不被 Java 虚拟机认为是相同的
Java 类的生命周期:
类从被加载到虚拟机内存中开始, 到卸载出内存为止, 它的整个生命周期包括: 加载 (Loading) 验证 (Verification) 准备 (Preparation) 解析 (Resolution) 初始化 (Initialization) 使用 (Using) 和卸载(Unloading)7 个阶段其中准备验证解析 3 个部分统称为连接(Linking)
加载(Loading):
就是将源文件的 class 文件找到类的信息将其加载到方法区中,
然后在堆区中实例化一个 java.lang.Class 对象, 作为方法区中这个类的信息的入口
连接(Linking):
验证: 确定该类是否符合 java 语言的规范, 有没有属性和行为的重复, 继承是否合理, 总之, 就是保证 jvm 能够执行
准备: 主要做的就是为由 static 修饰的成员变量分配内存, 并设置默认的初始值
(1. 八种基本数据类型默认的初始值是 0
2. 引用类型默认的初始值是 null
3. 有 static final 修饰的会直接赋值, 例如: static final int x=10; 则默认就是 10.)
解析: 这一阶段的任务就是把常量池中的符号引用转换为直接引用, 说白了就是 jvm 会将所有的类或接口名字段名方法名转换为具体的内存地址
初始化(Initialization)
这个阶段就是将静态变量 (类变量) 赋值的过程, 即只有 static 修饰的才能被初始化, 执行的顺序就是:
父类静态域或着静态代码块, 然后是子类静态域或者子类静态代码块
使用(Using)
在类的使用过程中依然存在三步: 对象实例化垃圾收集对象终结
卸载(Unloading)
类的生命周期走到了最后一步, 程序中不再有该类的引用, 该类也就会被 JVM 执行垃圾回收, 从此生命结束
参考文章:
- https://www.ibm.com/developerworks/cn/java/j-lo-classloader/
- http://blog.csdn.net/yangcheng33/article/details/52631940
来源: http://www.bubuko.com/infodetail-2524845.html