1.垃圾回收机制:
首先呢,我在研究垃圾回收机制的原理时,要知道需要回收的是什么呢?
1.1什么是垃圾(主要回收那些东西):
程序计数器、虚拟机栈、本地方法栈 3 个区域随线程生灭(因为是线程私有),栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。而 Java 堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期才知道那些对象会创建,这部分内存的分配和回收都是动态的,垃圾回收期所关注的就是这部分内存。
方法区主要针对的是常量池的回收和对类型的卸载,堆主要针对的是不在使用的对象进行内存回收,我们常说的GC就是对堆内存的回收,堆内存可以进一步划分为新生代和老年代(比例大概是1:2),老年代会发生full GC(标志-整理) 新生代会发生 Minor GC (标志-复制),年轻代又划分为 三个部分 Eden区和survivor区(from和to),比例为8:1:1。
1.2垃圾回收的过程:
在GC开始时,对象会存在Enden区和from区,to区为空,当Eden区存满,无法分配内存给对象的时候,会触发一次Minor GC ,进行GC时,会将Eden区存活的对象放入to区,from区中存活的对象会根据年龄值决定去留,达到阀值得会被移动到年代,没有达到的会被复制到to区;这时Eden区和from区已经清空,接下来要交换from与to的角色,以保证每次开始GC时to区是空的,MinorGC 会一直重复这个过程,直到to区被填满,填满后整个移动到老年代。如果老年代空间不足,则会触发Full GC ,
GC年龄阈值默认是15,该阈值是否可以调整?能否调整为16?年龄阈值是可以调整的,但是由于对象头中只分配了 4bit 位用于记录对象的GC年龄,因此最大只能调整为15
1.3对象是否存活:
哪究竟什么样的对象,会被成为垃圾呢?
垃圾收集器在对堆进行回收前,首先要确定对象是否存活,判断对象是否存活主要有两种算法:引用计数算法 和 可达性分析算法。
1)引用计数算法:对象创建时,给对象添加一个引用计数器,每当有一个地方引用到它时,计数器值加1;引用失效时,计数器值减1;当计数值值为0时,这个对象就是不可能再被引用的。
(2)可达性分析算法:以“GC Roots”对象为起点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连接时,则证明此对象是不可用的。
GC Roots对象包括:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象;
- 本地方法栈中JVM(Native)引用的对象;
- 方法区中类静态属性引用的对象;
- 方法区中常量引用的对象。
四种对象引用类别:(关联强度向下递减)
强引用:GC不会回收强引用的对象。
软引用:如果内存不紧张,这类对象可以不回收;如果内存紧张,这类对象就会被回收
弱引用:被弱引用关联的对象,只能生存到下一次垃圾收集。
虚引用:目的是能在对象被回收时收到一个系统通知。
1.4对象回收经历:
目前最普遍判断对象是否存活的方法采用的是可达性分析算法,对象真正死亡需要经历俩个阶段:
(1)可达性分析后,如果对象没有与GC Roots相连的引用链,会被第一次标记并筛选出来。如果对象覆盖了finalize()方法 且 未调用过finalize()方法,则对象会被放在F-Queue队列中,等待线程执行finalize()方法。
(2)若对象想要存活下来,finalize()方法是最后的机会,只需在finalize()方法中重新与引用链上的对象相关联,否则,GC对F-Queue队列进行第二次小规模标记后,真正地进行垃圾回收
1.5垃圾收集算法:
常见的垃圾回收算法如下:
(1)标记-清除法
首先标记出所有需要回收的对象,然后在标记完成后统一对进行标记的对象回收,缺点,会产出大量的不连续内存空间。
(2)复制法
将内存划分为相等的两块,每次只使用一块,当这块存储满时,把存活的对象复制到另一块中,然后清理当前内存快空间,缺点,内存缩小为当前的一班。
(3)标记-整理法
首先标记出所有需要回收的对象,然后把存活的对象都向另一端移动,然后清理掉标记的对象
(4)分代收集算法:
根据各个年龄段的不同特点选择不同的回收算法,在新生代中,每次都有大量的对象死去,存活的少,所有采用复制算法,需要复制的也少,老年代中,因为存活率高,也没有额外的空间对他进行担保,所以采用标记-清除算法,或者标记-整理算法,推荐标记整理。
1.6对象内存分配策略:
为了避免发生频繁的GC,JVM在内存分配上也有一套策略,
(1)对象优先分配在新生代Eden区:防止发生频繁的Full GC,
(2)大对象直接进入老年代: 避免Eden区和Survivor区发生大规模的内存复制。
(3)长期存活的对象进入老年代:年轻代中每经历一次minorGC 年龄加一,如果超过阀值15,就会进入老年代。
(4)动态年龄判断:在GC时会对Survivor中的对象进行判断,Survivor空间中年龄相同的对象占用内存总和大于等于Survivor空间一半的话,大于或等于该年龄的对象就会被复制到老年代
(5)空间分配担保: Minor GC前,虚拟机会检查老年代最大可用连续空间是否大于新生代所有对象总空间,若成立,则Minor GC是安全的。若不成立,则检查是否允许担保失败,如果允许,检查老年代最大可用连续空间是否大于历次晋升到老年代的平均大小,大于,则尝试进行Minor GC;如果小于或者不允许冒险,则Full GC。
2.垃圾收集器:
1、Serial收集器(新生代):
Serial 收集器是一个新生代收集器,使用复制算法。由于是单线程执行的,所以在进行垃圾收集时,必须暂停其他所有的用户线程(Stop the world),对于限定单个CPU的环境来说,由于没有线程切换的开销,可以获得最高的单线程收集效率。
2、ParNew收集器(新生代):
ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集之外,其余行为包括Serial收集器完全一样,包括控制参数、收集算法、Stop The Worl、对象分配规则、回收策略等。
在 多核CPU上,回收效率会高于Serial收集器;反之在单核CPU, 效率会不如Serial收集器。ParNew 收集器默认开启和 CPU 数目相同的线程数,可以通过 -XX:ParallelGCThreads 参数来限制垃圾收集器的线程数;
3、Parallel Scavenge收集器(新生代):
Parallel Scavenge 收集器是新生代收集器,使用复制算法,并行多线程收集。Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短GC时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。(吞吐量= 程序运行时间/(程序运行时间 + 垃圾收集时间),虚拟机总共运行了100分钟。其中垃圾收集花掉1分钟,那吞吐量就是99%)。高吞吐量可以最高效率地利用CPU时间,尽快完成程序的运算任务,主要适用于在后台不需要太多交互的任务。
4、Serial Old收集器(老年代):
Serial Old是Serial收集器的老年代版本,使用单线程执行和“标记-整理”算法。主要用途是作为CMS收集器的后备垃圾收集方案,在并发收集发生 Concurrent Mode Failure 的时候,临时启动Serial Old收集器重新进行老年代的垃圾收集。
5、Parallel Old收集器(老年代):
Parallel Old 是 Parallel Scavenge 收集器的老年代版本,JDK1.6之后开始提供,使用多线程和“标记-整理”算法。
在JDK1.6之前,新生代使用 Parallel Scavenge 收集器只能搭配年老代的 Serial Old 收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量,Parallel Old 正是为了在年老代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,可以优先考虑新生代 Parallel Scavenge 和年老代 Parallel Old 收集器的搭配策略。
6.CMS收集器:
CMS(Concurrent Mark Sweep)收集器应用于老年代,采用多线程和“标记-清除”算法实现的,实现真正意义上的并发垃圾收集器,是一种以获取最短回收停顿时间为目标的收集器。整个收集过程大致分为4个步骤,
(1)初始标记:需要停顿所有用户线程,初始标记仅仅是标记出GC ROOTS能直接关联到的对象,速度很快。
(2)并发标记:进行GC ROOTs可达性分析算法阶段,判定对象是否存活,和用户线程一起工作,不需要暂停工作线程。
(3)重新标记:修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。需要停顿所有用户线程,停顿时间会被初始标记阶段稍长,但比并发标记阶段要短。
(4)并发清除:清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。
CMS收集器的虽然真正意义上实现了并发收集以及低停顿,但CMS还远远达不到完美,主要有四个显著缺点:
(1)CMS收集器对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降。CMS默认启动的回收线程数是:(CPU数量+3) / 4。
(2)CMS收集器无法处理浮动垃圾。由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自然会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,称为“浮动垃圾”,CMS 无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。
(3)由于垃圾收集阶段会产生“浮动垃圾”,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分内存空间提供并发收集时的程序运作使用。在默认设置下,CMS收集器在老年代使用了68%的空间时就会被激活,也可以通过参数-XX:CMSInitiatingOccupancyFraction的值来提高触发百分比,以降低内存回收次数提高性能。要是CMS运行期间预留的内存无法满足程序其他线程需要,就会出现“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以参数 -XX:CMSInitiatingOccupancyFraction 设置的过高将会很容易导致 “Concurrent Mode Failure” 失败,性能反而降低。
(4)CMS是基于“标记-清除”算法实现的收集器,会产生大量不连续的内存碎片。空间碎片太多时,如果无法找到一块足够大的连续内存存放对象时,将不得不提前触发一次Full GC。为了解决这个问题,CMS收集器提供了一个-XX:UseCMSCompactAtFullCollection开关参数,用于在Full GC之后增加一个碎片整理过程,还可通过-XX:CMSFullGCBeforeCompaction参数设置执行多少次不压缩的Full GC之后,跟着来一次碎片整理过程。
7、G1收集器:
G1(Garbage First)收集器是JDK1.7提供的一个新收集器,与CMS收集器相比,最突出的改进是:
- 基于“标记-整理”算法实现,不会产生内存碎片。
- 可以在不牺牲吞吐量前提下,精确控制停顿时间,实现低停顿垃圾回收
并行性: 回收期间, 可由多个线程同时工作, 有效利用多核cpu资源;
并发性: 与应用程序可交替执行, 部分工作可以和应用程序同时执行,
分代GC: 分代收集器,同时兼顾年轻代和老年代。他能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过了多次GC的对象,以便获取更好的GC效果。
1、为什么要有Survivor区呢:
如果没有Survivor区,Eden区每满一次,进行一次Mionr GC ,就会将存活的对象存入到 ,老年代中,这样会使老年代很快的被填满,触发Full GC ,老年代得内存是远大于新生代得,老年代每满一次,进行得Full GC 得时间远大于MinorGC 得时间,这样会影响一些大型程序得 执行和相应速度,
Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
2.为什么要设置两个Survivor区:
设置两个Survivor区最大的好处就是解决了碎片化,