1. 参数分类
- 1.标准参数
功能和输出的参数都是很稳定的 在未来的JVM版本中不会改变 可以使用java -help检索出所有的标准参数 - 2.X参数
非标准化参数 在未来的版本可能会改变 所有的参数都用-X开始 可以使用java -X检索 但是注意没有-Xcomp - 3.XX参数
非标准 很长一段时间不会列出来 用于JVM开发的debug和调优
说明XX参数的语法:
所有的XX参数都以”-XX:”开始,但是随后的语法不同,取决于参数的类型。
对于布尔类型的参数,我们有”+”或”-“,然后才设置JVM选项的实际名称。例如,-XX:+<name>
用于激活<name>
选项,而-XX:-<name>
用于注销选项。
对于需要非布尔值的参数,如string或者integer,我们先写参数的名称,后面加上”=”,最后赋值。例如, -XX:<name>=<value>
给<name>
赋值<value>
。
2.参数的使用例子
1 ) -server 和 -client
有两种类型的HotSpot JVM
- a ) server
默认为堆提供了一个更大的空间和并行的垃圾收集器 并且在运行时可以更大程度的优化代码 - b ) client
客户端虚拟机有较小的默认堆内存 可以缩短JVM启动的时间和占用更少的内存 客户端的JVM只有在32位操作系统中才有
注意
- 1 ) 从JDK5开始 当应用启动时会检测当前的运行环境是否是服务器 如果是服务器就使用Server JVM 这是为了提升性能,一般来说Server JVM启动比Client JVM慢,原因是使用的是重量级的虚拟机,但是内部进行了很多优化,而Client JVM使用的是轻量级的JVM,当服务稳定运行后还是Server JVM的速度更快一些
- 2 ) 在JDK6中 Server JVM要求至少双核CPU和2GB物理内存
- 3 ) 在32位操作系统上 JDK可以运行Server JVM 但是JRE只能运行Client JVM
2. -version和-showversion
- 1) java -version
F:\Temp>java -version
java version "1.8.0_92"
Java(TM) SE Runtime Environment (build 1.8.0_92-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.92-b14, mixed mode)
显式虚拟机类型 当前版本号等等
这里注意JVM默认开启了mixmode混合模式,这意味着JVM在运行时可以动态的把字节码编译成本地代码
- 2 ) -showversion
-showversion 除了显式当前虚拟机版本还会继续处理并执行java程序
F:\Temp>java -showversion Test
java version "1.8.0_92"
Java(TM) SE Runtime Environment (build 1.8.0_92-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.92-b14, mixed mode)
hello world
3. -Xint,-Xcomp和-Xmixed
1 ) -Xint 指令
int是interpretation的简称 翻译解释的意思 意味着强制JVM执行所有的字节码 这会降低运行速度[10倍左右]2 ) -Xcomp 指令
comp是Compile的简称 编译的意思 意味着JVM在第一次使用时会把所有的字节码编译成本地代码 从而带来最大程度的优化,虽然比-Xint的效率要高,但是它没有让JVM启动JIT编译器的全部功能, JIT编译器一般会在运行时创建方法使用文件 然后一步步的优化每个方法,因此该指令还是会造成一定的效率衰减3 ) -Xmixed 指令
JVM在运行时可以动态的把字节码编译成本地代码
默认开启了混合模式,因此无需显示的指定
三种方式的对比 分别以以上三种指令运行程序
public static void main(String[] args) {
/*run in -Xint 强制执行所有字节码 Average time: 3271*/
/*run in -Xcomp 第一次使用时把字节码转换成本地文件 Average time: 1874*/
/*run in -Xmixed JVM默认支持的模式 无需设置 Average time: 1250*/
long startTime = System.currentTimeMillis();
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < 1000000; i++) {
map.put(String.valueOf(i), i);
}
System.out.println("Average time: " + (System.currentTimeMillis() - startTime));
}
什么都不指定 默认混合模式的速度最快
Average time: 1326
-Xcomp 的效率略有衰减
Average time: 1913
-Xint 的效率最差
Average time: 3770
4. -XX:+PrintComilation和-XX:+CITime
- 1)-XX:+PrintComilation
简单的输出一些关于从字节码转成本地代码的编译过程,每当一个方法被编译时 就输出一行-XX:+PrintCompilation,每行都包含顺序号[唯一的编译任务ID]和已编译方法的名称和大小和额外信息【本地的包装方法前面会有n参数 System::arraycopy 此类方法不会包含顺序号和方法占用的大小 因为不需要被编译成本地代码】
241 1 3 java.lang.String::equals (81 bytes)
243 2 3 java.lang.StringBuilder::append (8 bytes)
243 4 3 java.lang.Character::toLowerCase (9 bytes)
243 5 3 java.lang.CharacterData::of (120 bytes)
243 6 3 java.lang.CharacterDataLatin1::toLowerCase (39 bytes)
244 7 3 java.lang.CharacterDataLatin1::getProperties (11 bytes)
244 3 3 java.lang.AbstractStringBuilder::append (50 bytes)
244 11 n 0 java.lang.System::arraycopy (native) (static)
...
截取一小段分析
对于第一行241 1 3 java.lang.String::equals (81 bytes)
其中1就是顺序号表示唯一的编译任务,equals是编译的方法名 81bytes表示方法的大小
244 11 n 0 java.lang.System::arraycopy (native) (static)
这一行中编译的System::arraycopy表示编译的是System类的静态方法 arraycopy,且是本地方法因此前面有一个n参数
- 2)CITime
用于在JVM关闭时获得各种编译的统计信息
运行结果
Accumulated compiler times (for compiled methods only)
------------------------------------------------
Total compilation time : 0.020 s
Standard compilation : 0.020 s, Average : 0.000
On stack replacement : 0.000 s, Average : -1.#IO
Detailed C1 Timings
Setup time: 0.000 s ( 0.0%)
Build IR: 0.006 s (37.6%)
Optimize: 0.000 s ( 2.5%)
RCE: 0.000 s ( 0.9%)
Emit LIR: 0.006 s (40.7%)
LIR Gen: 0.001 s ( 9.4%)
Linear Scan: 0.005 s (30.7%)
LIR Schedule: 0.000 s ( 0.0%)
Code Emission: 0.002 s (13.6%)
Code Installation: 0.001 s ( 8.0%)
Instruction Nodes: 1800 nodes
Total compiled methods : 50 methods
Standard compilation : 50 methods
On stack replacement : 0 methods
Total compiled bytecodes : 2378 bytes
Standard compilation : 2378 bytes
On stack replacement : 0 bytes
Average compilation speed: 121576 bytes/s
nmethod code size : 34208 bytes
nmethod total size : 59168 bytes
5. -XX:+UnlockExperimentalJVMOptions
有些时候当设置一个特定的JVM参数时,JVM会在输出“Unrecognized VM option
”后终止。如果发生了这种情况,你应该首先检查你是否输错了参数。然而,如果参数输入是正确的,并且JVM并不识别,或许需要设置-XX:+UnlockExperimentalVMOptions 来解锁参数
6.-XX:+LogCompilation和-XX:+PrintOptoAssembly
- 1) LogCompilation
但是用PrintComilation得到的信息不够详细时 可以使用该指令把编译输出扩展到日志文件hotspot.log中
注意-XX:+LogCompilation 需要使用-XX:+UnlockExperimentalVMOptions来解锁。 - 2) -XX:+PrintOptoAssembly
由编译器生成的本地代码被输出并写道hotspot.log文件中
注意:-XX:+PrintOptoAssembly要求Server VM是debug版本
7. -XX:+PrintFlagsFinal和-XX:+PrintFlagsInitial
- 1 ) -XX:+PrintFlagsFinal 打印出所有XX参数和值
[Global flags]
uintx AdaptiveSizeDecrementScaleFactor = 4 {product}
uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product}
uintx AdaptiveSizePausePolicy = 0 {product}
uintx AdaptiveSizePolicyCollectionCostMargin = 50 {product}
uintx AdaptiveSizePolicyInitializingSteps = 20 {product}
uintx AdaptiveSizePolicyOutputInterval = 0 {product}
uintx AdaptiveSizePolicyWeight = 10 {product}
uintx AdaptiveSizeThroughPutPolicy = 0 {product}
uintx AdaptiveTimeWeight = 25 {product}
bool AdjustConcurrency = false {product}
bool AggressiveOpts = false {product}
intx AliasLevel = 3 {C2 product}
bool AlignVector = false {C2 product}
...
第一列表示参数的数据类型 第二列是名称 第四列是值 第五列是参数的类别
第三列‘=’表示第四列是参数的默认值 “:=”表示参数被用户或者JVM赋值了
例如使用-XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal
指令运行后发现
uintx InitialHeapSize := 57505088 {product}
uintx MaxHeapSize := 920649728 {product}
uintx ParallelGCThreads := 4 {product}
bool PrintFlagsFinal := true {product}
bool UseParallelGC := true {product}
以上被赋值的参数
2 ) -XX:+PrintFlagsInitial
打印的是XX参数的初始化值3)-XX:+PrintCommandLineFlags
这个参数让JVM打印出那些已经被用户或者JVM设置过的详细的XX参数的名称和值。它列举出-XX:+PrintFlagsFinal
的结果中第三列有”:=”的参数。以这种方式,我们可以用-XX:+PrintCommandLineFlags作为快捷方式来查看修改过的参数
-XX:InitialHeapSize=65596736 -XX:MaxHeapSize=1049547776 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
内存管理参数
- 新生代[young generation]: 存储新分配的和较年轻的对象
- 老生代[old generation]: 存储着长寿的对象
- 永久代[permanent generation]: 存储需要伴随整个JVM生命周期的对象 比如已加载的对象的类定义或者String对象内部Cache
1. -Xms 和 -Xmx (-XX:InitialHeapSize 和 -XX:MaxHeapSize)
- 1 ) -Xms 和 -XX:InitialHeapSize
-Xms是 -XX:InitialHeapSize的简写 表示的是初始化堆的大小 - 2 ) -Xmx 和 -XX:MaxHeapSize
-Xmx 是 -XX:MaxHeapSize的简写 表示的是设置堆的最大大小
需要注意的是,所有JVM关于初始\最大堆内存大小的输出都是使用它们的完整名称:“InitialHeapSize”和“InitialHeapSize”。所以当你查询一个正在运行的JVM的堆内存大小时,如使用-XX:+PrintCommandLineFlags
参数或者通过JMX查询,应该寻找“InitialHeapSize”和“InitialHeapSize”标志而不是“Xms”和“Xmx”
2. -XX:+HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath
- 1 )
-XX:+HeapDumpOnOutOfMemoryError
:
使得JVM在产生内存溢出时自动生成堆内存快照 - 2 ) XX:HeapDumpPath=
<path>
改变默认的堆内存快照生成路径,<path>
可以是相对或者绝对路径
3.-XX:OnOutOfMemoryError
当内存发生溢出时 执行一串指令
$ java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof -XX:OnOutOfMemoryError ="sh ~/cleanup.sh" MyApp
4.-XX:PermSize和-XX:MaxPermSize
- 1 ) -XX:PermSize:设置永久代的初始大小
- 2 ) -XX:MaxPermSize:设置永久代的最大大小
$ java -XX:PermSize=128m -XX:MaxPermSize=256m MyApp
5.-XX:InitialCodeCacheSize和-XX:ReservedCodeCacheSize
代码缓存内存区域 用来存储已编译方法生成的本地代码
如果代码缓存被占满,JVM会打印出一条警告消息,并切换到interpreted-only 模式:JIT编译器被停用,字节码将不再会被编译成机器码。因此,应用程序将继续运行,但运行速度会降低一个数量级
6. -XX:+UseCodeCacheFlushing
如果代码缓存不断增长[因为热部署引起的内存泄露 提高代码的缓存大小只会延缓发生溢出]
根据的解决方法可以使用-XX:+UseCodeCacheFlushing使当代码缓存被填满时 让JVM放弃一些编译代码
新生代垃圾回收
HotSpot中新生代被划分为3个区域
1.一个相对大点的区域—Eden区
2.两个相对小点的区域From Survivor和To Survior
1.按照规定 新对象首先分配在Eden中[如果新对象过大 会直接分配在老生代中]
2.在GC中 Eden区的对象会被移动到Survivor中 直到对象满足一定的年纪[熬过GC的次数]会被移动到老生带
GC过程
新生代的GC采用复制算法,在GC前To Survivor保持清空,对象保存在Eden和From Survivor中 GC运行时,Eden中的幸存对象被复制到To Survior中,针对From Survior中的幸存对象会考虑对象的年纪,如果年纪没有达到阈值,会被复制到To Survior去,如果达到阈值会被复制到老生代。
复制阶段完成后,Eden和From Survivor中只保存死对象,可以清空,如果在复制过程中To Survior被填满 剩余的对象会被复制到生代, 最后From Survivor和To Survior会调换名字
总结:
- 一般对象出生在Eden区 年轻代GC过程中对象在两个幸存区之间移动 如果对象存活到指定的年龄 会被移动到老生代
- 对象在老生代死亡时 需要更高级别的GC 更重量级的GC算法[老生代不使用复制算法 因为老生代中没有多余的内存空间复制]
- 如果新生代过小 导致新生对象很快晋升到老生代 在老生代中的对象很难被回收
- 如果新生代过大 会发生过多的赋值过程
1) -XX:NewSize和-XX:MaxNewSize
- 1 ) -XX:NewSize 设置新生代的初始大小
- 2 ) -XX:MaxNewSize 设置新生代的最大大小
注意:新生代只是堆的一部分 新生代越大老生代越小 一般不允许新生代比老生代还大 考虑到GC最坏的情况 新生代全部复制到老生代会产生OOM错误
一般设置为-Xmx/2[堆大小的一半]
2) -XX:NewRatio
设置新生代和老生代的相对大小
优点是新生代大小会随着整个堆大小动态扩展
-XX:NewRatio=3[老生代/新生代=3]
3)-XX:SurvivorRatio
指定Eden区和Survivor区的大小比例
注意两个幸存区是一样大的
-XX:SurvivorRatio=10 表示Eden区占整个新生代的10/12 每个Survivor占1/12
- Survior区过大 虽然有足够的空间容纳GC后的幸存对象 但是Eden区药效会导致空间很快耗尽 增加新生代GC次数
- Survior区过小 没有足够的空间容纳GC后的幸存对象 使得对象都会移动到老生代 不方便回收
4) -XX:+PrintTenuringDistribution
指定JVM在每次新生代GC时 输出Survivor中对象的年龄分布
5) -XX:InitialTenuringThreshold,-XX:MaxTenuringThreshold和-XX:TargetSurvivorRatio
- 1 ) -XX:InitialTenuringThreshold 设置老生代阈值的初始值
- 2 ) -XX:MaxTenuringThreshold 设置老生代阈值的最大值
- 3 ) -XX:TargetSurvivorRatio设置幸存区的目标使用率
- 4 ) -XX:+NeverTenure和-XX:+AlwaysTenure
- 1 ) -XX:+NeverTenure:对象永远不会晋升到老年代[当不需要老生代的时候可以这样设置]
- 2 ) -XX:+AlwaysTenure:表示没有Survivor区 对象会直接被移动到老生代中
吞吐量收集器
评价一个垃圾收集GC算法的两个标准
- 1.吞吐量越高算法越好
2.暂停时间越短算法越好
a) 吞吐量[throughput]:
JVM在专门的线程[GC Threads]中执行GC 只要GC线程是活动的 就会和应用程序线程[Application Threads]争用当前可用CPU的时钟周期
而吞吐量就是指应用程序线程占程序总用时的比例- b) 暂停时间[pause times]:
一个时间段内应用程序线程让GC线程执行而完全暂停
对于老生代提供了两种垃圾收集算法[除了新的G1的垃圾收集算法]
1.第一类尝试最大限度的提高吞吐量
2.第二类试图最小化暂停时间
老生代由于缺乏内存空间导致对象从年轻代移动到年老代失败时会触发垃圾收集器 ,从所谓的GC根开始,搜索堆中可到达对象并标记成活的 之后垃圾收集器把活的对象移动到年老代一块无碎片的内存块中 并标记剩余内存空间是空闲的。
垃圾收集器使用一个或多个线程来执行垃圾收集 使用多个线程时算法的不同步骤被分解 使得每个垃圾收集现在在自己的区域而不干扰其他线程
在垃圾收集期间 所有的应用程序线程暂停 只有垃圾收集完成之后才会重新开始
1) -XX:+UseSerialGC
激活串行垃圾收集器 例如单线程面向吞吐量的垃圾收集器 推荐用于只有单个CPU的JVM
2) -XX:+UseParallelGC
使用多线程并行执行年轻代垃圾收集
3) -XX:+UseParallelOldG
除了激活年轻代并行垃圾收集,也激活了年老代并行垃圾收集
4) -XX:ParallelGCThreads
-XX:ParallelGCThreads=<value>
指定并行垃圾收集的线程数量
5) -XX:-UseAdaptiveSizePolicy
垃圾收集器能将堆大小动态变动像GC设置一样应用到不同的堆区域,只要有证据表明这些变动将能提高GC性能
6) -XX:GCTimeRatio
-XX:GCTimeRatio=<value>
指定JVM吞吐量要达到的目标值
指定目标应用程序线程的执行时间和总的程序执行时间达到N/(N+1)的目标比值
通过-XX:GCTimeRatio=9我们要求应用程序线程在整个执行时间中至少9/10是活动的(因此,GC线程占用其余1/10)
-XX:GCTimeRatio的默认值是99,也就是说,应用程序线程应该运行至少99%的总执行时间
7) -XX:MaxGCPauseMillis
通过-XX:GCTimeRatio=<value>
告诉JVM最大暂停时间目标值[ms为单位]
CMS收集器
并发标记清理收集器CMS收集器:低应用停顿时间
CMS收集器的过程
- 1.初始标记
为了收集应用程序的对象引用需要暂停应用程序线程 该阶段完成后 应用程序线程再次启动 - 2.并发标记
从第一阶段收集到的对象引用开始 遍历所有的其他对象引用 - 3.并发预处理
改变当运行第二阶段时 由应用程序线程产生的对象引用 以更新第二阶段的结果 - 4.重标记
由于第三阶段是并发的 对象引用可能会发生进一步的改变 应用程序会再一次被暂停以更新这些变化
在进行实际的清理之前确保一个正确的对象引用视图 - 5.并发清理
所有不再被引用的对象从内存中清理掉 - 6.并发重置
收集器做一些清尾的工作
可能存在的问题
- 1.堆碎片
由于使用的是标记-清除算法 因此可能会产生内存碎片
当没有足够的连续内存时间时 会发起一次Full GC 这会导致Stop the world - 2.对象分配率高
老生代没有足够的内存空间存放新生代提升过来的对象 JVM会执行堆碎片整理 触发FullGC
解决方法:
- 1.增加新生代的堆大小 防止新生代的短生命对象提前进入老生代
- 2.利用分析器 分析过度的对象分配 减少这些对象的申请
1) -XX:+UseConcMarkSweepGC
激活CMS收集器 JVM默认使用的是并行处理器
2) -XX:UseParNewGC
使用CMS收集器时 激活年轻代使用多线程并行执行垃圾回收
注意:
最新的JVM版本,当使用-XX:+UseConcMarkSweepGC时,-XX:UseParNewGC会自动开启。
因此,如果年轻代的并行GC不想开启,可以通过设置-XX:-UseParNewGC来关掉。
3) -XX:+CMSConcurrentMTEnabled
并发的CMS阶段以多线程执行 默认开启
4) -XX:ConcGCThreads
-XX:ConcGCThreads=<value>
定义并发CMS中运行的线程数
如果还标志未设置,JVM会根据并行收集器中的-XX:ParallelGCThreads参数的值来计算出默认的并行CMS线程数。该公式是ConcGCThreads = (ParallelGCThreads + 3)/4。因此,对于CMS收集器
-XX:ParallelGCThreads标志不仅影响“stop-the-world”垃圾收集阶段,还影响并发阶段。
5) -XX:CMSInitiatingOccupancyFraction
-XX:CMSInitiatingOccupancyFraction=来设置,该值代表老年代堆空间的使用率。比如,value=75意味着第一次CMS垃圾收集会在老年代被占用75%时被触发。通常CMSInitiatingOccupancyFraction的默认值为68(之前很长时间的经历来决定的)。
6) -XX:+UseCMSInitiatingOccupancyOnly
命令JVM不基于运行时收集的数据来启动CMS垃圾收集周期
JVM通过CMSInitiatingOccupancyFraction的值进行每一次CMS收集,而不仅仅是第一次
7) -XX:+CMSClassUnloadingEnabled
CMS默认不会对永久代进行垃圾回收 如果希望对永久代进行垃圾回收 可以设置此标志
注意,即使没有设置这个标志,一旦永久代耗尽空间也会尝试进行垃圾回收,但是收集不会是并行的,而再一次进行Full GC。
8) -XX:+CMSIncrementalMode
开启CMS收集器的增量模式
ps:增量模式会经常暂停CMS过程 以便对应用程序作出完全的让步
9) -XX:+ExplicitGCInvokesConcurrent和-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
1.-XX:+ExplicitGCInvokesConcurrent
命令JVM无论什么时候调用系统GC,都执行CMS GC,而不是Full GC
2.-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
保证当有系统GC调用时,永久代也被包括进CMS垃圾回收的范围内。因此,通过使用这些标志,我们可以防止出现意料之外的”stop-the-world”的系统GC。
10) -XX:+DisableExplicitGC
该标志将告诉JVM完全忽略系统的GC调用(不管使用的收集器是什么类型)
GC日志
1)-XX:+PrintGC
开启简单GC日志模式 为每一次新生代的GC和每一次Full GC打印一行信息
[GC 246656K->243120K(376320K), 0.0929090 secs]
[Full GC 243120K->241951K(629760K), 1.5589690 secs]
开始首先是GC类型 然后是在GC之前和之后使用的堆空间 然后是当前的堆容量 最后是GC的执行时间
2)-XX:PrintGCDetails
开启了详细GC日志模式 此模式下的日志格式和GC算法有关
使用Throughput垃圾收集器在新生代中生成的日志
[GC
[PSYoungGen: 142816K->10752K(142848K)] 246648K->243136K(375296K), 0.0935090 secs
]
[Times: user=0.55 sys=0.10, real=0.09 secs]
Full GC输出日志
[Full GC
[PSYoungGen: 10752K->9707K(142848K)]
[ParOldGen: 232384K->232244K(485888K)] 243136K->241951K(628736K)
[PSPermGen: 3162K->3161K(21504K)], 1.5265450 secs
]
3)-XX:+PrintGCTimeStamps和-XX:+PrintGCDateStamps
- 1.-XX:+PrintGCTimeStamps
可以将时间和日期也加到GC日志中。表示自JVM启动至今的时间戳 - 2.-XX:+PrintGCDateStamps
每一行就添加上了绝对的日期和时间。
4) -Xloggc
缺省的GC日志时输出到终端的,使用-Xloggc:也可以输出到指定的文件。需要注意这个参数隐式的设置了参数-XX:+PrintGC和-XX:+PrintGCTimeStamps,但为了以防在新版本的JVM中有任何变化,我仍建议显示的设置这些参数。