垃圾收集(Garbage Collection,GC),它的任务是解决如下 3 件问题:java
其中第一个问题很好回答,在 Java 中,GC 主要发生在 Java 堆和方法区中,对于后两个问题,咱们将在以后的内容中进行讨论,并介绍 HotSpot 的 7 个垃圾收集器。git
何时回收对象?固然是这个对象不再会被用到的时候回收。因此要想解决 “何时回收?” 这个问题,咱们要先能判断一个对象何时何时真正的 “死” 掉了,判断对象是否可用主要有如下两种方法。github
objA.instance = objB; objB.instance = objA;
,objA 和 objB 都不会再被访问后,它们仍然相互引用着对方,因此它们的引用计数器不为 0,将永远不能被判为不可用。即使如此,一个对象也不是一旦被判为不可达,就当即死去的,宣告一个的死亡须要通过两次标记过程。算法
JDK 1.2 后,Java 中才有了后 3 种引用的实现。安全
Object obj = new Object()
这种,只要强引用还存在,垃圾收集器就永远不会回收掉被引用的对象。SoftReference
。WeakReference
。PhantomReference
。finalize()
方法的判断;
finalize()
方法,或者 finalize()
方法已被执行过(finalize()
只被执行一次);finalize()
方法是对象逃脱死亡的最后一次机会,不过虚拟机不保证等待 finalize()
方法执行结束,也就是说,虚拟机只触发 finalize()
方法的执行,若是这个方法要执行超久,那么虚拟机并不等待它执行结束,因此最好不要用这个方法。finalize()
方法能作的,try-finally 都能作,因此忘了这个方法吧!永久代的 GC 主要回收:废弃常量 和 无用的类。数据结构
算法描述:多线程
不足: 可用内存缩小为原来的一半,适合GC事后只有少许对象存活的新生代。架构
节省内存的方法:并发
-XX:SurvivorRatio=8
表示 Eden 区大小 / 1 块 Survivor 区大小 = 8
。经过前两小节对于判断对象生死和垃圾收集算法的介绍,咱们已经对虚拟机是进行 GC 的流程有了一个大体的了解。可是,在 HotSpot 虚拟机中,高效的实现这些算法也是一个须要考虑的问题。因此,接下来,咱们将研究一下 HotSpot 虚拟机究竟是如何高效的实现这些算法的,以及在实现中有哪些须要注意的问题。jvm
经过以前的分析,GC 算法的实现流程简单的来讲分为如下两步:
想要找到死掉的对象,咱们就要进行可达性分析,也就是从 GC Root 找到引用链的这个操做。
也就是说,进行可达性分析的第一步,就是要枚举 GC Roots,这就须要虚拟机知道哪些地方存放着对象应用。若是每一次枚举 GC Roots 都须要把整个栈上位置都遍历一遍,那可就费时间了,毕竟并非全部位置都存放在引用呀。因此为了提升 GC 的效率,HotSpot 使用了一种 OopMap 的数据结构,OopMap 记录了栈上本地变量到堆上对象的引用关系,也就是说,GC 的时候就不用遍历整个栈只遍历每一个栈的 OopMap 就好了。
在 OopMap 的帮助下,HotSpot 能够快速准确的完成 GC 枚举了,不过,OopMap 也不是万年不变的,它也是须要被更新的,当内存中的对象间的引用关系发生变化时,就须要改变 OopMap 中的相应内容。但是能致使引用关系发生变化的指令很是之多,若是咱们执行完一条指令就改下 OopMap,这 GC 成本实在过高了。
所以,HotSpot 采用了一种在 “安全点” 更新 OopMap 的方法,安全点的选取既不能让 GC 等待的时间过长,也不能过于频繁增长运行负担,也就是说,咱们既要让程序运行一段时间,又不能让这个时间太长。咱们知道,JVM 中每条指令执行的是很快的,因此一个超级长的指令流也可能很快就执行完了,因此 真正会出现 “长时间执行” 的通常是指令的复用,例如:方法调用、循环跳转、异常跳转等,虚拟机通常会将这些地方设置为安全点更新 OopMap 并判断是否须要进行 GC 操做。
此外,在进行枚举根节点的这个操做时,为了保证准确性,咱们须要在一段时间内 “冻结” 整个应用,即 Stop The World(传说中的 GC 停顿),由于若是在咱们分析可达性的过程当中,对象的引用关系还在变来变去,那是不可能获得正确的分析结果的。即使是在号称几乎不会发生停顿的 CMS 垃圾收集器中,枚举根节点时也是必需要停顿的。这里就涉及到了一个问题:
咱们让全部线程跑到最近的安全点再停顿下来进行 GC 操做呢?
主要有如下两种方式:
除此安全点以外,还有一个叫作 “安全区域” 的东西,一个一直在执行的线程能够本身 “走” 到安全点去,但是一个处于 Sleep 或者 Blocked 状态的线程是没办法本身到达安全点中断本身的,咱们总不能让 GC 操做一直等着这些个 ”不执行“ 的线程从新被分配资源吧。对于这种状况,咱们要依靠安全区域来解决。
安全区域是指在一段代码片断之中,引用关系不会发生变化,所以在这个区域中的任意位置开始 GC 都是安全的。
当线程执行到安全区域时,它会把本身标识为 Safe Region,这样 JVM 发起 GC 时是不会理会这个线程的。当这个线程要离开安全区域时,它会检查系统是否在 GC 中,若是不在,它就继续执行,若是在,它就等 GC 结束再继续执行。
本小节咱们主要讲述 HotSpot 虚拟机是如何发起内存回收的,也就是如何找到死掉的对象,至于如何清掉这些个对象,HotSpot 将其交给了一堆叫作 ”GC 收集器“ 的东西,这东西又有好多种,不一样的 GC 收集器的处理方式不一样,适用的场景也不一样,咱们将在下一小节进行详细讲述。
垃圾收集器就是内存回收操做的具体实现,HotSpot 里足足有 7 种,为啥要弄这么多,由于它们各有各的适用场景。有的属于新生代收集器,有的属于老年代收集器,因此通常是搭配使用的(除了万能的 G1)。关于它们的简单介绍以及分类请见下图。
Serial 收集器是虚拟机在 Client 模式下的默认新生代收集器,它的优点是简单高效,在单 CPU 模式下很牛。
ParNew 收集器就是 Serial 收集器的多线程版本,虽然除此以外没什么创新之处,但它倒是许多运行在 Server 模式下的虚拟机中的首选新生代收集器,由于除了 Serial 收集器外,只有它能和 CMS 收集器搭配使用。
首先,这俩货确定是要搭配使用的,不只仅如此,它俩还贼特别,它们的关注点与其余收集器不一样,其余收集器关注于尽量缩短垃圾收集时用户线程的停顿时间,而 Parallel Scavenge 收集器的目的是达到一个可控的吞吐量。
吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时间 )
所以,Parallel Scavenge 收集器无论是新生代仍是老年代都是多个线程同时进行垃圾收集,十分适合于应用在注重吞吐量以及 CPU 资源敏感的场合。
可调节的虚拟机参数:
-XX:MaxGCPauseMillis
:最大 GC 停顿的秒数;-XX:GCTimeRatio
:吞吐量大小,一个 0 ~ 100 的数,最大 GC 时间占总时间的比率 = 1 / (GCTimeRatio + 1)
;-XX:+UseAdaptiveSizePolicy
:一个开关参数,打开后就无需手工指定 -Xmn
,-XX:SurvivorRatio
等参数了,虚拟机会根据当前系统的运行状况收集性能监控信息,自行调整。参数设置:
-XX:+UseCMSCompactAtFullCollection
:在 CMS 要进行 Full GC 时进行内存碎片整理(默认开启)-XX:CMSFullGCsBeforeCompaction
:在多少次 Full GC 后进行一次空间整理(默认是 0,即每一次 Full GC 后都进行一次空间整理)关于 CMS 使用 标记 - 清除 算法的一点思考:
以前对于 CMS 为何要采用 标记 - 清除 算法十分的不理解,既然已经有了看起来更高级的 标记 - 整理 算法,那 CMS 为何不用呢?最近想了想,感受多是这个缘由,不过也不是很肯定,只是我的的一种猜想。
标记 - 整理 会将全部存活对象向一端移动,而后直接清理掉边界之外的内存。这就意味着须要一个指针来维护这个分隔存活对象和无用空间的点,而咱们知道 CMS 是并发清理的,虽然咱们启动了多个线程进行垃圾回收,不过若是使用 标记 - 整理 算法,为了保证线程安全,在整理时要对那个分隔指针加锁,保证同一时刻只有一个线程能修改它,加锁的这一过程至关于将并行的清理过程变成了串行的,也就失去了并行清理的意义了。
因此,CMS 采用了 标记 - 清除 算法。
原文:Java架构笔记
免费Java高级资料须要本身领取,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G。
传送门: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q