0%

classloader

1. 3 种默认 ClassLoader

JVM 中的3 种默认 ClassLoader:

  • 启动类加载器 Bootstrap ClassLoader
    它是用 C++ 语言写的.
    它是在 Java 虚拟机启动后初始化的.
    它没有父类加载器.
    它负责加载 $JAVA_HOME/jre/lib, -Xbootclasspath 参数指定的路径以及 $JAVA_HOME/jre/classes 中的类, 并且仅按文件名识别, 如 rt.jar, 名字不符合的了库即使防在 lib 目录下, 也不会被加载.
  • 扩展类加载器 Extension ClassLoader
    它的父类加载器为启动类加载器.
    它的具体为 sun.misc.Launcher$ExtClassLoader.
    它加载 $JAVA_HOME/jre/lib/ext, 此路径下的所有 classes 目录以及java.ext.dirs 系统变量指定的路径中类库.
  • 应用程序类加载器 Application ClassLoader
    它的父类加载器为扩展类加载器.
    它的具体为 sun.misc.Launcher$AppClassLoader.
    ClassLoader.getSystemClassLoader() 方法返回的正是 AppClassLoader, 所以一般也称它为系统类加载器.
    负责加载 classpath 所指定的位置的类或者是 jar 文档, 它也是 Java 程序默认的类加载器.

2. 双亲委派模型

结构: 除了顶层的启动类加载器之外, 其余的类加载器都应该有自己的父类加载器. 这里的父子关系不是用继承的方式实现的, 而是使用组合的方式实现.

工作过程: 如果一个类加载器收到了类加载的请求, 它首先不会自己区尝试加载这个类, 而是把这个请求委派给父类加载器去完成. 每一个层次的类加载器都是如此, 因此所有的加载请求最终都应该传送到顶层的启动类加载器中, 只有当父类加载器反馈自己无法完成这个加载请求 (它的搜索范围内没有找到所需的类) 时, 子加载器才会尝试自己去加载.

3. Java 类的生命周期

类从被加载到虚拟机内存开始, 到卸载出内存位置, 它的生命周期包括:

  • 加载 Loading
  • 验证 Verification
  • 准备 Preparation
  • 解析 Resolution
  • 初始化 Initialization
  • 使用 Using
  • 卸载 Unloading

其中加载, 验证, 准备, 初始化, 卸载这5个阶段的顺序是确定的.

类在什么情况下进行加载: 虚拟机对类的加载时机并没有明确的规定, 是由具体的虚拟机具体实现来自由把握的, 但是对于初始化阶段, 虚拟机规范严格规定了有且仅有以下5种情况下必须立即进行初始化 (加载, 验证, 准备必须在初始化之前) .

  1. 遇到 new (使用 new 关键字创建实例对象的时候) , getstatic (读取一个静态变量, 被final修饰, 已在编译把结果放入常量池的静态变量除外) , putstatic (设置一个静态变量, 被final修饰, 已在编译把结果放入常量池的静态变量除外) 或invokestatic (调用一个类的静态方法) 这四条字节码指令的时候, 如果类没有进行初始化, 则需要先触发其初始化.
  2. 使用 java.lang.reflect 方法对类进行反射调用的时候, 如果类没有进行初始化, 则需要先触发器初始化
  3. 初始化一个类时, 发现其父类没有进行初始化, 则需要先触发父类的初始化.
  4. 虚拟机启动的时候, 用户需要指定一个执行的主类 (包含main方法的类) , 虚拟机会先对初始化这个主类
  5. 当使用 JDK 1.7 的动态语言支持时, 如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法的句柄, 并且这个方法句柄所对应的类没有进行过初始化, 则需要先触发其初始化. (使用场景未知)

这 5 种方式也被称为主动引用, 除此之外, 所有引用类的方式都不会触发初始化, 称为被动引用.

4. JIT

Just-In-Time, 即时编译器技术.

这种技术可以把程序全部或部分翻译成本地机器码(这本来是Java虚拟机的工作), 程序运行速度因此得以提升.
当需要装载某个类(通常是在为该类创建第一个对象)时, 编译器会先找到其.class文件, 然后将该类的字节码装入内存.
此时有两种方案可供选择.
一种就是让即时编译器编译所有代码.
但这种做法有两个缺陷:这种加载动作散落在整个程序生命周期内, 累加起来要花更多时间;并且会增加可执行代码的长度(字节码要比即时编译器展开后的本地机器码小很多), 这将导致页面调度, 从而降低程序速度.
另一种做法称为惰性评估(lazy evaluation), 意思是即时编译器只在必要的时候才编译代码.
这样, 从不会被执行的代码也许就压根不会被JIT编译.
新版JDK中Java HotSpot技术就采用了类似的方法, 代码每次被执行的时候都会做一些优化, 所以执行的次数越多, 它的速度就越快.

5. 多个 ClassLoader 的作用和意义

  • 类隔离
    如果想让同一个类 (类的包路径相同) 的不同版本共存, 那么这些类必须由不同的类加载器进行加载.
    比如 Tomcat 中, 为了各个应用之间的文件互不影响.
  • 安全考虑
    避免类被恶意替换
  • 热加载
    通过奇幻 ClassLoader 实例的方式实现热加载. 比如 Tomat 中, 可以不重启服务, 发布和删除服务上的应用.

6. Resource