什么是引用计数器法
在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。
缺点:无法解决对象之间循环引用的问题。例如,当二个对象相互引用时,并且这二个对象也不可能再被访问,那么这二个对象将永远存在于内存当中不会被回收。
引用计数器算法有一些比较著名的应用案例,但是Java虚拟机并没有采用这种算法。
1.什么是可达性分析算法
可达性分析算法是通过一系列称为"GC Roots"的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为"引用链",如果某个对象到GC Roots间没有任何引用链相连,则证明此对象是不可能被使用的。
2.可以作为 GC Roots 的对象
除了上述这些固定的GC Roots集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其它对象临时性的加入,共同构成完整的GC Roots集合。例如后面会提到的分代收集和局部回收,如果只针对Java堆中某一块区域发起垃圾收集(例如只针对新生代的垃圾收集),而这个区域的对象完全有可能被位于堆中其他区域的对象所引用,这时候就需要将这些关联区域的对象一并加入GC Roots集合中。
3.四种引用
强引用:
软引用:
弱引用:
虚引用:
注意
1.上述前四种引用的引用强度从上到下依次减弱
2.除上述四种引用外,还有一种无须手动编码的引用,称为终结器引用
终结器引用:无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象
1.分代收集理论
当前商业虚拟机的垃圾收集器,大多数都遵循了分代收集的理论进行设计,分代收集名为理论,实质上是一套符合大多数程序运行实际情况的经验法则,它建立在二个分代假说之上:
弱分代假说:绝大多数对象是朝生夕灭的
强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡
这二个分代假说奠定了多款常用的垃圾收集器的一致的设计原则:收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。显而易见,如果一个区域中大多数对象都是朝生夕灭,难以熬过垃圾收集过程的话,那么把它们集中放在一起,每次回收时只关注如何保留少量存活而不是去标记那些大量将要被回收的对象,就能以较低代价回收到大量的空间;如果剩下的都是难以消亡的对象,那把它们集中放在一块,虚拟机便可以使用较低的频率来回收这个区域。
把分代收集理论放到现在的商用Java虚拟机里,设计者一般至少会把Java堆划分为新生代和老年代二个区域。顾名思义,新生代中,每次垃圾回收都有大批对象死去,而每次回收后存活的少量对象,将会逐步晋升到老年代中存放。针对新生代或老年代或整个堆的收集则有如下不同的名词:
部分收集
整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集
注意
1.但是对象之间很可能是存在跨代引用的。例如,新生代中的对象有可能被老年代中的对象所引用,具体解决方法见<<深入理解Java虚拟机>>
2.在HotSpot中,新生代内存不足,会触发Minor GC,使用新生代垃圾回收器完成垃圾回收;老年代内存不足,会触发Full GC,使用新生代垃圾回收器完成新生代的垃圾回收,使用老年代垃圾回收器完成老年代的垃圾回收
2.HotSpot的分代垃圾回收
对象首先分配在伊甸园区域
新生代空间不足时,触发 minor gc,伊甸园和 from 存活的对象使用 copy 复制到 to 中,存活的对象年龄加 1 并且将幸存区的 from 和 to 交换
minor gc 会引发 stop the world,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行
当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit)
当老年代空间不足,会先尝试触发 minor gc,如果之后空间仍不足,那么触发 full gc,STW的时 间更长
什么是标记清除
标记清除算法分为标记和清除两个阶段:首先需要标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。
优点:
缺点:
什么是标记整理算法
首先标记出所有需要回收的对象,也可以反过来,标记存活的对象,然后让所有存活的对象向内存空间一端移动,最后直接清理掉边界以外的内存,
优点
不会产生内存碎片
缺点
相比于垃圾清除算法速度慢
什么是标记复制算法
它将可用内存按容量划分为大小相等的二块,每次只使用其中的一块。当这一块的内存用完了,就将还活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
优点
不会有内存碎片
缺点
需要占用双倍内存空间
现在的商用Java虚拟机优先采用了这种收集算法去回收新生代。但是,IBM曾有一项研究对新生代的朝生夕死的特点做了一个更量化的诠释:新生代中的对象有98%熬不过第一轮收集。因此,并不需要1:1的比例划分新生代的内存空间
什么是垃圾回收器
垃圾收集算法是内存回收的方法论,而垃圾回收器是内存回收的实践者。《Java虚拟机规范》中对垃圾收集器如何实现并没有做出任何规定,因此不同厂商、不同版本的虚拟机所包含的垃圾回收器都可能会有很大差别,不同的虚拟机一般也会提供各种参数供用户根据自己的应用特点和要求组合出各个内存分代所使用的收集器。
垃圾回收器的分类
1. 串行
2. 吞吐量优先
3. 响应时间优先
不同垃圾回收器的组合
上图展示了七种作用于不同分代的垃圾回收器,如果二个回收器之间存在连线,这说明它们可以搭配使用,但这个关系不是一成不变的,在JDK的一些版本中,某些组合已经被废弃了。
直到目前还没有最好的垃圾回收器,跟更加不存在万能的垃圾回收器,我们应选择对具体的应用最合适的垃圾回收器。
开启串行垃圾回收器的JVM参数
-XX:+UseSerialGC = Serial + SerialOld
Serial和SerialOld是二种串行的垃圾回收器。其中:
Serial:是一种单线程的垃圾回收器,用于新生代,采用标记复制算法进行垃圾回收。
SerialOld:是一种单线程的垃圾回收器,用于老年代,采用标记整理算法进行垃圾回收。
开启吞吐量优先的垃圾回收器
-XX:+UseParallelGC ~ -XX:+UseParallelOldGC
Parallel和ParallelOld是二种吞吐量优先的垃圾回收器。其中:
Parallel:是一款多线程并行收集的垃圾回收器,用于新生代,采用标记复制算法
ParallelOld:是一款多线程并行收集的垃圾回收器,用于老年代,采用标记整理算法
开启其它参数
//动态调整伊甸园和幸存区的比例、整个堆的大小、晋升阈值的大小
-XX:+UseAdaptiveSizePolicy
//下面这二个参数开启后,Parallel和ParallelOld会尝试达到对应的目标
//吞吐量的目标
-XX:GCTimeRatio=ratio
//最大暂停毫秒数的目标,默认200ms
-XX:MaxGCPauseMillis=ms
//控制线程数
-XX:ParallelGCThreads=n
开启响应时间优先的垃圾回收器
-XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~ SerialOld
CMS 和 ParNew 是二种响应时间优先的垃圾回收器。其中:
CMS:是一款多线程的垃圾回收器,进行垃圾回收的过程中涉及到并行和并发,用于老年代,采用标记清除算法。另外,CMS进行垃圾回收时可能会造成内存碎片过多,当新生代垃圾回收内存不足时,老年代由于内存碎片太多,也内存不足,这种情况下,会采用SerialOld进行老年代的垃圾回收
ParNew:是一款多线程并行收集的垃圾回收器,用于新生代,采用标记复制算法
//参数1:并行的线程数,默认是4、参数2:并发的线程数,建议设置为并行线程数的四分之一
-XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads
//当老年代内存占比达到percent时,执行CMS,这么做的目的是预留一些空间给浮动垃圾用
-XX:CMSInitiatingOccupancyFraction=percent
//新生代对象有可能引用老年代的对象,但某些引用了老年代对象的新生代对象,本身是要作为垃圾处理的,
所以通过打开此开关,在进行重新标记之前,先针对新生代进行一次垃圾回收,这样做就减少了重新标记的
压力
-XX:+CMSScavengeBeforeRemark