Java虚拟机--垃圾回收机制

  Java与C++相比,具有动态分配内存垃圾回收机制的技术优势,使得我们不用把精力集中在内存的管理上,那我们为什么还要去了解GC和内存分配呢?原因很简单:当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。

1.为什么进行垃圾回收

   随着程序的运行,系统内存中存在的对象实例、各种变量越来越多,如果不进行垃圾回收,会影响到程序的性能,当占用内存过多时,还会产生OOM等系统异常。

2.哪些内存需要回收

  关于JVM内存结构,分为:程序计数器、虚拟机栈、本地方法栈、堆、方法区。其中前三者随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出而有条不紊的执行者出栈和入栈的操作,当方法调用结束或线程结束时,内存就自然跟着回收了,所以只有堆和方法区需要GC。那如何鉴定GC的对象呢?简单讲:如果某个对象已经不存在任何引用,那么它可以被回收。

3.什么时候进行回收

  引用计数算法来判断对象是否存活:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1;引用失效则减1;任何时刻计数器为0的对象就是不能被使用的。这种算法并没有在主流的Java虚拟机里使用,因为它有一个很大的缺点:很难解决对象之间互相循环引用的问题。

  可达性分析算法判断对象是否存活:基本思路就是通过一系列的称为“GC Roots”的对象为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连,则证明此对象是不可用的。如图所示:虽然object5、6、7相关联,但它对于GC Roots来说是不可达的,所以是可以回收的对象。

             

  :在Java语言中,可以作为GC Roots对象的包括以下几种:虚拟机栈(栈帧中的本地变量表)中引用的对象方法区中类静态属性引用的对象方法区常量引用的对象本地方法栈中Native方法引用的对象

  拓展:JDK 1.2之后,Java将引用的概念进行了扩充,分为:强、软、弱、虚,强度逐级递减。

    强(Strong Refence):指在程序代码中普遍存在的,类似“Object o = new Object()”这类引用,只要强引用在,垃圾回收器就永远不会回收调被引用的对象。

    软(Soft Refence):用来描述一些还有用但非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,会把它列入可回收的范围内,回收之后内存仍不够,才会抛出内存溢出异常。

    弱(Weak Refence):同样是用来描述一些有用但非必须的对象,但比软引用还弱,被弱引用关联的对象,只能生存到下一次垃圾手机发生之前。

    虚(Phantom Refence):最弱的一种引用关系,它的存在,完全不会对其关联的对象的生存时间构成影响,也无法通过虚引用来取得一个对象的实例,它的唯一作用就是能在这个关联对象被回收时收到一个系统通知。

  生存还是死亡?

  即使是可达性分析算法中不可达,也并非确认“死亡”,要真正宣告一个对象“死亡”,至少要经历两次标记过程:当发现不可达后,会进行第一次标记并进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,当对象没有覆盖finalize()方法或finalize()方法已经被虚拟机调用过,虚拟机将视这两种情况为“没有必要执行”;如果这个对象被判定为有必要执行finalize()方法,将会被放入F-Queue队列中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它,此处“执行”指触发这个方法。finalize()方法是摆脱“死亡”的最后一次机会,稍后GC将对F-Queue中的对象进行第二次标记。

  总结:什么样的类需要回收呢?满足条件:1>.该类的所有实例对象都已经被回收  2>.加载该类的ClassLoader已经被回收  3>.该类对应的反射类java.lang.Class对象没有被任何地方引用。

4.如何回收

  标记清除算法:分为“标记”和“清除”两个阶段,标记所有需要回收的对象,然后清楚。缺陷:标记和清楚两个阶段的效率不高;产生大量的空间碎片,可能导致分配大对象时空间不足而提前进行一次GC。

  复制算法:将内存分为大小相等的两块,只用一块,当一块空间满时会将活着的对象复制到另一块中,然后对原先使用过的那块内存空间进行一次清理。优点:运行高效,不用担心空间碎片的产生。缺陷:将内存一分为二,代价稍高。BUT,在虚拟机的实际设计中并不是这么分的,因为新生代中绝大多数都是“朝生夕死”,所以不需要1:1划分,而是分为Eden:From Survivor:To Survivor(8:1:1),当内存回收时,将Eden和From Survivor中的存活对象复制到另一块Survivor中。

  标记整理算法:当对象成活率很高时,复制算法就不合适了,例如老年代。标记整理算法是把被标记的对象都移动到一段,直接清理端界以外的内存。

  分代收集算法:当前商业虚拟机都采用的此算法,根据新生代和老年代各自的特征选择对应的回收算法。新生代-复制算法;老年代-“标记-清除”或“标记-整理”。

5.内存回收的具体实现----垃圾收集器

            

                 注:如果两个收集器间有连线,则可以搭配使用。

  Serial收集器:一个单线程收集器,除了只会使用一个CPU或一条垃圾收集线程去回收垃圾,更重要的是它在垃圾收集时会停止进程,知道收集结束,即“Stop The World”。当然也有优点:简单而高效(与其他收集器的单线程比)。

  ParNew收集器:Serial收集器的多线程版本,是运行在Server模式下的虚拟机中首选的新生代收集器,其中一个很重要的原因是可以和CMS搭配使用。

  Parallel Scavenge收集器:一个并行的、使用复制算法、作用于新生代的收集器,它与CMS等收集器的不同在于关注点不一样。CMS关注于尽可能的缩短垃圾收集时用户线程的停顿时间;而Parallel Scavenge收集器则是达到一个可控制的吞吐量(运行用户代码时间/(运行用户代码时间+垃圾收集时间)),主要适合在后台运算而不需要太多交互的任务。

  Serial Old收集器:使用“标记-整理”算法的Serial收集器的老年代版本,此收集器的意义在于给Client模式下的虚拟机使用。

  Parallel Old收集器:Parallel Scavenge的老年代版本,使用多线程和“标记-整理”算法。

  CMS收集器:是一种以获取最短回收停顿时间为目标的收集器,基于“标记-清楚”算法实现,运作过程分为四个步骤:初始标记并发标记重新标记并发清除

    ■ 初始标记:标记GC Roots能直接关联到的对象,速度很快。

    ■ 并发标记:进行GC Roots Tracing(判断对象是否可回收)的过程。

    ■ 重新标记:修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。

    ■ 并发清除:清除可回收的对象。和并发标记过程一起,都可以与用户线程一起工作

  CMS优点:并发收集、低停顿。缺点也很明显,如下

    ● CMS对CPU资源非常敏感。在并发阶段,虽不会导致用户线程停顿,但会占用CPU资源,导致程序变慢,总吞吐量下降。

    ● CMS无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败导致另一次Full GC。因为并发清理阶段和用户线程同步运行,会不断的产生新的垃圾,在被标记后,无法在当次垃圾收集时对其进行回收,只能等到下次,这些就成为“浮动垃圾”。JDK 1.5默认老年代使用68%空间就会激活CMS,之所以这样留出空间也是因为并发的原因。

    ● CMS基于“标记-清除”,产生大量空间碎片。

  注并发(Concurrent):指用户线程与垃圾收集线程同时执行(不一定是并行,可能交替执行),用户程序在继续运行,而垃圾收集程序运行在另一个CPU上。

    并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。

  G1收集器:一款面向服务端的垃圾收集器,具有如下特点:

    ▲ 并行与并发:集两种方式的优势于一体。   

    ▲ 分代收集 :同其他收集器一样,虽然G1不需要其他收集器配合就可以管理整个GC堆,但能采用不同方式去管理新生代和老年代,且效果不错。

    ▲ 空间整合:整体来看基于“标记-整理”,局部来看基于“复制”;无论怎么看也不会产生空间碎片。

  

    

  

  

  

猜你喜欢

转载自www.cnblogs.com/lemon-pomelo/p/9255274.html
今日推荐