1、什么是垃圾回收
程序的运行必然需要申请内存资源,无效的对象如果不及时清理就会一直占用内存资源,此时无效对象已成为垃圾,需要回收,垃圾回收就是对内存资源进行管理。java语言依赖虚拟机自动对垃圾进行回收。
2、垃圾回收常见算法
自动化的内存资源管理必定需要一套算法来进行计算,哪些是有效的对象,哪些是无效的对象。
常见的垃圾回收算法有引用计数法,标记清除,标记压缩,复制算法,分代算法等。
2.1 引用计数法
引用计数法是历史最悠久的一种算法,直到今天依然被很多编程语言使用。
- 优点
实时性较高,无需等到内存不够的时候,才开始回收,运行时根据对象的计数器是否为0,就可以直接回收。
在垃圾回收过程中,应用无需挂起。如果申请内存时,内存不足,则立刻报outofmember 错误。
区域性,更新对象的计数器时,只是影响到该对象,不会扫描全部对象。 - 缺点
无法解决循环引用问题。
2.2 标记清除法
标记清除算法是将垃圾回收分为两个阶段,分别是标记和清除。 - 标记,从根节点开始标记引用的对象。
- 清除,未被标记的对象就是垃圾对象,可以清除。
优缺点:
解决了循环引用的问题,没有从root节点引用的对象都会被回收。
缺点:效率低。标记和清除两步都要遍历所有对象,并且需要STW。
标记清除出来的内存,碎片化比较严重。
2.3 标记压缩算法
标记压缩算法对标记清除算法做了改进。在标记阶段也是和根节点开始,对对象的引用进行标记,在清理阶段并不是简单的清理,而是把存活对象压缩到内存一端,解决碎片化问题。
2.4 复制算法
复制算法的核心就是将内存一分为二,每次只使用其中一块,垃圾回收时,将仍然存活的对象复制到另一块内存,然后将内存空间清理。适用于需要清理的垃圾对象较多的情形,否则需要复制的对象太多。
2.5 分代算法
在jvm中,年轻代适合使用复制算法,老年代适合使用标记清除或标记压缩算法。
3、垃圾收集器以及内存分配
有了垃圾回收算法,还需要有具体的实现,在jvm中实现了:串行垃圾收集器、并行垃圾收集器、CMS(并发)垃圾收集器、G1垃圾收集器。
3.1 串行垃圾收集器
串行垃圾收集器指的是使用单线程进行垃圾回收,垃圾回收时只有一个线程在工作,并且java应用中所有线程都要暂停等待垃圾回收完成。STW
一般交互性强的应用不会采用。
- -XX:+UseSerialGC
指定年轻代和老年代都使用串行垃圾收集器 - -XX:+PrintGCDetails
打印垃圾回收的详细信息
示例:-XX:+UseSerialGC -XX:+PrintGCDetails -Xms16m -Xmx16m
3.2 并行垃圾收集器
并行垃圾收集器对串行垃圾收集器做了改进,将单线程改为多线程进行垃圾回收,垃圾回收过程中也会暂停应用程序,只是速度更快,暂停时间更短一些。
3.2.1 ParNew垃圾收集器
ParNew垃圾收集器工作在年轻代,只是将串行的垃圾回收改成并行。通过-XX:+UseParNewGC参数设置年轻代使用ParNew回收器,老年代使用的依然是串行收集器。
3.2.2 ParallelGC垃圾收集器
ParallelGC收集器工作机制和ParNewGC收集器一样,只是在此基础之上,新增了两个和系统吞吐量相关的参数,使得其使用起来更加的灵活和高效。 - -XX:+UseParallelGC
年轻代使用ParallelGC垃圾回收器,老年代使用串行回收器。 - -XX:+UseParallelOldGC
年轻代使用ParallelGC垃圾回收器,老年代使用ParallelOldGC垃圾回收器。 - -XX:MaxGCPauseMillis
设置最大的垃圾收集时的停顿时间,单位为毫秒
需要注意的时,ParallelGC为了达到设置的停顿时间,可能会调整堆大小或其他的参数,如果堆的大小
设置的较小,就会导致GC工作变得很频繁,反而可能会影响到性能。该参数使用需谨慎。 - -XX:GCTimeRatio
设置垃圾回收时间占程序运行时间的百分比,公式为1/(1+n)。它的值为0~100之间的数字,默认值为99,也就是垃圾回收时间不能超过1% - -XX:UseAdaptiveSizePolicy
自适应GC模式,垃圾回收器将自动调整年轻代、老年代等参数,达到吞吐量、堆大小、停顿时间之间的平衡。一般用于,手动调整参数比较困难的场景,让收集器自动进行调整。
3.3 CMS垃圾收集器
CMS全称 Concurrent Mark Sweep,是一款并发的、使用标记-清除算法的垃圾回收器,用于老年代的垃圾回收。通过参数-XX:+UseConcMarkSweepGC进行设置。
CMS垃圾收集器过程如下:
- 初始化标记,标记root,会导致STW
- 并发标记,与用户线程同时进行
- 预处理,与用户线程同时进行
- 重新标记,会导致STW
- 并发清理,与用户线程同时进行
- 调整堆大小,CMS在清理会后进行内存压缩,目的是清理内存中的碎片
- 重置,等待下次CMS的触发,与用户线程同时进行
设置启动参数:-XX:+UseConcMarkSweepGC
3.4 G1垃圾收集器
G1是jdk1.7开始正式使用的全新垃圾收集器,oracle官网计划jdk1.9将G1变成默认垃圾收集器。
G1的设计原则就是简化内存调优: - 开启G1收集器
- 设置对内存大小
- 设置最大停顿时间
G1提供了三种垃圾收集模式,Young GC,
Mixed GC,Full GC。
3.4.1 原理
G1垃圾收集器和其他垃圾收集器的最大不同,取消了年轻代、老年代在物理上的划分,取而代之的是将堆分成若干个区域,这些区域包含了有逻辑上的年轻代、老年代。
在G1划分的区域中,年轻代的垃圾收集器依然采用了暂停所有用户线程的方式,将存活对象拷贝到老年代或者Survivor区,G1通过将对象从一个区域复制到另一个区域完成了清理工作,这也意味着在正常的清理过程中,G1完成了部分堆的压缩工作。
G1中有个特殊区域,叫Humongous区域。 - 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。
- 这些巨型对象,默认直接会被分配在老年代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。
- 为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。
3.4.2 Young GC
Young GC主要对Eden去进行GC,他在Eden空间耗尽时触发。 - Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代
空间。 - Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。
- 最终Eden空间的数据为空,GC停止工作,应用线程继续执行。
Remembered Set(已记忆集合)
根对象可能存在于年轻代中也可能存在于老年代中,如果要对对象进行全量扫描会话费比较多的时间。G1引进了Remembered Set的概念。作用是跟踪指向某个堆内对象的引用。
3.4.3 Mixed GC
当越来越多的对象晋升到老年代时,为了避免堆内存耗尽,虚拟机会触发Mixed GC,该算法除了回收年轻代还会回收一部分老年代对象。触发的时机由参数由 -XX:InitiatingHeapOccupancyPercent=n 决定。默认:45%,意思是老年代大小占整个堆内存大小达到一定阈值时触发。
分为两步: - 全局并发标记
初始标记(initial mark,STW)
根区域扫描(root region scan)
并发标记(Concurrent Marking)
重新标记(Remark,STW)
清除垃圾(Cleanup,STW) - 拷贝存活对象
Evacuation阶段是全暂停的,通过把一部分Region中对象拷贝到另一部分Region中,实现垃圾回收。
3.4.4 G1垃圾回收器相关参数 - -XX:+UseG1GC
使用G1垃圾收集器 - -XX:MaxGCPauseMillis
设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到),默认值是 200 毫秒。 - -XX:G1HeapRegionSize=n
设置的 G1 区域的大小。值是 2 的幂,范围是 1 MB 到 32 MB 之间。目标是根据最小的 Java 堆大小划
分出约 2048 个区域。
默认是堆内存的1/2000。 - -XX:ParallelGCThreads=n
设置 STW 工作线程数的值。将 n 的值设置为逻辑处理器的数量。n 的值与逻辑处理器的数量相同,最多为 8。 - -XX:ConcGCThreads=n
设置并行标记的线程数。将 n 设置为并行垃圾回收线程数 (ParallelGCThreads) 的 1/4 左右。 - -XX:InitiatingHeapOccupancyPercent=n
设置触发标记周期的 Java 堆占用率阈值。默认占用率是整个 Java 堆的 45%。
示例:-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:+PrintGCDetails -Xmx256m
注意点:
不要显式设置年轻代大小。
暂停时间不要太苛刻。
G1 GC 的吞吐量目标是 90% 的应用程序时间和 10%的垃圾回收时间。
4、可视化日志分析工具
可以使用GC Easy工具进行分析。地址:
http://gceasy.io/
需要将GC日志打印到文件中,使用第三方工具进行分析。
- -XX:+PrintGC 输出GC日志
- -XX:+PrintGCDetails 输出GC的详细日志
- -XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
- -XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
- -XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
- -Xloggc:…/logs/gc.log 日志文件的输出路径
示例:
-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -Xmx256m
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps -XX:+PrintHeapAtGC
-Xloggc:D://g1.log
可以与CMS垃圾回收器进行比较:
-XX:+UseConcMarkSweepGC -Xmx256m
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps -XX:+PrintHeapAtGC
-Xloggc:D://cms.log