什么是垃圾回收机制:在系统运行过程中,会产生一些无用的对象,这些对象占据着一定的内存,如果不对这些对象清理回收无用对象的内存,可能会导致内存的耗尽,所以垃圾回收机制回收的是内存。同时GC回收的是堆区和方法区的内存。
JVM回收特点:(stop-the-world)当要进行垃圾回收时候,不管何种GC算法,除了垃圾回收的线程之外其他任何线程都将停止运行。被中断的任务将会在垃圾回收完成后恢复进行。GC不同算法或是GC调优就是减少stop-the-world的时间。à(为何非要stop-the-world),就像是一个同学的聚会,地上有很多垃圾,你去打扫,边打扫边丢垃圾怎么都不可能打扫干净的哈。当在垃圾回收时候不暂停所有的程序,在垃圾回收时候有new一个新的对象B,此时对象A是可达B的,但是没有来及标记就把B当成无用的对象给清理掉了,这就会导致程序的运行会出现错误。
如何判断哪些对象需要回收呢:
1. 引用计数算法(java中不是使用此方法):每个对象中添加一个引用计数器,当有别人引用它的时候,计数器就会加1,当别人不引用它的时候,计数器就会减1,当计数器为0的时候对象就可以当成垃圾。算法简单,但是最大问题就是在循环引用的时候不能够正确把对象当成垃圾。
2. 根搜索方法(这是后面垃圾搜集算法的基础):这是JVM一般使用的算法,设立若干了根对象,当上述若干个跟对象对某一个对象都不可达的时候,这个对象就是无用的对象。对象所占的内存可以回收。
根搜索算法的基础上,现代虚拟机的实现当中,垃圾搜集的算法主要有三种,分别是标记-清除算法、复制算法、标记-整理算法。
标记-消除算法:当堆中的有效内存被耗尽的时候,就会停止整个系统,就会调用标记-消除算法,主要做两件事,1就是标记,2就是清除。然后让程序恢复。
标记:遍历所有GCroots把可达的对象标记为存活的对象。
清除:把未标记为存活的对象清楚掉。
缺点:1.就是效率相对比较低。会导致stop-the-world时间过长。
2:因为无用的对象内存不是连续的因此清理后的内存也不是连续的,(会产生内存碎片)因此JVM还要维持一个空闲列表,增加一笔开销,同时在以后内存使用时候,去查找可用的内存这个效率也是很低的。
复制算法:(这个算法一般适合在新生代GC),将原有的内存分为两块,每次只适用其中的一块,在垃圾回收的时候,将一块正在使用的内存中存活(上述根搜索的算法)的对象复制到另一块没有使用的内存中,原来的那一块全部清除。与上述的标记-清除算法相比效率更高,但是不太适合使用在对象存活较多的情况下(如老年代)。
缺点:每次对整个半区内存回收,因此效率比上面的要高点,同时在分配内存的时候不需要考虑内存的碎片。按照顺序分配内存。简单高效。
但是最大的问题在于此算法在对象存活率非常低的时候使用,将可用内存分为两份,每次只使用一份这样极大浪费了内存。
注意(重要):现在的虚拟机使用复制算法来进行新生代的内存回收。因为在新生代中绝大多数的对象都是“朝生夕亡”,所以不需要将整个内存分为两个部分,而是分为三个部分,一块为Eden和两块较小的Survivor空间(比例->8:1:1)。每次使用Eden和其中的一块Survivor,垃圾回收时候将上述两块中存活的对象复制到另外一块Survivor上,同时清理上述Eden和Survivor。所以每次新生代就可以使用90%的内存。只有10%的内存是浪费的。(不能保证每次新生代都少于10%的对象存活,当在垃圾回收复制时候如果一块Survivor不够时候,需要老年代来分担,大对象直接进入老年代)
标记-整理算法:(老年代GC)在存活率较高的情况下,复制的算法效率相对比较低,同时还要考虑存活率可能为100%的极端情况,因此又不能把内存分为两部分的复制算法。
在上面标记-复制算法的基础之上,演变出了一个新的算法就是标记-整理算法。首先从GCroots开始标记所有可达的对象,标记为存活的对象。然后将存活的对象压缩到内存一端按照内存地址的次序依次排列,然后末端内存地址之后的所有内存都清除。
总结:将标记存活的对象按照内存地址顺序排列到内存另一端,末端内存地址之后的内存都会被清除。
比较:相比较于标记-清楚算法 (传统的),该算法可以解决内存碎片问题同时还可以解决复制算法部分内存不能利用的问题。但是标记-整理算法的效率也不是很高。
->上述算法都是根据根节点搜索算法来判断一个对象是不是需要回收,而支撑根节点搜索算法能够正常工作理论依据就是语法中变量作用域的相关内容。
三种算法比较:
效率:复制算法>标记-整理算法>标记-清除算法;
内存整齐度:复制算法=标记-整理算法>标记-清除算法
内存利用率:标记-整理算法=标记-清除算法>复制算法
分代收集算法:
现在使用的Java虚拟机并不是只是使用一种内存回收机制,而是分代收集的算法。就是将内存根据对象存活的周期划分为几块。一般是把堆分为新生代、和老年代。短命对象存放在新生代中,长命对象放在老年代中。
对于不同的代,采用不同的收集算法:
新生代:由于存活的对象相对比较少,因此可以采用复制算法该算法效率比较快。
老年代:由于存活的对象比较多哈,可以采用标记-清除算法或是标记-整理算法
(注意)新生态由于根据统计可能有98%对象存活时间很短因此将内存分为一块比较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。
上述是垃圾回收机制的算法,但是垃圾回收器才是垃圾回收的具体实现:
常见有五个垃圾回收器:
一:串行收集器:(Serial收集器)
该收集器最古老、稳定简单是一个单线程的收集器,(stop-the-world)可能会产生长时间的停顿. serial 收集器一定不能用于服务器端。这个收集器类型仅应用于单核CPU桌面电脑。
新生代和老年代都会使用serial收集器。新生代使用复制算法(内存分三块的那个复制算法)。老年代使用标记-整理算法。
二:并行收集器:(Parallel收集器)
parallel收集器使用多线程并行处理GC,因此更快。当有足够大的内存和大量芯数时,parallel收集器是有用的。它也被称为“吞吐量优先垃圾收集器。”
三:并行收集器:(Parallel Old 垃圾收集器)
相比于parallel收集器,他们的唯一区别就是在老年代所执行的GC算法的不同。它执行三个步骤:标记-汇总-压缩(mark – summary – compaction)。汇总步骤与清理的不同之处在于,其将依然幸存的对象分发到GC预先处理好的不同区域,算法相对清理来说略微复杂一点。
四:并行收集器:(CMS收集器)
(ConcurrentMark Sweep:并发标记清除)是一种以获取最短回收停顿时间为目标的收集器。适合应用在互联网站或者B/S系统的服务器上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短。
五:G1收集器
这个类型的垃圾收集算法是为了替代CMS 收集器而被创建的,因为CMS 收集器在长时间持续运行时会产生很多问题。