码迷,mamicode.com
首页 > 编程语言 > 详细

Java垃圾回收与算法详解

时间:2019-10-17 00:50:21      阅读:104      评论:0      收藏:0      [点我收藏+]

标签:强引用   obj   判断   搜索   一半   开始   c++   先来   queue   

首先来张祖传的思维导图:

技术图片

  

  内存回收与垃圾收集器在很多时候都是影响系统性能、并发能力的主要因素,虚拟机之所以提供多种不同的收集器以及提供大量的调节参数,是因为只有根据实际应用需求、实现方式选择最优的垃圾收集方式才能获得最好的性能。

GC发生在那里:

  JVM虚拟机运行时内存区域主要分为(如下图):虚拟机栈、本地方法栈、程序计数器、Java堆、方法区。其中虚拟机栈、本地方法栈、程序计数器为线程私有区域,在这几个区域中就不需要过多的考虑垃圾回收的问题,因为方法结束或者线程结束时,内存自然就跟着回收了。而Java堆和方法区则不一样,这两个区域为线程共享区域,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间时才能知道会创建那些对象,这两部分内存的分配和回收都是动态的,垃圾回收主要发生在这两部分区域。JVM内存区域解析可参考:xxxxxx

技术图片

如何给对象分配内存:

  详细讲解见:xxx

如何判断那些对象需要被回收:

  现在主流的虚拟机主要使用可达性分析算法来判断对象是否可以被回收,而不是使用引用计数算法来判断,并行进行二次标记筛选来最终回收对象。

1、引用计数算法:

  基本思想:给对象中加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值减1;任何时刻计数器为0时对象就是不能被使用,可回收了。

  例子:在发生gc时t1、t2对象是否会被回收?如果按照引用计数算法对象t1和t2之间相互引用,对象不会被回收,但是结果是要被回收的。说明虚拟机并不是使用引用计数算法来判断对象是否可以被回收。主要原因是引用计数算法很难解决对象之间相互引用的问题。

 1 public class TestGC {
 2     public Object instance = null;
 3     private static final int oneMb = 1024 * 1024;
 4     private byte[] bigSize = new byte[2 * oneMb];
 5     
 6     public static void test(){
 7         TestGC t1 = new TestGC();
 8         TestGC t2 = new TestGC();
 9         t1.instance = t2;
10         t2.instance = t1;
11         
12         t1 = null;
13         t2 = null;
14         //在这里发生GC, t1和t2是否能被回收?
15         System.gc();
16     }
17 }

2、可达性分析算法:

  基本思想:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则说明该对象不可用,可以被回收。

技术图片

可以作为GC Roots的对象包括:

  (1)虚拟机栈中引用的对象

  (2)方法区中类静态属性引用的对象

  (3)方法区中常量引用的对象

  (4)本地方法栈中JNI引用的对象

  可以理解为:  

  (1)首先第一种是虚拟机栈中的引用的对象,我们在程序中正常创建一个对象,对象会在堆上开辟一块空间,同时会将这块空间的地址作为引用保存到虚拟机栈中,如果对象生命周期结束了,那么引用就会从虚拟机栈中出栈,因此如果在虚拟机栈中有引用,就说明这个对象还是有用的,这种情况是最常见的。

  (2)第二种是我们在类中定义了全局的静态的对象,也就是使用了static关键字,由于虚拟机栈是线程私有的,所以这种对象的引用会保存在共有的方法区中,显然将方法区中的静态引用作为GC Roots是必须的。

  (3)第三种便是常量引用,就是使用了static final关键字,由于这种引用初始化之后不会修改,所以方法区常量池里的引用的对象也应该作为GC Roots。最后一种是在使用JNI技术时,有时候单纯的Java代码并不能满足我们的需求,我们可能需要在Java中调用C或C++的代码,因此会使用native方法,JVM内存中专门有一块本地方法栈,用来保存这些对象的引用,所以本地方法栈中引用的对象也会被作为GC Roots。

  无论是通过引用计数算法还是可达性分析算法判断对象,判断对象是否存活都与引用有关,java中的引用包括4种(由强到弱):强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)。

  • 强引用(StrongReference)
    强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。类似Object obj = new Object()。
  • 软引用(SoftReference)
    如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存(下文给出示例)。
    软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
  • 弱引用(WeakReference)
    弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
    弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
  • 虚引用(PhantomReference)
    “虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
    虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。

3、最终判定(二次标记筛选):  

  即使对象进行了可达性分析算法判定后,发现没有与GC Roots可达的引用链,此对象也不会立刻进行回收,还需要经历二次标记筛选过程,才能回收对象。

  (1)第一次标记:如果某个对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机执行过,则这两种情况下,虚拟机会视为这对象“没有必要执行”,对象会被回收。

  (2)第二次标记:以第一次标记情况相反,如果这个对象被判断为有必要执行finalize()方法,则将这个对象放入F-Queue队列中,此后虚拟机会自动建立一个低优先级的Finalizer线程对F-Queue中的对象进行二次标记,如果对象在finalize()方法中成功的重新与引用链上的任何一个对象建立关联,那么将把该对象移出“即将回收”的集合,该对象不会被回收,否则将被回收。每个对象都有finalize()方法(Object类里定义了该方法),finalize()方法是对象逃脱被回收的最后一次机会。

如何回收内存垃圾:

  当然是使用各种垃圾垃圾收集算法来对内存进行清理,包括有:标记-清除算法(Mark-Sweep)、复制算法(Copying)、标记-整理算法(Mark-Compact)、分代收集算法(Generational Collection)。

  1、标记-清除算法(Mark-Sweep):

  基本思想:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,它的标记过程是根据可达性分析算法和二次标记过程来判断和标记。

  缺点:(1)标记和清除两个过程效率不高;(2)标记清除后会产生大量不连续的内存碎片;内存碎片太多会导致以后在程序运行需要分配较大对象时,无法找到足够连续的内存空间而不得不提前触发另一次的垃圾回收动作。

技术图片

 

   2、复制算法(Copying):

  基本思想:它将可用内存容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完之后,就将还存活的对象复制到另外一块上面,然后在把已使用过的内存空间一次清理掉。这样使得每次都是对其中的一块进行内存回收,不会产生碎片等情况,只要移动堆订的指针,按顺序分配内存即可,实现简单,运行高效。

  复制算法主要应用于回收新生代,新生代内存划分为:Eden区、From Servivor区、To Servivor区,如图:

技术图片

 

   缺点:(1)内存缩小为原来的一半。(2)如果对象存活率较高时需要进行较多次的复制操作,效率会降低。(3)需要有额外的空间进行分配担保。

技术图片

  3、标记-整理算法(Mark-Compact):

  基本思想:标记过程与标记-整理算法一样,但后续步骤不是直接对可回收的对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

技术图片

 

   4、分代收集算法(Generational Collection):

  基本思想:根据对象的存活时间不同,将java堆分为新生代和老年代,根据各个年代的特点采用最合适的收集算法。在新生代中,每次垃圾收集时都发现有大量的对象死去,只有少数存活,可以采用复制算法,只需要复制出少量存活的对象就可以完成收集。在老年代中对象的存活率高,没有额外空间进行分配担保,可以采用“标记-清除”或者“标记-整理”算法。当前商业虚拟机都是采用此算法。

垃圾收集器

  如下图所示,java垃圾收集器主要有一下7种,作用于新生代:Serial收集器、ParNew收集器、Parallel Scavenge收集器。作用于老年代:Serial Old收集器、Parallel Old收集器、CMS(Concurrent Mark Sweep)收集器。G1(Garbage First)收集器主要面向服务端应用的垃圾收集器。两个收集器之间存在连线,说明它们可以搭配一起使用。    技术图片

 

 

未完,待续...  

 

本文参考:

  《深入理解java虚拟机》周志明

注:本文主要是学习《深入理解java虚拟机》一书的读书笔记,以此来总结和学习。本文图片大部分来源于网络,侵删。

 

Java垃圾回收与算法详解

标签:强引用   obj   判断   搜索   一半   开始   c++   先来   queue   

原文地址:https://www.cnblogs.com/oush/p/11686671.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!