内建的JIT编译器、日渐成熟的垃圾收集器(多线程化)、不断改进的运行时环境(JVM Runtime Environment)。
HotSpot VM组件
VM运行时(Runtime)、JIT编译器(JIT Compiler)、内存管理器(Memory Manager)。
HotSpot VM的基本架构
HotSpot VM架构功能强大,可以满足高性能和高扩展性。支持HotSpot VM JIT编译器的动态优化,可以在Java应用运行时制定优化策略,并依据底层系统架构生成高效的本地机器指令。
JIT编译器(Client或Server)和垃圾收集器(Serial、Throughput、CMS或G1)都是可插拔的。
HotSpot VM运行时系统为HotSpot JIT编译器和垃圾收集器提供服务和通用API。还为VM提供启动、线程管理、JNI(Java本地接口)等基本功能。
早期的HotSpot VM(32位JVM)
早期的HotSpot VM是32位JVM,内存地址空间限制为4G,关键的是,实际Java堆的大小还进一步受限于底层操作系统。
Windows系统上HotSpot VM最大可用的Java堆大约为1.5G。
HotSpot VM在最新Linux内核上的最大可用Java堆大约为2.5G到3.0G。在较早内核版本上大约为2G。
Solaris上HotSpot VM最大可用的Java堆大约为3.3G。
实际消耗的最大内存地址空间随给定的Java应用和JVM版本而有所不同。
64位HotSpot VM
增大了Java堆,使得这些系统可以使用更多内存。
64位HotSpot VM——性能损失——压缩指针
HotSpot VM内部Java对象表示(普通对象指针,Ordinary Object Pointers,oops)长度从32位变为64位,导致CPU高速缓存行中可用的普通对象指针变少,从而降低了CPU缓存的效率。缓存效率的降低导致性能比32位JVM降低8%~15%。
最新的Java 6 HotSpot VM添加了压缩指针(Compressed oops,-XX:+UseCompressedOops开启)的新特性,使得64位Java的大尺寸堆和32位JVM的性能可以兼得。
一些Java应用在64位HotSpot VM上使用压缩指针之后,性能要好于32位VM。
64位HotSpot VM——性能提高原因
64位JVM可以使用更多的CPU寄存器,这有助于程序性能的改善。更多的CPU寄存器可以避免寄存器卸载。当活跃状态数超过CPU寄存器数,多出的活跃状态只能存放在内存中时,就会发生寄存器卸载。寄存器卸载时,某些活跃状态必须从CPU寄存器卸载到内存中,因此避免寄存器卸载可以让程序执行得更快。
压缩指针改善性能的原因
通过对齐、偏移量(Offset)将64位指针压缩成32位。
性能提高是因为使用了更小更节省空间的压缩指针而不是完整长度的64位指针,CPU缓存使用率由此得到改善。
HotSpot VM运行时责任
HotSpot VM运行时环境责任:命令行选项解析、VM生命周期管理、类加载、字节码解释、异常处理、同步、线程管理、Java本地接口、VM致命错误处理、C++(非Java)堆管理。
HotSpot VM运行时——命令行选项——使用的对象——启动器和HotSpot VM
命令行选项
- HotSpot VM启动器使用(指定选择哪个JIT编译器、选择何种垃圾收集器)
- 经启动器处理后传给完成启动的HotSpot VM。
HotSpot VM运行时——命令行选项——类别
- 标准选项(Standard Option)
- 非标准选项(Nonstandard Option)
- 非稳定选项(Developer Option)
HotSpot VM运行时——命令行选项——标准选项
Java Virtual Machine Specification要求所有Java虚拟机都必须实现的选项,它们在发行版之间保持稳定,但也可能在后续的发行版中被废除。
HotSpot VM运行时——命令行选项——非标准选项(以-X
为前缀)
不保证、也不强制所有JVM实现都必须支持,它可能未经通知就在Java SDK发行版之间发生更改。
HotSpot VM运行时——命令行选项——非稳定选项(以-XX为前缀)
为了特定需要而对JVM的运行进行校正,并且可能需要有系统配置参数的访问权限。
非稳定选项也可能不经通知就在发行版之间发生变动。
HotSpot VM运行时——命令行选项——布尔类型的选项
命令行选项用于控制HotSpot VM的内部变量,每个变量都有类型和默认值。
对于内部变量为布尔类型的选项来说,只要在HotSpot VM命令行上添加或去掉就可以控制这些变量。
对于带有布尔标记的非稳定选项来说,选项名前的+或-表示true或false,用于开启或关闭特定的HotSpot VM特性或参数。
HotSpot VM运行时——VM生命周期——启动器
HotSpot VM运行时系统负责启动和停止HotSpot VM。
启动HotSpot VM的组件是启动器:
Unix/Linux: java
Windows:java和javaw
JNI接口:JNI_CreateJavaVM启动内嵌的JVM
网络启动器:javaws(Java Web Start),Web浏览器用它来启动applet。
HotSpot VM运行时——VM生命周期——启动器启动HotSpot VM步骤
(1)解析命令行选项
启动器会直接处理一些命令行选项,例如-client
或-server
,它们决定加载那个JIT编译器,其他参数则传给HotSpot VM。
(2)设置堆的大小和JIT编译器。
如果命令行没有明确设置堆的大小和JIT编译器(client或server),启动器则通过自动优化进行设置。自动优化的默认设定因底层系统配置和操作系统而有所不同。
(3)设定环境变量如LD_LIBRARY_PATH。
(4)如果命令行有-jar
选项,启动器则从指定JAR的manifest
中查找Main-Class,否则从命令行读取Main-Class。
(5)使用标准Java本地接口(Java Native Interface,JNI)方法JNI_CreateJavaVM在新创建的线程中创建HotSpot VM。
与后创建的线程相比,初始线程是启动新进程时操作系统内核分配的第一个线程。
不在初始线程中创建HotSpot VM,是为了可以对它进行定制。
(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脱离(Detached)。每次HotSpot VM调用DetachCurrentThread
时,线程数就会减1,因此Java本地接口知道何时可以安全地关闭HotSpot VM,并能确保当时HotSpot VM中没有正在执行的操作,Java栈中也没有激活的Java帧。
JNI_CreateJavaVM详解
(1)确保只有一个线程调用这个方法并且确保只创建一个HotSpot VM实例。
HotSpot VM创建的静态数据结构无法再次初始化,所以一旦初始到达到某个确定点后,进程空间里就只能有一个HotSpot VM。HotSpot VM启动至此已经是无法逆转了。
(2)检查并确保支持当前的JNI版本,初始化垃圾收集日志的输出流。
(3)初始化OS模块,如随机数生成器,当前进程id、高精度计时器、内存页尺寸、保护页。
保护页是不可访问的内存页,用做内存访问区域的边界。例如操作系统常在线程栈顶压入一个保护页以保证引用不会超出栈的边界。
(4)解析传入JNI_CreateJavaVM的命令行选项,保存以备将来使用。
(5)初始化标准的Java系统属性,例如java.version、java.vendor、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全局数据,例如事件日志(Event Log),OS同步原语、perfMemory(性能统计数据内存),以及chunkPool(内存分配器)。
(14)至此,HotSpot VM可以创建线程了。创建出来的Java版main线程被关联到当前操作系统的线程,只不过还没有添加到已知线程列表中。
(15)初始化并激活Java级别的同步。
(16)初始化启动类加载器(Bootclassloader)、代码缓存、解释器、JIT编译器、JNI、系统词典(System Dictionary)及universe(一种必备的全局数据结构集)。
(17)现在,添加Java主线程到已知线程列表中。检查universe是否正常。创建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请求。
网友评论