HotSpot VM基本架构,在这个图中,包括了三个部分,VM运行时(Runtime),JIT编译器(JIT Compiler),内存管理器(Memory Manager),其中VM运行时是最基础的,垃圾收集和JIT编译器都在其之上
HotSpot VM启动的一些步骤
- 解析命令行选项
- 设置堆大小和JIT编译器
- 设置环境变量如LD_LIBRAY_PATH和CLASSPAHT
- 如命令行有-jar,则执行JAR的manifest中的Main-Class
- 使用标准Java本地接口函数JNI_CreateJavaVM在新创建的线程中创建HotSpotVM
- 一旦创建并初始化好HotSpotVM,就会加载Java Main-Class
- HotSpotVM通过JNI函数CallStaticVoidMethod调用Java的main方法
至此,HotSpotVM开始正式执行命令行指定的Java程序了
JNI_CreateJavaVM执行步骤
- 确保只有一个线程调用这个方法,并确保只创建一个HotSpotVM实例,因为HotSpotVM创建的静态数据结构无法再次初始化
- 检查并去报支持当前的JNI版本,初始化垃圾收集日志的输出流
- 初始化OS模块,如随机数,当前进程ID,内存页尺寸
- 解析传入JNI_CreateJavaVM的命令行选项
- 初始化标志的Java系统属性
- 初始化支持同步,栈,内存和安全点页的模块
- 加载libzip,libhpi,libjava,libthread等
- 初始化并设置信号处理器
- 初始化线程库
- 初始化输出流日志记录器
- 如果用到Agent库,初始化并启动
- 初始化线程状态,本地存储ThreadLocalStorage
- 初始化部分HotSpotVM全局数据,如事件日志,OS同步原语,perfMemory,chunkPool等
- 至此HotSpotVM可以创建线程了,创建出来的Java版main线程被关联到当前操作系统的线程,
- 初始化并激活Java级别的同步
- 初始化类加载器,代码缓存,解释器,JIT编译器,JNI,系统词典
- 添加Java主线程到已知线程列表中
- 加载和初始化一些核心类,如java.lang中的类
- 启动HotSportVM的信号处理器线程,初始化JIT编译器并启动HotSpot编译代理线程
- 生产JNIEnv对象返回给调用者,HotSpot则准备响应新的JNI请求
VM内部线程
- VM线程,C++单列线程,负责执行VM操作
- 周期任务线程,模拟计时器中断使得在HotSpotVM内可以执行周期性操作
- 垃圾收集线程,
- JIT编译器线程,这些线程进行运行时编译,将字节码编译成机器码
- 信号分发线程,等待进程发来的信号并将他们发给Java的信号处理函数
HotSpotVM使用Arena进行内存分配,是常规malloc/free之上的一层
分为3个全局的ChunkPool,
1K内存的分配从小的ChunkPool中分配,10K的从中等分配,其余从大的分配
JVM分代收集
新生带,老年代,持久带
GC免费的分析工具 GCHisto
GC区域划分
yong GC
old GC
-X 表示非标准参数
-XX 表示非稳定参数
通知JVM为每个优化或逆优化的函数输出一条日志
-XX:+PrintCompilation
System.currentTimeMillis()有精度问题,虽然返回的是毫秒级别的,但取决于操作系统实现
打印内联结果
-XX:+PrintInlining
-XX:MaxInlineSize=N
逆优化
动态Java追踪工具BTrace
JVM的监控范围包括垃圾收集,JIT编译,类加载
影响垃圾收集的三个主要属性
- 吞吐量,是评价垃圾收集器能力的重要指标之一,指不考虑垃圾收集引起的停顿时间或内存消耗,垃圾收集器能支撑应用程序达到的最高性能指标
- 延迟, 也是评价垃圾收集器能力的重要指标,度量标准是缩短由于垃圾收集引起的停顿时间,或完全消除因垃圾收集锁引起的
- 停顿,避免应用程序运行时发生抖动
内存占用,垃圾收集器流畅运行所需的内存数量
这其中任何一个属性性能的提高几乎都是以另一个或者两个属性性能的损失作为代价的
原则
- 每次MinorGC都尽可能多的手机垃圾对象,可以减少应用程序发生FullGC的频率,称为MinorGC回收原则
- Java堆空间越大,垃圾收集效果越好,应用程序运行也越流畅,称之为GC内存最大化原则
- 吞吐量,延迟,内存占用三选二,称之为GC调优3选2原则
获得应用程序由于执行VM安全点操作而阻塞的时间以及两个安全点操作之间应用程序运行的时间
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintGCApplicationConcurrentTime
安全点操作使JVM进入到一种状态,所有的Java应用线程都被组盎司,执行本地代码的线程都被禁止返回VM执行Java代码
-XX:+PrintSafepointStatistics 可将垃圾收集的安全点与其他的安全点区分开来
Java堆大小设置原则
- Java堆的初始值 -Xms和-Xmx设置为老年代活跃数据大小的3-4倍
- 持久代的初始值 -XX:PermSize和-XX:MaxPermSize 是永久代活跃数据1.2--1.5倍
- 老年代的大小是 活跃数据大小的2-3倍
通过长时间观察GC日志,就可以得出比较真实的活跃数据大小,通过活跃数据大小设置以上参
新生代的调优准则
- 老年代空间大小不应小于活跃数据大小的1.5倍
- 新生代空间至少为Java堆大小的10%
- 增大Java堆大小时,不能超过JVM可用的物理内存数
计算每次MinorGC后晋升的数据
比如每次MinorGC后晋升为1M,老年代大小为20G,那么需要20*1024/60.0/60.0
大约5.6个小时会把老年代填满(这里假设老年代一开始是空的)
注意新生代GC的频率,持续时间,时间长就将新生代调小,频率快就新生代调大
老年代注意晋升的频率,减少晋升数量,尽量在MinorGC的时候搞定
CMS调优的三个难点
- 对象从新生代提到到老年代的速率
- 并行老年代垃圾收集线程回收空间的速率
- 老年代空间的碎片化
对于CMS同样要做到MinorGC优先原则
Survivor空间默认是新生代的1/10
Eden默认占1/8,两个Survivor默认各占10%新生代
适当调大Survivor大小,比每次晋升的略大
控制晋升阈值,-XX:MaxTenuringThreshold=N,默认是15
调成0就是每次都会往老年代放,容易将老年代撑满
调成无限大则始终不往老年代放,但是当新生代不够时会将大批量对象导入老年代
CMS调优参数
参数名称 | 含义 |
-XX:+UseConcMarkSweepGC | 使用CMS内存收集 |
-XX:CMSInitiatingOccupancyFraction=70 | 使用cms作为垃圾回收使用70%后开始CMS收集 |
-XX:+UseCMSInitiatingOccupancyOnly | 开始CMS收集 |
-XX+UseCMSCompactAtFullCollection | 在FULL GC的时候, 对年老代的压缩 |
-XX:CMSFullGCsBeforeCompaction | 多少次后进行内存压缩 |
-XX:+CMSParallelRemarkEnabled | 降低标记停顿 |
-XX:CMSInitiatingPermOccupancyFraction | 设置Perm Gen使用到达多少比率时触发 |
-XX:+CMSPermGenSweepingEnabled | 开启持久代CMS垃圾收集 |
-XX:+CMSClassUnloadingEnabled | 回收持久代的class |
-XX:+CMSScavengeBeforeRemark | 在CMS重标记之前进行MinorGC |
NUMA系统上部署 -XX:+UseNUMA
逃逸分析 -XX:+DoEscapeAnalysis
偏向锁 -XX:+UseBiasedLocking
支持大页面 -XX:LargePageSizeInBytes=N