说明本文是阅读《Java性能权威指南》的第五章节梳理内容,不是本人原创,觉得写的非常好,特意总结下,所以有了下文。
1.GC作用
主要做三件事情:
- 查找不可用的对象。
不能采用简单的引用计数功能,因为有类似于双向链表的将无法清除。 - 回收垃圾对象占用内存
不同的垃圾回收器,采用不同的算法,采用单线程或多线程来回收垃圾对
象。 - 内存碎片的整理
将回收后的对象进行压缩形成大的空闲区域。
2.垃圾回收共有特性
均采用分代回收形式
分代可以将堆空间划分成不同的代,减少每个代的大小,有利于扫描垃圾对象。所有的GC回收算法在新生代都会暂停所有应用程序线程
新生代直接涉及到的是应用程序对象的分配,在分配的时候,会更改对象的空间。** 新生代的垃圾回收称为Minor GC**
新生代初始分配空间为Enden,要么对象移到老年代,要么移到Survior空间。** 老年代空间即将占满时候回收,采用不同垃圾回收算法**
简单垃圾回收算法: 暂停应用程序线程,直接进行Full GC。
CMS/G1: 尽量减少停顿,会占用更多CPU称为Concurrent收集器。
3. GC垃圾回收算法选择
1.使用内存小于100M
使用Serial 垃圾收集器。
2. Concurrent or Throughput
单核心CPU或者cpu很忙:利用Throughput,处理应用程序的批处理任务,通常可以获得更好的性能,因为在cpu很忙的时候,CMS多线程和应用程序争夺资源会造成系统运行更慢。
多核CPU或CPU资源不忙:
采用Concurrent收集器可以获得更好的性能。
3. CMS 和 Throughput 的CPU利用率特点:
Throughput: 在进行Full GC或Minor GC 都会占用100%cpu;在90%和99%的响应时间上更有优势,如果超负荷运行大量Full GC,则切换到Concurrent性能更好。
CMS: 在后台垃圾回收线程和应用程序线程同时运行,CPU利用率更平滑,偶尔达到100%。在CPU资源不够时候,可能会退化到Serial方式,CPU占不满,性能比Throughput 差。
4. CMS VS G1
基本原则:堆内存小于4G情况下,建议用CMS ,性能更好;大型堆采用G1更好。
CMS
CMS后台线程需要扫描整个老年代内存,扫描时间与堆大小关系密切,如果在堆未填满之前,CMS后台线程停止扫描,直接回收对象,则发生并发失效。一旦发生了Concurrent Mode Failure,则处理Full GC的只有一个线程,性能损耗严重,CMS并发模式失效同时也会受到程序内存分配的影响。另外堆的碎片化也会导致Full GC。
G1
将堆分为多个区域,更利于使用多线程分担扫描老年代,如果后台线程跟不上处理速度也会发生并发模式失效的问题;也会发生堆的内存碎片化,不过比CMS要好。
此外说明:Throughput 比CMS、G1 更老,所以经过长期验证,稳定性,可调整参数更多,相反G1新的垃圾回收器,容易遇到极端情况多。
5. GC调优基础
内存大小调节
堆大小设置:
设置过小:导致一直在GC,程序执行速度慢。
设置过大:
1.导致垃圾回收时间长,停顿时间短,但是持续时间会让程序整体性能变慢。
2.JVM虚拟内存如果占用了系统的虚拟内存,Full GC性能将会非常糟糕。
堆设置的首要原则
永远不要将堆的总大小设置超过机器的物理内存,特别是有多个JVM在跑的时候,通常情况对于普通的操作系统需要预留1G的内存给操作系统。
堆大小控制参数
-XmsN 设置初始值
-XmxN 设置最大值
默认值取决于操作系统、系统内存大小、JVM和命令行标志,堆大小调节是JVM自适应调节核心。
堆默认值
经验法则:
一次Full GC后尽量保持70%内存可用。
设置方法:在稳定运行程序的时候通过Jconsole连接程序进行Full GC观察有多少内存被占用。
初始值和最大值设置一样可以稍微提高GC运行效率,调整尽量调整算法。
代空间调整
需要调整的原因:给新生代分配的比较大,垃圾收集的频率降低,从新生代晋级的也比较少,但是相应的老年代的相对较小,比较容易被填满,从而造成Full GC,所以需要找个平衡点。
所有的GC算法都采用同一套标志来设置代的大小:
-XX:NewRatio=N
设置新生代与老年代的空间占比率
-XX:NewSize= N
设置新生代的空间大小
-XX:MaxNewSize=N
设置新生代空间的最大大小
-XmnN
将新生代的NewSize和MaxNewSize设置为同一个值。
最初新生代空间由NewRadtio决定,默认设置为2.
新生代空间= 初始堆大小/(1+NewRadio) 即新生代空间为初始堆的33%。
NewSize 比NewRadio优先级更高。
建议: 如果堆大小固定,则新生代可以设置为固定值;否则可以设置为按照比例。
整个堆空间的划分是由新生代的大小来决定的。
永生代/元空间调整
- Java7保存的是类元数据信息,称为永久代,还保存一些类无关的杂项信息。
- Java8改名永生代为元空间只对编译器和JVM运行时候有用。
永久代/元空间默认大小:
对永久代而言:
可以通过-XX:PermSize=N / -XX:MaxPermSize=N类设置
对于元空间而言:
可以通过-XX:MetaspaceSize=N/ -XX:MaxMetaspaceSize= N 来设置
增加启动速度可以通过增加用具带或元空间达到,特别是才启动有大量Full GC,
通常设置为128MB/192MB
同样会被垃圾回收,不是永久存在。
比如类加载器,通过jmap –permstat 或-clstats来查看输出类加载器信息。
6. GC的并发控制
控制参数: -XX:ParallelGCThreads=N
影响下面的线程数目:
-XX:+UseParallelGC 收集新生代空间
-XX:+UseParallelOldGC 收集老生代空间
-XX:+UseNewParNewGC: 收集新生代空间
-XX:+UseG1GC 收集新生代空间
CMS收集器的STW阶段(非Full GC)
G1收集器的STW阶段(非Full GC)
GC默认线程数:
1、在小于8个cpu的时候,一个cpu启动一个线程。
2、一旦超过8个则:parallelGCThread= 8 + ((N-8)*5/8)
3、在8核或更少核心,JVM 100%占用cpu,在更多核心上,运行垃圾回收器会更多占用cpu,例如一个16核心cpu,如果运行4个JVM,则默认一个13个线程,则严重影响程序性能,建议每个JVM设置4个垃圾回收线程比较合理。
7.自适应调节
JVM自动运行的时候,根据默认的指标自动调整代的大小尽量的满足目标。
好处:
1、以为小型程序不需要为指定过大的堆而担心。
2、不需要担心堆大小,JVM自动根据优化目标调整堆的大小。
3、缺点:调整需要时间开销,如果已经做了优化,可以考虑关闭自适应调整。
通过-XX:+UseAdaptiveSizePolicy 在全局范围关闭自适应调整功能。
如果堆最大和最小设置相同,新生代的最大最小设置相同,则将会自动关闭自适应调整。
4、如果设置-XX:+PrintAdaptiveSizePolicy标志,则垃圾回收日志打印不同代的调整细节
8.垃圾回收工具
1、判断垃圾回收性能好坏需要日志
-verbose:gc 或-XX:+PrintGC 这两个标示中的任意一个可以创建基本的GC日志
2、详细垃圾回收日志
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps 或-XX:+PrintGCDateStamps 便于查看几次垃圾回收操作之间的时间。
前者时间戳是基于0(JVM启动时间)的;后者基于真正日期,需要耗费更多性能。
3、垃圾回收结果输出到日志:
-Xloggc:filename
-XX:+UseGCLogfileRotation –XX:NumberOfGCLogFileRotation=N –XX:GCLogfileSize=N
来设置gc日志轮询打印和轮询打印的日志文件数、日志文件大小。
4、利用工具查看日志
GC Histogram 读取日志生成图形表格
Jconsole 内存板可以查看堆的使用情况。
Jstat –gcutil 能够打印各个区的占用百分比和垃圾回收情况,
Jstat –gcutil process_id 1000 【每隔1s打印一次情况】