实战Java虚拟机读书笔记

第3章 常用Java虚拟机参数
本章涉及的主要知识点有:
跟踪Java虚拟机的垃圾回收和类加载等信息。
配置Java虚拟机的堆空间。
配置永久区和Java栈。
学习虚拟机的服务器和客户端模式。
各项初始值
堆内存分配
最小默认是物理内存的1/64;JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每次GC后调整堆的大小。

最大堆小大:
64位的JVM上,物理内存小于192MB时,为物理内存的一半;物理内存大192MB且小于128GB时,为物理内存的四分之一;大于等于128GB时,都为32GB

非堆内存分配
JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。
默认值分配
默认情况下年轻代占1/3内存,老年代占2/3内存;年轻代又被划分为Eden区(伊甸园区)和两个Survivor区(幸存区),各自分别占年轻代空间的8/10、/1/10、1/10。也就是说如果堆内存区域有600M,那么年轻代200M、老年代400M、Eden区160M、S0区20M、S1区20M。
3.1一切运行都有迹可循:掌握跟踪调试参数
3.1.1跟踪垃圾回收——读懂虚拟机日志
最简单的一个GC参数是-XX:+PrintGC,使用这个参数启动Java虚拟机后,只要遇到GC,就会打印日志,如下所示:
[GC 4793K->377K(15872K),0.0006926 secs]
该日志显示,一共进行了1次GC,每次GC占用一行,在GC前,堆空间使用量约为4MB,GC后,堆空间使用量为377KB,当前可用的堆空间总和约为16MB (15936KB)。最后,显示的是本次GC所花费的时间。

如果需要更加详细的信息,则可以使用-XX:+PrintGCDetails参数。它的输出可能如下:
[GC [DefNew: 8704K->1087K (9792K),0.0665590 secs]22753K->17720K(31680K) ,0.0666180 secs][Times: user=0.06 sys=0.00, real=0.06 secs]
[GC [DefNew: 9791K->9791K(9792K),0.0000350 secs] [Tenured:16632K->13533K
(21888K),0.4063120 secs] 26424K->13533K(31680K),[Perm :2583K->2583K(21248K)],0.4064710secs][Times: user=0. 41 sys=0.00,real=0.40 secs]

从这个输出中可以看到,系统经历了2次GC,第1次仅为新生代GC,回收的效果是新生代从回收前的8MB左右降低到1MB。整个堆从22MB左右降低到了17MB。

第2次(加粗部分)为Full GC,它同时回收了新生代、老年代和永久区。日志显示,新生代在这次GC中没有释放空间(严格来说,这是GC日志的一个小 bug,事实上,在这次Full GC完成后,新生代被清空,由于GC日志输出时机的关系,各个版本的JDK 的日志多少有些不太精确的地方,读者需要留意),老年代从16MB降低到了13MB。整个堆大小从26MB 左右降低为13MB左右(这个大小完全与老年代实际大小相等,因此也可以推断,新生代实际上已被清空)。永久区的大小没有变化。在日志的最后,显示了GC所花费的时间,其中 user表示用户态CPU耗时,sys表示系统CPU耗时,real表示GC实际经历的时间。

参数-XX:+PrintGCDetails还会使虚拟机在退出前打印堆的详细信息,详细信息描述了当前堆的各个区间的使用情况。如上输出所示,当前新生代(new generation)总大小为9792KB,已使用4586KB。紧跟其后的3个16进制数字表示新生代的下界、当前上界和上界。
[0x00000000f8e00000,0x00000000f98a0000,0x00000000f98a0000)

如果需要更全面的堆信息,还可以使用参数-XX:+PrintHeapAtGC。它会在每次GC前后分别打印堆的信息,就如同-XX:+PrintGCDetails的最后输出一样。

如果需要分析GC发生的时间,还可以使用-XX:+PrintGCTimeStamps参数,该参数会在每次GC 发生时,额外输出GC 发生的时间,该输出时间为虚拟机启动后的时间偏移量。如下代码表示在系统启动后0.08秒、0.088秒、0.094秒发生了3次GC。
0.080:[GC0.080:[DefNew: 4416K->512K(4928K),0.0055792 secs] 4416K->3889K (15872K),0.0057061 secs][Times: user=0.00 sys=0.00,real=0.01 secs]

默认情况下,GC 的日志会在控制台中输出,这不便于后续分析和定位问题。为此,虚拟机允许将GC日志以文件的形式输出,可以使用参数-Xloggc指定。比如使用参数-Xloggc:log/gc.log启动虚拟机,可以在当前目录下的 log 文件夹下的gc.log文件中记录所有的GC日志。图3.1显示了由-Xloggc 生成的gc.log文件。
3.1.2类加载/卸载的跟踪
使用jvm参数跟踪
3.1.3系统参数查看
由于目前的Java 虚拟机支持众多的可配参数,不同的参数可能对系统的执行效果有较大的影响,因此,有必要明确当前系统的实际运行参数。虚拟机提供了一些手段来帮助研发人员获得这些参数。
3.2 让性能飞起来:学习堆的配置参数
3.2.1最大堆和初始堆的设置
直接将初始堆-Xms与最大堆-Xmx设置相等
3.2.2 新生代的配置
参数-Xmn可以用于设置新生代的大小。设置一个较大的新生代会减小老年代的大小,这个参数对系统性能以及GC行为有很大的影响。新生代的大小一般设置为整个堆空间的1/3到1/4左右。

参数-XX:SurvivorRatio(默认值8)用来设置新生代中eden空间和 from/to空间的比例关系,它的含义如下:
一Xx:SurvivorRatio=eden/from=eden/to
如设置-XX:SurvivorRatio=2 -Xmn1m
这里eden与 from的比值为2比1,故eden区为512KB,from区委256KB。总可用的新生代为512KB+256KB=-768KB,而新生代总大小为512KB+256KB+256KB =1024KB=1MB。

除了可以使用参数-Xmn指定新生代的绝对大小外,还可以使用参数-XX:NewRatio来设置新生代和老年代的比例。它的含义如下:
一XX:NewRatio=老年代/新生代
使用参数-Xmx20M -Xms20M -XX:NewRatio=2
此时,因为堆大小为20MB。新生代和老年代的比为1比2。故新生代大小为20MB*1/3=6MB左右,老年代为13MB左右

-XX:SurvivorRatio可以设置eden区与 survivor区的比例。-XX:NewRatio可以设置老年代与新生代的比例。

3.2.3 堆溢出处理
Java 虚拟机提供了参数-XX:+HeapDumpOnOutOfMemoryError,使用该参数,可以在内存溢出时导出整个堆信息。和它配合使用的还有-XX:HeapDumpPath,可以指定导出堆的存放路径。

使用如下参数执行上述代码:
-Xmx20m -Xms5m -XX :+HeapDumpOnoutofMemoryError -XX: HeapDumpPath=d: / a.dump
3.3 别让性能有缺口:了解非堆内存的参数配置
3.3.1 方法区配置
方法区主要存放类的元信息。
在JDK 1.6和 JDK 1.7等版本中,可以使用-XX:PermSize和-XX:MaxPermSize配置永久区大小。其中-XX:PermSize表示初始的永久区大小,-XX:MaxPermSize表示最大永久区。
在JDK 1.8中,永久区被彻底移除,使用了新的元数据区存放类的元数据。默认情况下,元数据区只受系统可用内存的限制,但依然可以使用参数-XX:MaxMetaspaceSize指定永久区的最大可用值
3.3.2 栈配置
栈是每个线程私有的内存空间。在Java虚拟机中可以使用-Xss 参数指定线程的栈大小
3.4 Client和Server二选一:虚拟机的工作模式
第5章 垃圾收集器和内存分配
5.1 一心一意一件事:串行回收器
使用-XX:+UseSerialGC参数可以指定使用新生代串行收集器和老年代串行收集器。当虚拟机在 Client模式下运行时,它是默认的垃圾收集器。
5.1.2老年代串行回收器
若要启用老年代串行回收器,可以尝试使用以下参数。
-XX:+UseSerialGC:新生代、老年代都使用串行回收器。
-XX:+UseParNewGC:新生代使用ParNew回收器,老年代使用串行收集器。
-XX:+UseParallelGC:新生代使用ParallelGC回收器,老年代使用串行收集器。
5.2人多力量大:并行回收器
5.2.1新生代 ParNew回收器
开启 ParNew回收器可以使用以下参数。
-XX:+UseParNewGC:新生代使用ParNew回收器,老年代使用串行回收器。-XX:+UseConcMarkSweepGC:新生代使用ParNew回收器,老年代使用CMS.
ParNew回收器工作时的线程数量可以使用-XX:ParallelGCThreads参数指定。一般,最好与CPU 数量相当,避免过多的线程数,影响垃圾收集性能。在默认情况下,当CPU 数量小于8个时,ParallelGCThreads 的值等于CPU数量,当CPU 数量大于8个时,ParallelGCThreads 的值等于3+((5*CPU_Count)/8)。
5.2.2新生代 ParallelGC回收器
新生代 ParallelGC回收器也是使用复制算法的收集器。从表面上看,它和 ParNew回收器一样,都是多线程、独占式的收集器。但是,ParallelGC回收器有一个重要的特点:它非常关注系统的吞吐量。
新生代 ParallelGC回收器可以使用以下参数启用。
·-XX:+UseParallelGC:新生代使用ParallelGC回收器,老年代使用串行回收器。
-XX:+UseParallelOldGC:新生代使用ParallelGC回收器,老年代使用Paralle1OldGC回收器。
ParallelGC回收器提供了两个重要的参数用于控制系统的吞吐量。
。-XX:MaxGCPauseMillis:设置最大垃圾收集停顿时间。它的值是一个大于0的整数。
ParallelGC在工作时,会调整Java堆大小或者其他一些参数,尽可能地把停顿时间控制在 MaxGCPauseMillis 以内。如果读者希望减少停顿时间,而把这个值设得很小,为了达到预期的停顿时间,虚拟机可能会使用一个较小的堆(一个小堆比一个大堆回收快),而这将导致垃圾回收变得很频繁,从而增加了垃圾回收总时间,降低了吞吐量。
-XX:GCTimeRatio:设置吞吐量大小。它的值是一个0 到100 之间的整数。假设GCTimeRatio的值为n,那么系统将花费不超过1/(1+n)的时间用于垃圾收集。比如GCTimeRatio等于19(默认值),则系统用于垃圾收集的时间不超过1/(1+19)=5%。默认情况下,它的取值是99,即不超过1/(1+99)=1%的时间用于垃圾收集。

ParallelGC回收器关注系统吞吐量。可以通过-XX:MaxGCPauseMillis和-XX:GCTimeRatio设置期望的停顿时间和吞吐量大小。但是鱼和熊掌不可兼得,这两个参数是相互矛盾的,通常如果减少一次收集的最大停顿时间,就会同时减少系统吞吐量,增加系统吞吐量又可能会同时增加一次垃圾回收的最大停顿。
5.2.3 老年代ParallelOldGC回收器
老年代ParallelOldGC回收器也是一种多线程并发的收集器。和新生代 ParallelGC回收器一样,它也是一种关注吞吐量的收集器。从名字上看,它在 ParallelGC 中间插入了Old,表示这是一个应用于老年代的回收器,并且和 ParallelGC新生代回收器搭配使用。

使用-XX:+UseParallelOldGC可以在新生代使用 ParallelGC回收器,老年代使用ParallelOldGC回收器。这是一对非常关注吞吐量的垃圾回收器组合。在对吞吐量敏感的系统中,可以考虑使用。参数-XX:ParallelGCThreads也可以用于设置垃圾回收时的线程数量。
5.3 —心多用都不落下:CMS回收器
CMS 回收器主要关注于系统停顿时间。CMS是Concurrent Mark Sweep 的缩写,意为并发标记清除,从名称上就可以得知,它使用的是标记清除算法,同时它又是一个使用多线程并行回收的垃圾回收器。
5.3.1 CMS主要工作步骤
CMS回收器的工作过程与其他垃圾收集器相比,略显复杂。CMS工作时,主要步骤有;初始标记、并发标记、预清理、重新标记、并发清除和并发重置。其中初始标记和重新标记是独占系统资源的,而预清理、并发标记、并发清除和并发重置是可以和用户线程一起执行的。因此,从整体上说,CSM收集不是独占式的,它可以在应用程序运行过程中进行垃圾回收。

根据标记清除算法,初始标记、并发标记和重新标记都是为了标记出需要回收的对象。并发清理则是在标记完成后,正式回收垃圾对象。并发重置是指在垃圾回收完成后,重新初始化CMS数据结构和数据,为下一次垃圾回收做好准备。并发标记、并发清理和并发重置都是可以和应用程序线程一起执行的。
5.3.2 CMS主要的设置参数
启用CMS回收器的参数是-XX:+UseConcMarkSweepGC。CMS是多线程回收器,设置合理的工作线程数量也对系统性能有重要的影响。
CMS 默认启动的并发线程数是(ParallelGCThreads+3)/4)。ParallelGCThreads表示GC 并行时使用的线程数量,如果新生代使用ParNew,那么ParallelGCThreads也就是新生代GC的线程数量。这意味着有4个 ParallelGCThreads时,只有1个并发线程,而两个并发线程时,有5~-8个ParallelGCThreads 线程数。

由于CMS回收器不是独占式的回收器,在CMS回收过程中,应用程序仍然在不停地工作。在应用程序工作过程中,又会不断地产生垃圾。这些新生成的垃圾在当前CMS回收过程中是无法清除的。同时,因为应用程序没有中断,所以在CMS回收过程中,还应该确保应用程序有足够的内存可用。因此,CMS回收器不会等待堆内存饱和时才进行垃圾回收,而是当堆内存使用率达到某一阈值时便开始进行回收,以确保应用程序在CMS工作过程中,依然有足够的空间支持应用程序运行。

这个回收阈值可以使用-XX:CMSInitiatingOccupancyFraction来指定,默认是68。即当老年代的空间使用率达到68%时,会执行一次CMS回收。如果应用程序的内存使用率增长很快,在CMS的执行过程中,已经出现了内存不足的情况,此时,CMS回收就会失败,虚拟机将启动老年代串行收集器进行垃圾回收。如果这样,应用程序将完全中断,直到垃圾回收完成,这时,应用程序的停顿时间可能会较长。

CMS是一个基于标记清除算法的回收器。在本章之前的篇幅中已经提到,标记清除算法将会造成大量内存碎片,离散的可用空间无法分配较大的对象。图5.5显示了CMS回收前后老年代的情况。
在这种情况下,即使堆内存仍然有较大的剩余空间,也可能会被迫进行一次垃圾回收,以换取一块可用的连续内存。这种现象对系统性能是相当不利的,为了解决这个问题,CMS回收器还提供了几个用于内存压缩整理的参数。
-XX:+UseCMSCompactAtFullCollection 开关可以使CMS在垃圾收集完成后,进行一次内存碎片整理,内存碎片的整理不是并发进行的。-XX:CMSFullGCsBeforeCompaction参数可以用于设定进行多少次CMS回收后,进行一次内存压缩。
5.3.3 CMS的日志分析
CMS回收器工作时的日志输出如下所示:

CMS 回收器是一个关注停顿的垃圾收集器。同时CMS 回收器在部分工作流程中,可以与用户程序同时运行,从而降低应用程序的停顿时间。
5.4未来我做主:G1回收器
5.4.1 G1的内存划分和主要收集过程
Gl收集器将堆进行分区,划分为一个个的区域,每次收集的时候,只收集其中几个区域,以此来控制垃圾回收产生的一次停顿时间。
G1的收集过程可能有4个阶段:
·新生代GC
并发标记周期
混合收集
如果需要,可能会进行Full GC
5.4.3G1的并发标记周期
Gl的并发阶段和CMS有点类似,它们都是为了降低一次停顿时间,而将可以和应用程序并发的部分单独提取出来执行。

并发标记周期执行前后最大的不同是在该阶段后,系统增加了一些标记为G的区域。这些区域被标记,是因为它们内部的垃圾比例较高,因此希望在后续的混合GC中进行收集(注意在并发标记周期中并未正式收集这些区域)。这些将要被回收的区域会被G1记录在一个称为Collection Sets(回收集)的集合中。

除了初始标记、重新标记和独占清理外,其他几个阶段都可以和应用程序并发执行。
5.4.4混合回收
在并发标记周期中,虽然有部分对象被回收,但是总体上说,回收的比例是相当低的。但是在并发标记周期后,G1已经明确知道哪些区域含有比较多的垃圾对象,在混合回收阶段,就可以专门针对这些区域进行回收。当然,Gl会优先回收垃圾比例较高的区域,因为回收这些区域的性价比也比较高。而这也正是G1名字的由来。Gl全称为Garbage First Garbage Collector,直译为垃圾优先的垃圾回收器,这里的垃圾优先(Garbage First)指的就是回收时优先选取垃圾比例最高的区域。
这个阶段叫作混合回收,是因为在这个阶段,既会执行正常的年轻代GC,又会选取一些被标记的老年代区域进行回收,它同时处理了新生代和老年代,如图5.9所示。因为新生代GC的原因,eden区域必然被清空,此外,有两块被标记位G的垃圾比例最高的区域被清理。被清理区域中的存活对象会被移动到其他区域,这样的好处是可以减少空间碎片。
5.4.6 G1日志
G1的日志与先前的回收器相比已经丰富了很多
【示例5-2】以下是一个完整的G1新生代日志。

注意:Collection Sets表示被选取的、将要被收集的区域的集合。
5.4.7 G1相关的参数
对于G1收集器,可以使用-XX:+UseG1GC标记打开G1收集器开关,对G1收集器进行设置时,最重要的一个参数就是-XX:MaxGCPauseMillis,它用于指定目标最大停顿时间。如果任何一次停顿超过这个设置值时,Gl就会尝试调整新生代和老年代的比例、调整堆大小、调整晋升年龄等手段,试图达到预设目标。对于性能调优来说,有时候,总是鱼和熊掌不可兼得的,如果停顿时间缩短,对于新生代来说,这意味着很可能要增加新生代GC的次数,GC反而会变得更加频繁。对于老年代区域来说,为了获得更短的停顿时间,那么在混合GC收集时,一次收集的区域数量也会变少,这样无疑增加了进行Full GC的可能性。
另外一个重要的参数是-XX:ParallelGCThreads,它用于设置并行回收时,GC的工作线程数量。

此外,-XX:InitiatingHeapOccupancyPercent参数可以指定当整个堆使用率达到多少时,触发并发标记周期的执行。默认值是 45,即当整个堆占用率达到45%时,执行并发标记周期。InitiatingHeapOccupancyPercent一旦设置,始终都不会被G1收集器修改,这意味着G1收集器不会试图改变这个值,来满足MaxGCPauseMillis 的目标。如果 InitiatingHeapOccupancyPercent值设置偏大,会导致并发周期迟迟得不到启动,那么引起Full GC的可能性也大大增加,反之,一个过小的InitiatingHeapOccupancyPercent值,会使得并发周期非常频繁,大量GC 线程抢占CPU,会导致应用程序的性能有所下降。
5.5回眸:有关对象内存分配和回收的一些细节问题
5.5.1 禁用System.gc()
5.5.4对象何时进入老年代
虚拟机提供了一个参数来控制新生代对象的最大年龄: MaxTenuringThreshold。默认情况下,这个参数为15。也就是说,在新生代的对象最多经历15次GC,就可以晋升到老年代。

大对象进入老年代
5.5.5在TLAB上分配对象
TLAB的全称是Thread Local Allocation Buffer,即线程本地分配缓存。从名字上可以看到,TLAB是一个线程专用的内存分配区域。
为什么需要TLAB这个区域呢?这是为了加速对象分配而生的。由于对象一般会分配在堆上,而堆是全局共享的。因此在同一时间,可能会有多个线程在堆上申请空间。因此,每一次对象分配都必须要进行同步,而在竞争激烈的场合分配的效率又会进一步下降。考虑到对象分配几乎是Java最常用的操作,因此Java虚拟机就使用了TLAB这种线程专属的区间来避免多线程冲突,提高了对象分配的效率。TLAB本身占用了eden区的空间。在TLAB启用的情况下,虚拟机会为每一个 Java线程分配一块TLAB空间。

可以看到,TLAB是否启用,对于对象分配的影响是很大的。
由于TLAB空间一般不会太大,因此大对象无法在TLAB上进行分配,总是会直接分配在堆上。TLAB空间由于比较小,因此很容易装满。比如,一个100KB的空间,如果已经使用了80KB,当需要再分配一个30KB的对象时,肯定就无能为力了。这时,虚拟机有两种选择,第一,废弃当前的TLAB,这样就会浪费20KB空间。第二,将这30KB的对象直接分配在堆上,保留当前的TLAB,这样可以希望将来有小于20KB 的对象分配请求可以直接使用这块空间。当发生请求分配的对象大于TLAB 内可用空间时,虚拟机如何在这两种行为间抉择呢﹖虚拟机内部会维护一个叫作refill_waste的值,当请求对象大于refill_waste时,会选择在堆中分配,若小于该值,则会废弃当前TLAB,新建TLAB来分配新对象。这个阈值可以使用虚拟机参数TLABRefillWasteFraction来调整,它表示TLAB中允许产生这种浪费的比例。默认值为64,即表示使用约为1/64的TLAB空间大小作为refill_waste。
默认情况下,TLAB和 refill waste都是会在运行时不断调整的,使系统的运行状态达到最优。如果想要禁用自动调整TLAB的大小,可以使用-XX:-ResizeTLAB 禁用ResizeTLAB,并使用-XX:TLABSize手工指定一个TLAB的大小。

图5.12展示了简要的对象分配流程。首先,如果运行栈上分配,系统会先进行栈上分配,没有开启栈上分配或者不符合条件则会进行TLAB分配,如果TLAB分配不成功,再尝试在堆上分配,如果满足了直接进入老年代的条件(PretenureSizeThreshold 等参数)),就在老年代分配,否则就在eden区分配对象,当然,如果有必要,可能会进行一次新生代GC。
5.6温故又知新:常用的 GC参数
1.与串行回收器相关的参数
-XX:+UseSerialGC:在新生代和老年代使用串行收集器。
-XX:SurvivorRatio:设置eden区大小和 survivior区大小的比例。
-XX:PretenureSizeThreshold:设置大对象直接进入老年代的阈值。当对象的大小超过这个值时,将直接在老年代分配。
-XX:MaxTenuringThreshold:设置对象进入老年代的年龄的最大值。每一次 Minor GC后,对象年龄就加1。任何大于这个年龄的对象,一定会进入老年代。

2.与并行GC相关的参数
-XX:+UseParNewGC:在新生代使用并行收集器。
-XX:+UseParallelOldGC:老年代使用并行回收收集器。
-XX:ParallelGCThreads:设置用于垃圾回收的线程数。通过情况下可以和CPU数量相等,但在CPU 数量比较多的情况下,设置相对较小的数值也是合理的。
-XX:MaxGCPauseMillis:设置最大垃圾收集停顿时间。它的值是一个大于0的整数。收集器在工作时,会调整Java堆大小或者其他一些参数,尽可能地把停顿时间控制在MaxGCPauseMillis 以内。
-XX:GCTimeRatio:设置吞吐量大小。它的值是一个0到100 之间的整数。假设GCTimeRatio的值为n,那么系统将花费不超过1/(1+n)的时间用于垃圾收集。
-XX:+UseAdaptiveSizePolicy:打开自适应GC策略。在这种模式下,新生代的大小、eden和survivior 的比例、晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。

3.与CMS回收器相关的参数
-XX:+UseConcMarkSweepGC:新生代使用并行收集器,老年代使用CMS+串行收集器。-XX:ParallelCMSThreads:设定 CMS的线程数量。
-XX:CMSInitiatingOccupancyFraction:设置CMS 收集器在老年代空间被使用多少后触发,默认为68%。
-XX:+UseCMSCompactAtFullCollection:设置CMS 收集器在完成垃圾收集后是否要进行一次内存碎片的整理。
-XX:CMSFullGCsBeforeCompaction:设定进行多少次CMS垃圾回收后,进行一次内存压缩。
-XX:+CMSClassUnloadingEnabled:允许对类元数据区进行回收。
-XX:CMSInitiatingPermOccupancyFraction:当永久区占用率达到这一百分比时,启动CMS回收(前提是-XX:+CMSClassUnloadingEnabled激活了)。
-XX:UseCMSInitiatingOccupancyOnly:表示只在到达阈值的时候才进行CMS回收。-XX:+CMSIncrementalMode:使用增量模式,比较适合单CPU。增量模式在JDK8中标记为废弃,并且将在JDK 9中彻底移除。

4.与G1回收器相关的参数
-XX:+UseG1GC:使用G1回收器。
-XX:MaxGCPauseMillis:设置最大垃圾收集停顿时间。
-XX:GCPauseIntervalMillis:设置停顿间隔时间。

5.TLAB相关
-XX:+UseTLAB:开启 TLAB分配。
-XX:+PrintTLAB:打印TLAB相关分配信息。
-XX:TLABSize:设置TLAB大小。
-XX:+ResizeTLAB:自动调整TLAB大小。

6.其他参数
-XX:+DisableExplicitGC:禁用显式GC。
-XX:+ExplicitGCInvokesConcurrent:使用并发方式处理显式GC。
5.7动手才是真英雄:垃圾回收器对Tomcat性能影响的实验
压力工具 jmeter
针对某一重要接口,比如下单和支付,进行压力测试
测试中查看gc日志确定需要得内存大小
不断调整内存及回收器来测试不同得效果

第6章 性能监控工具
本章涉及的主要知识点有:
Linux下的性能监控工具。
Windows下的性能监控工具。
JDK自带的命令行工具。
JConsole、 Visual VM和 Mission Control的介绍。
第7章 分析Java堆
7.1 对症才能下药:找到内存溢出的原因
7.1.1 堆溢出
7.1.2直接内存溢出
直接内存不一定能够触发GC(除非直接内存使用量达到了-XX:MaxDirectMemorySize的设置),所以保证直接内存不溢出的方法是合理地进行Full GC的执行,或者设定一个系统实际可达的-XX:MaxDirectMemorySize值(默认情况下等于-Xmx的设置)。
7.1.3 过多线程导致OOM
由于每一个线程的开启都要占用系统内存,因此当线程数量太多时,也有可能导致OOM。由于线程的栈空间也是在堆外分配的,因此和直接内存非常相似,如果想让系统支持更多的线程,那么应该使用一个较小的堆空间。

循环创建线程系统抛出了0OM,并且打印出了“unable to create new nativethread”,表示系统创建线程的数量已经饱和,其原因是Java进程已经达到了可使用的内存上限。要解决这个问题,也可以从以下两方面下手:
(1)一个方法是可以尝试减少堆空间,如使用以下参数运行程序:-Xmx512m
使用512MB堆空间后,操作系统就可以预留更多内存用于线程创建,因此程序可以正常执行。
(2)另一个方法是减少每一个线程所占的内存空间,使用-Xss参数可以指定线程的栈空间。尝试以下参数: -Xmxlg -Xss128k

注意:如果减少了线程的栈空间大小,那么栈溢出的风险会相应地上升。
因此,处理这类OOM 的思路,除了合理的减少线程总数外,减少最大堆空间、减少线程的栈空间也是可行的
7.1.4永久区溢出
如果一个系统不断地产生新的类,而没有回收,那最终非常有可能导致永久区溢出
一般来说,要解决永久区溢出问题,可以从以下几个方面考虑:
增加 MaxPermSize的值。
减少系统需要的类的数量。
使用ClassLoader合理地装载各个类,并定期进行回收。
7.1.5 GC效率低下引起的OOM
虚拟机会评估GC的效率,一旦虚拟机认为GC 的效率过低,就有可能直接抛出OOM异常。
一般情况下,虚拟机会检查以下几种情况:
·花在GC上的时间是否超过了98%。
·老年代释放的内存是否小于2%。
eden区释放的内存是否小于2%。
是否连续最近5次GC都出现了上述几种情况(注意是同时出现,不是出现一个)。只有满足所有条件,虚拟机才有可能抛出如下OOM:
java.lang.OutOfMemoryError: GC overhead limit exceeded
7.2 无处不在的字符串:String在虚拟机中的实现
7.3虚拟机也有内窥镜:使用 MAT分析Java堆
MAT是 Memory Analyzer的简称,它是一款功能强大的Java堆内存分析器。可以用于查找内存泄露以及查看内存消耗情况。MAT是基于Eclipse开发的,是一款免费的性能分析工具。读者可以在 http://www.eclipse.org/mat/下载并使用MAT。
7.4筛选堆对象:MAT对OQL的支持
7.5更精彩的查找:Visual VM对OQL的支持
第8章 锁与并发
8.2避免残酷的竞争:锁在Java虚拟机中的实现和优化
8.2.1偏向锁
若某一锁被线程获取后,便进入偏向模式,当线程再次请求这个锁时,无需再进行相关的同步操作,从而节省了操作时间。如果在此之间有其他线程进行了锁请求,则锁退出偏向模式。在JVM中使用-XX:+UseBiasedLocking可以设置启用偏向锁。

偏向锁在锁竞争激烈的场合没有太强的优化效果,因为大量的竞争会导致持有锁的线程不停地切换,锁也很难一直保持在偏向模式,此时,使用锁偏向不仅得不到性能的优化,反而有可能降低系统性能。因此,在激烈竞争的场合,可以尝试使用-XX:-UseBiasedLocking参数禁用偏向锁。
8.2.2轻量级锁
8.2.3 锁膨胀
当轻量级锁失败,虚拟机就会使用重量级锁
8.2.4 自旋锁
在前文已经提到,锁膨胀后,进入ObjectMonitor的 enter(),线程很可能会在操作系统层面被挂起,这样线程上下文切换的性能损失就比较大。
在JDK 1.6中,Java虚拟机提供-XX:+UseSpinning参数来开启自旋锁,使用-XX:PreBlockSpin参数来设置自旋锁的等待次数。
在JDK 1.7中,自旋锁的参数被取消,虚拟机不再支持由用户配置自旋锁。自旋锁总是会执行,自旋次数也由虚拟机自行调整。
8.2.5 锁消除
锁消除是Java虚拟机在JIT编译时,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁。通过锁消除,可以节省毫无意义的请求锁时间。

逃逸分析和锁消除分别可以使用参数-XX:+DoEscapeAnalysis和-XX:+EliminateLocks开启(锁消除必须工作在-server模式下)。
8.3应对残酷的竞争:锁在应用层的优化思路
8.3.1减少锁持有时间
8.3.2减小锁粒度
8.3.3 锁分离
锁分离是减小锁粒度的一个特例,它依据应用程序的功能特点,将一个独占锁分成多个锁
8.3.4锁粗化
虚拟机在遇到一连串连续地对同一锁不断进行请求和释放的操作时,便会把所有的锁操作整合成对锁的一次请求,从而减少对锁的请求同步次数。这个操作叫做锁的粗化。
8.4无招胜有招:无锁
8.4.1理解CAS
8.4.2原子操作
为了能让CAS操作被Java应用程序充分使用,在JDK的java.util.concurrent.atomic包下,有一组使用无锁算法实现的原子操作类,主要有 AtomicInteger、AtomicIntegerArray ,AtomicLong、AtomicLongArray和 AtomicReference等。它们分别封装了对整数、整数数组、长整型、长整型数组和普通对象的多线程安全操作。
8.5将随机变为可控:理解Java内存模型
8.5.1原子性
8.5.2有序性
8.5.3可见性
8.5.4 Happens-Before原则
第9章Class文件结构

第10章Class装载系统
10.1.1类装载的条件
Class只有在必须要使用的时候才会被装载,Java虚拟机不会无条件地装载Class类型。Java虚拟机规定,一个类或接口在初次使用前,必须要进行初始化。这里指的“使用”,是指主动使用,主动使用只有下列几种情况:
·当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化。
·当调用类的静态方法时,即当使用了字节码invokestatic指令。
当使用类或接口的静态字段时(final常量除外),比如,使用getstatic或者 putstatic指令。
当使用java.lang.reflect包中的方法反射类的方法时。
·当初始化子类时,要求先初始化父类。
·作为启动虚拟机,含有main(方法的那个类。
10.1.2 加载类
加载类处于类装载的第一个阶段。在加载类时,Java虚拟机必须完成以下工作:
通过类的全名,获取类的二进制数据流。
解析类的二进制数据流为方法区内的数据结构。
创建java.lang.Class类的实例,表示该类型。
10.2一切Class从这里开始:掌握 ClassLoader
10.2.2 ClassLoader的分类
在标准的Java程序中,Java虚拟机会创建3类ClassLoader为整个应用程序服务。它们分别是: BootStrap ClassLoader(启动类加载器)、Extension ClassLoader(扩展类加载器)和AppClassLoader(应用类加载器,也称为系统类加载器)。此外,每一个应用程序还可以拥有自定义的ClassLoader,扩展Java虚拟机获取 Class数据的能力。
第11章 字节码执行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值