一、哪些内存可以回收
JVM 的内存区域中,程序计数器、虚拟机栈和本地方法栈这 3 个区域是线程私有的,随着线程的创建而创建,销毁而销毁,因此不用回收。
垃圾回收的重点就是关注堆和方法区中的内存了,堆中的回收主要是对象的回收,方法区的回收主要是废弃常量和无用的类的回收。
二、什么时候可以被回收
一般一个对象不再被引用,就代表该对象可以被回收。目前有以下两种算法可以判断该对象是否可以被回收:
1、引用计数算法
这种算法是通过一个对象的引用计数器来判断该对象是否被引用了。每当对象被引用,引用计数器就会加 1;每当引用失效,计数器就会减 1。
当对象的引用计数器的值为 0 时,就说明该对象不再被引用,可以被回收了。
虽然引用计数算法的实现简单,判断效率也很高,但它存在着对象之间相互循环引用的问题。
2、可达性分析算法
GC Roots 是所有对象的根引用。在垃圾回收时,会从这些 GC Roots 开始向下搜索,当一个对象到 GC Roots 没有任何引用链相连时,就证明此对象可以被回收。
目前 HotSpot 虚拟机采用的就是这种算法。
2.1、可作为GC Roots的对象:
Java虚拟机栈中的引用的对象 ;本地方法栈中JNI的引用的对象 ;方法区中的类静态属性、常量对象 。
以上两种算法都是通过引用来判断对象是否可以被回收。在 JDK 1.2 之后,Java 对引用的概念进行了扩充,将引用分为了以下四种:
三、回收算法
四、回收器/类型
五、GC 性能衡量指标
吞吐量:这里的吞吐量是指应用程序所花费的时间和系统总运行时间的比值。我们可以按照这个公式来计算 GC 的吞吐量:系统总运行时间 = 应用程序耗时 +GC 耗时。如果系统运行了 100 分钟,GC 耗时 1 分钟,则系统吞吐量为 99%。
停顿时间:指垃圾收集器正在运行时,应用程序的暂停时间。对于串行回收器而言,停顿时间可能会比较长;而使用并发回收器,由于垃圾收集器和应用程序交替运行,程序的停顿时间就会变短,但其效率很可能不如独占垃圾收集器,系统的吞吐量也很可能会降低。
六、查看 & 分析 GC 日志
首先,我们需要通过 JVM 参数预先设置 GC 日志,通常有以下几种 JVM 参数设置:
-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 日志文件的输出路径
eg:-XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:/log/heapTest.log
如果GC日志非常大,可以用GCViewer工具打开日志文件,图形化界面查看整体的 GC 性能,如下图所示:
也可以用GCeasy工具,并且还可以将日志文件压缩之后,上传到 GCeasy 官网即可看到非常清楚的 GC 日志分析结果
七、回收调优
1. 降低 Minor GC 频率
Eden区存活的对象数量小,可以通过增大新生代空间来降低 Minor GC 的频率。
通常情况下,由于新生代空间较小,Eden 区很快被填满,就会导致频繁 Minor GC,因此我们可以通过增大新生代空间来降低 Minor GC 的频率。
可能你会有这样的疑问,扩容 Eden 区虽然可以减少 Minor GC 的次数,但不会增加单次 Minor GC 的时间吗?如果单次 Minor GC 的时间增加,那也很难达到我们期待的优化效果呀。
如果在堆内存中存在较多的长期存活的对象,此时增加年轻代空间,反而会增加 Minor GC 的时间。如果堆中的短期对象很多,那么扩容新生代,单次 Minor GC 时间不会显著增加。因此,单次 Minor GC 时间更多取决于 GC 后存活对象的数量,而非 Eden 区的大小。
2、降低 Full GC 的频率
2.1、减少创建大对象
2.2、增大堆内存空间
2.3、选择合适的 GC 回收器
垃圾收集器的种类很多,我们可以将其分成两种类型,一种是响应速度快,一种是吞吐量高。通常情况下,CMS (Concurrent Mark Sweep)和 G1 回收器的响应速度快,Parallel Scavenge 回收器的吞吐量高。
ps1:首先cms在1.9已经被标记为废弃,主要原因在于标记清除下的悬浮内存,导致内存空间碎片化,进而导致fullGC的发生。不过其并行执行垃圾回收的性能还是值得认可的,至少1.9后主推的G1在常规情况下也是不如它的效率好的。
ps2:G1与CMS的优势在于以下几点:
1、并行与并发:G1能够更充分利用多CPU、多核环境运行
2、分代收集:G1虽然也用了分代概念,但相比其他收集器需要配合不同收集协同工作,但G1收集器能够独立管理整个堆
3、空间管理:与CMS的标记一清理算法不同,G1从整体上基于标记一整理算法,将整个Java堆划分为多个大小相等的独立区域(Region),这种算法能够在运行过程中不产生内存碎片
4、可预测的停顿:降低停顿时间是G1和CMS共同目标,但是G1追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集器上的时间不得超过N毫秒。
eg:通过XX:MaxGCPauseMillis =500可以设置full gc 稳定在500ms以内
ps3:oracle一些1.9 10 11 12的特性都有以补丁的方式落到1.8。所以1.8还是比较安全实用的