jvm-java-performance-hotspot

有些东西在<<深入理解JVM>>里没写清楚

HotSpot VM 运行

命令行选项

  • 标准选项
  • 非标准选项
    以-X开头, 不保证, 也不强制所有JVM实现都必须支持, 可能未经通知就在Java SDK发行版之间发生更改.
  • 非稳定选项
    以-XX开头, 通常是为了特定需要面对JVM的运行进行校正,并且可能需要有系统配置参数的访问权限.和非标准选项一样,非稳定选项也可能不经通知就在发行版之间发生变动.

VM生命周期

HotSpot在Java程序运行前以及终止退出时所作的工作

启动HotSpot VM的组件是启动器, HotSpot VM有若干个启动器, 比如Windows上的java和javaw

启动器启动HotSpot VM时会执行一系列操作:

  1. 解析命令行选项
  2. 设置堆大小和JIT编译器
  3. 设定环境变量, 如LD_LIBRARY_PATH和CLASSPATH
  4. 如果命令行有-jar选项,启动器则从指定JAR的manifest中查找Main-Class, 否则从命令行读取Main-Class
  5. 使用标准Java本地接口(JNI)方法JNI_CreateJavaVM在新创建的线程中创建HotSpot VM
    不在初始线程中创建HotSpot VM, 是为了可以对它进行定制, 例如Windows上更改栈的大小.
  6. 一单创建并初始化好HotSpot VM, 就会加载Java Main-Class, 启动器也会从Java Main-Class中得到Java main方法的参数
  7. HotSpot VM通过JNI方法CallStaticVoidMethod调用Java main方法, 并将命令行选项转给它

至此, HotSpot VM开始正式执行命令行指定的Java程序了

一旦Java程序或者Java main方法执行结束, HotSpot VM就必须检查和清理所有程序或者方法执行过程中生成的未经处理异常. 此外, 方法的退出状态和程序的退出状态也必须返回给他们的调用者.调用Java本地接口方法DetachCurrentThread将Java main方法与HotSpot VM脱离.每次HotSpot VM调用DetachCurrentThread时,线程数就会减1, 因此Java本地接口知道何时可以安全地关闭HotSpot VM, 并能确保当时的HotSpot VM中没有正在执行的操作, Java栈中也没有激活的Java帧.

JNI_CreateJavaVM详解

JNI_CreateJavaVM详解 HotSpot VM启动时JNI_CreateJavaVM方法将执行以下一系列操作.
  1. 确保只有一个线程调用这个方法并且确保只创建一个HotSpot VM实例
    因为HotSpot VM创建的静态数据结构无法再次初始化, 所以一旦初始化达到某个确定点后, 进程空间里就只能有一个HotSpot VM
  2. 检查并确保支持当前的JNI版本, 初始化垃圾收集日志输出流
  3. 初始化OS模块
    如随机数生成器(Random Number Generator), 当前进程id(Current Process id), 高精度计数器(High-Resolution Timer), 内存页尺寸(Memory Page Sizes), 保护页(Guard Pages). 保护页是不可访问的内存页, 用作内存访问区域的边界. 例如, 操作系统常在线程栈顶压入一个保护页以保证引用不会超出栈的边界.
  4. 解析传入JNI_CreateJavaVM的命令行选项, 保存以备将来使用
  5. 初始化标准的Java系统属性
    例如java.version, java.vender, os.name等
  6. 初始化支持同步, 栈, 内存和安全点页的模块
  7. 加载libzip, libhpi, libjava及libthread等库
  8. 初始化并设置信号处理器(Signal Handler)
  9. 初始化线程库.
  10. 初始化输出流日志记录器(Logger)
  11. 如果用到Agent库(hprof, jdi), 则初始化并启动
  12. 初始化线程状态(Thread State)和线程本地存储(Thread Local Storage), 它们存储了线程的私有数据
  13. 初始化部分HotSpot VM全局数据
    例如事件日志, OS同步原语, perfMemory(性能统计数据内存), 以及chunkPool(内存分配器)
  14. 至此, HotSpot VM可以创建线程了. 创建出来的Java版main线程被关联到当前操作系统的线程, 只不过还没有添加到一直线程列表中.
  15. 初始化并激活Java级别的同步
  16. 初始化启动类加载器(Bootclassloader), 代码缓存, 解释器, JIT编译器, JNI, 系统词典(System Dictionary)及universe(一种必备的全局数据结构集)
  17. 现在, 添加Java主线程到一只线程列表中,.
    检查univers是否正常. 创建HotSpot VMThread, 它执行HotSpot VM所有的关键功能. 同时发出适当的JVMTI时间, 报告HotSpot VM的当前状态.
  18. 加载和初始化以下Java类:
    java.lang.String, java.lang.System, java.lang.Thread, java.lang.ThreadGroup, java.lang.reflect.Method, java.lang.ref.Finalizer, java.lang.Class以及余下的Java系统类. 此时, HotSpot已经初始化完毕并可使用, 只是功能还不完备
  19. 启动HotSpot VM的信号处理器线程, 初始化JIT编译器并启动HotSpot编译代理线程. 启动HotSpot VM辅助线程(如监控现场和统计抽样器).
    此时, HotSpot VM已功能完备
  20. 最后生成JNIEnv对象返回给调用者, HotSpot则准备响应新的JNI请求.

DestroyJavaVM详解

DestroyJavaVM详解 如果HotSpot VM启动过程中发生错误, 启动器则调用DestroyJavaVM方法关闭HotSpot VM. 如果HotSpot VM启动后的执行过程中发生很严重的错误, 也会调用DestroyJavaVM方法.

DestroyJavaVM按以下步骤停止HotSpot VM:

  1. 一直等待, 直到只有一个非守护的线程(即当前线程)执行
    此时HotSpot仍然可用
  2. 调用java.lang.Shutdown.shutdown(), 它会调用Java上的shutdown钩子方法, 如果finalization-on-exit为true, 则运行Java对象的finalizer.
  3. 运行HotSpot VM上的shutdown钩子(通过JVM_OnExit()注册), 停止以下线程: 性能分析器, 统计数据抽样器, 监控现场及垃圾收集器线程. 发出状态事件通知JVMTI, 然后关闭JVMTI, 停止信号线程
  4. 调用HotSpot的JavaThread::exit()释放JNI处理快, 移除保护页, 并将当前线程从已知线程队列中移除. 从这时起, HotSpot VM就无法执行任何Java代码了
  5. 停止HotSpot VM线程, 将遗留的HotSpot VM线程带到安全点并停止JIT编译器线程
  6. 停止追踪JNI, HotSpot VM及JVMTI屏障
  7. 为那些仍然以本地代码运行的线程设置标记vm exited
  8. 删除当前线程
  9. 删除或移除所有的输入/输出流, 释放PerfMemory(性能统计内存)资源
  10. 最后返回到调用者

VM类加载

HotSpot VM和Java SE类加载库共同负责类加载. HotSpot VM负责解析常量池符号. 这个过程需要加载, 链接, 然后初始化Java类和Java接口. 类加载的最佳时机是在解析Java字节码类文件中常量池符号的时候. JavaAPI如Class.fromName(), ClassLoader.loadClass(), 反射API和JNI_FindClass都可以引发类加载. HotSpot VM自身也可以引起类加载. HotSpot VM启动时, 除了加载许多普通类, 也会加载注入java.lang.Object和java.lang.Thread这样的核心类. 加载类时需要加载它的所有Java超类和所有Java超接口. 此外, 作为链接阶段的一部分, 类文件验证也需要加载一些其他类. 实际上, 加载阶段是HotSpot VM和特定类加载器如java.lang.ClassLoader之间相互协作的过程.

类加载阶段

类加载器委派

启动类加载器

类型安全

Java类或接口的名字为全限名. Java的类型由全限名和类加载器唯一确定. 类加载器定义了类型空间, 这意味着两个不同的类加载器加载的类, 即使全限名相同, 仍然是两个不同的类型. 如果有用户类加载器, HotSpot VM则需要确保类型安全不被恶意的类加载器破坏. 当类A调用B.someMethod()时, HotSpot VM会追踪和检查类加载器约束, 从而确保A和B的类加载器所看到的的someMethod()方法签名(包括参数列表和返回类型)是一致的

HotSpot类元数据

类加载时, HotSpot VM会在永久代创建类的内部表示instanceKlass或arrayKlass. instanceKlass引用了与之对应的java.lang.Class实例, 后者是前者的Java镜像. HotSpot VM内部使用成为klassOop的数据结构访问instanceKlass. 后缀Oop表示普通对象指针, 所以klassOop是引用java.lang.Class的HotSpot内部抽象, 它是指向Klass的普通对象指针

内部类加载数据

类加载过程中, HotSpot VM维护了3张散列表:

  • SystemDictionary
    包含已加载的类, 它将建立类名/类加载器(包括初始类加载器和定义类加载器)与klassOop对象之间的映射. 目前只有在安全点时才能删除SystemDictionary中的元素
  • PlaceholderTable
    包含当前正在加载的类, 它用于检查ClassCircularError, 多线程类加载器并行加载类时也会用到它
  • LoaderConstraintTable
    用于追踪类型安全检查的约束条件

这些散列表都需要加锁以保证访问安全, 这个锁成为SystemDictionary_lock. 通常, HotSpot VM借助类加载器对象锁对加载类的过程进行序列化.

字节码验证

Java是一门类型安全的语言, 官方标准的Java编译器可以生成合法的类文件和类型安全的字节码, 但Java虚拟机无法确保字节码一定是由可信的java编译器产生的, 所以在链接时必须进行字节码验证以保障类型安全. JVM Specification 4.8节规定了字节码验证. 这个规范规定了JVM需要进行字节码的静态和动态约束验证. 如果发现任何冲突, JVM就会抛出VertifyError并且阻止链接该类

类数据共享