垃圾收集(Garbage Collection ,GC),是一个长久以来就被思考的问题,当考虑GC的时候,我们必须思考3件事情:

  •   哪些内存需要回收?
  •   什么时候回收?
  •   如何回收?

  那么在Java中,我们要怎么来考虑GC呢?首先回想以下内存区域的划分,其中程序计数器、本地方法栈、虚拟机栈三个区域随线程而生,随线程释放,栈中的栈帧随着方法的进入和退出执行着出栈和入栈的操作,每一个栈帧分配多少内存基本是在类结构确定时就已经固定的(可能会进行一些优化,但是大体上已知),因此这几个区域就不需要考虑回收的问题,因为方法结束或者线程结束时,内存自然都被回收。不需要额外的GC算法等。

  然而Java堆和方法区则不一样,一个接口所对应的多个实现类所需要的内存可能不一样,一个方法中的多个分支所需要的内存也可能不一样,我们只有在程序处于运行期间才能知道程序需要创建那些对象,这部分的内存的分配和回收是动态的,因此,垃圾收集器关注的是这方面的内存。

一. 如何确定对象可以回收

1.引用计数算法

  最容易想到与理解的算法,即对于每一个对象,每当该对象被引用时,计数器值就+1,引用失效时,计数器就-1。因此,当对象的引用计数为0时,即为不可再被使用的。该算法也在一些领域被使用来进行内存管理,但是JAVA虚拟机中并没有选用该算法。主要是因为不能很好的解决循环引用的问题。

  举个简单的例子来说明循环引用:

class Container{
public Object obj ;
} public class ReferTest { public static void main(String[] args){
Container c1 =new Container();
Container c2 =new Container();
c1.obj = c2 ;
c2.obj = c1 ; c1 = null ;
c2 = null ;
//此时c1 c1会被判定为死亡对象么?
}
}

  事实上会被判定为死亡对象,因为JAVA虚拟机不是采用引用计数来进行判断的,因此如果发生垃圾回收,c1,c2 都会被回收内存。

2.可达性分析

  Java、C#的主流实现都是采用该种方式,来判断对象是否存活。

  这个算法的基本思路就是一系列“GC Roots”作为起始点,从这些节点向下搜索,搜索到的所有引用链中的对象都是可达的,其余的对象都是不可达的,如上例,即使c1,c2互相引用,但是c1,c2都不属于GC Roots对象,因此都不可达。

  Java中,以下几种对象可以作为GC Roots:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  • 本地方法栈JNI方法引用的对象。
  • 方法区类的静态属性引用的对象。
  • 方法区常量引用的对象。

3.引用的分类

  了解了GC Roots之后,我们可能会希望存在这么一种对象,内存够的时候不进行回收,当需要内存时再将其回收。JDK 1.2 中对引用进行了扩充。将引用分为了4种,从强到弱依次为;

  强引用(Strong Reference)

  我们一般情况下使用的都是强引用,如Object o = new Object(),之类的代码。只要强引用还在,垃圾收集器就永远不会回收被引用的对象。

  软引用(Soft  Reference)

  SoftReference类来实现,用来描述一些还有用但是不必须的对象,在系统如果不回收就会发生OOM时才会对软引用进行内存回收。

  弱引用(Weak  Reference)

  WeakReference类来实现,描述非必需的对象,强度弱,只能活到下一次发生垃圾回收前,无论那时内存是否短缺,都会对软引用对象进行内存回收

  虚引用(Phantom Reference)

  PhantomReference类实现,不会对生存时间发生任何影响,唯一目的时能在这个对象被收集器回收时得到一个通知。

4.其他

  及其不建议使用finalize()方法,虽然可以在回收时被调用,但是finalize()方法的执行代价高昂,不确定性大,无法保证各个对象的调用顺序。使用finalize()能做的工作,使用try()finally()或其他方式可以执行的更好。大家可以忘记JAVA中有这个方法的存在。本身就是在JAVA刚诞生时向C/C++程序员做的妥协,但是未得到优化。

  方法区(永久代)进行GC的效率极低,花费较大,但是在大量使用反射、动态代理等场景都需要虚拟机具备类卸载的功能,以保证永生代的空间。

二.垃圾收集算法

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

  算法分为两个阶段,标记与清除。

  标记阶段:标记出所有需要回收的对象。回收阶段:将所有标记区域回收。由于该算法不对空间进行整理,因此会产生大量的内存碎片,内存空间碎片过多会导致在分配较大的对象时,因为没有连续的内存而不得不提前触发一个GC。另外,标记与清除的过程效率都不高。这也是最基础的GC算法。

2.复制算法(Copying)

  将内存的总容量分为两块,每次只使用其中的一块,当这一块用完了,触发GC,此时将还存活的对象转移到另一块内存中,之前使用的那一块内存完全清理掉。这样每次对一个半区进行回收,也不会存在内存碎片,实现简单,运行高效,但是一次只能使用半块内存可能会造成浪费。

  在新生代中,绝大部分的对象时“朝生夕死”的,因此,不需要按照1:1来划分空间。而是将内存分为一块较大的Eden区以及两个Survivor区,HotSpot虚拟机中,Eden:Survivor=8:1 ,每次使用一个Eden区以及一个Survivor区,90%的空间,触发GC后,将剩余的对象转移到未使用的Survivor中,然后清理Eden区和用过的Survivor区,空间不够时,会担保分配到老年代。这样一次可以使用90%的内存空间,极大的提高了内存的使用率。因此,新生代一般采用这种算法来回收。

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

  如果回收时空间内的对象存活率较高,那么使用复制算法一次只能使用50%的空间(以应对所有对象都存活的情况),因此老年代采用标记整理算法。先对需要清理的对象进行标记,然后将存活的对象都向一端移动,直接清理掉端边界以外的内存。这种方式也不会留下内存碎片。

  标记整理算法没有复制算法快。

三. Java垃圾收集器

(了解即可,需要时可以网上细查)

新生代收集器:Serial收集器、ParNew收集器(Serial的多线程版本)、Parallel Scanvenge收集器(控制吞吐量,提高相应速度)

老年代收集器:Serial Old收集器、Parallel Old收集器、CMS收集器(最短停顿)、G1(新生代、老年代都可回收)

四. 内存的分配与回收

新生代:即复制算法中提到的Eden区以及2个Survivor区。

老年代:新生代存活足够长时间后进入老年代。堆上的另一块区域。

Minor GC:发生在新生代的垃圾收集动作。因为Java对象存活时间一般较短,故Minor GC非常频繁,一般回收速度也较快。

Full GC:发生在老年代的垃圾收集动作,伴随着最少一次的Minor GC,且速度较慢(比Minor GC慢10倍以上)

1.空间的分配

  1)对象优先在新生代Eden区分配。当Eden区没有足够空间时,将发动一次Minor GC.

  2)较大对象需要连续的空间,如长字符串或数组,如果放在新生代会提前触发GC。故大对象直接进入老年代区域,避免频繁的GC。

  3)长期存活的对象进入老年代,每个对象有一个年龄,在对象头Mark Word中记录,刚被创建时年龄为0,当它活过一次Minor GC,并且转移到Survivor中,年龄变为1,此后,在Survivor区中每活过一个Minor GC,年龄就会+1,当年龄达到某个程度(默认为15),就会晋升到老年代。

  4)此外,为了适应内存的复杂情况,年龄不一定达到规定值才能进入老年代。当Survivor区的相同年龄所有对象大小大于Survivor区大小的一半时,此年龄就会被作为判定标准,大于等于该年龄的都会进入老年代。

2.空间的回收--GC

这里我用一张图来彻底解释清除:

需要解释的地方有:担保失败,这个的作用在图上已经解释的很清楚了,可以在JVM参数设置。

 另外一个地方就是平均大小来作比较,因为有多少对象晋升到老年代是无法知道的,所以只好取之前每一次晋升到老年代的对象的容量的平均值大小来作为经验值,来决定是否进行Full GC来让老年代腾出更多空间。如果仍然失败,那么只能进行一次Full GC。在我个人开来,之所以使用担保,经验值来尽可能的只进行MinorGC,所有的一切,都是为了尽可能不执行Full GC的情况下将需要申请的内存空间搞定

 至于Full GC 和Minor GC的具体操作,请参考之前的标记整理算法和复制算法。

一篇文章彻底了解Java垃圾收集(GC)机制的更多相关文章

  1. 一篇文章读懂Java类加载器

    Java类加载器算是一个老生常谈的问题,大多Java工程师也都对其中的知识点倒背如流,最近在看源码的时候发现有一些细节的地方理解还是比较模糊,正好写一篇文章梳理一下. 关于Java类加载器的知识,网上 ...

  2. Java多线程详解——一篇文章搞懂Java多线程

    目录 1. 基本概念 2. 线程的创建和启动 2.1. 多线程实现的原理 2.2.多线程的创建,方式一:继承于Thread类 2.3.多线程的创建,方式一:创建Thread匿名子类(也属于方法一) 2 ...

  3. 一篇文章弄懂 Java 反射的使用

    说到Java反射,必须先把 Java 的字节码搞明白了,也就是 Class , 大 Class 在之前的文章中,我们知道了Java的大Class就是类的字节码,就是一个普通的类,里面保存的是类的信息, ...

  4. Java 虚拟机 - GC机制

    GC机制的一些总结 https://blog.csdn.net/super_qing_/article/details/85263991 https://blog.csdn.net/yhyr_ycy/ ...

  5. 一篇文章看懂java反射机制(反射实例化对象-反射获得构造方法,获得普通方法,获得字段属性)

    Class<?> cls = Class.forName("cn.mldn.demo.Person"); // 取得Class对象传入一个包名+类名的字符串就可以得到C ...

  6. JVM(3)对象A和B循环引用,最后会不会不被GC回收?-------关于Java的GC机制

    ①首先说一下,GC里边在JVM其中是使用的ROOT算法,ROOT算法,什么称作为ROOT呢,就是说类的静态成员,静态成员就是static修饰的那种,是"根"的一个,根还包含方法中的 ...

  7. JAVA 调用gc机制强制删除文件

    在删除文件前调用System.gc()方法,也就是垃圾回收机制,即可成功删除被JAVA虚拟机占用的文件.

  8. 一篇文章看懂Java并发和线程安全

    一.前言 长久以来,一直想剖析一下Java线程安全的本质,但是苦于有些微观的点想不明白,便搁置了下来,前段时间慢慢想明白了,便把所有的点串联起来,趁着思路清晰,整理成这样一篇文章. 二.导读 1.为什 ...

  9. 以前没有写笔记的习惯,现在慢慢的发现及时总结是多么的重要。 这一篇文章主要关于java多线程一些常见的疑惑点。因为讲解多线程的书籍和文章已经很多了,所以我也不好意思多说,嘻嘻嘻、大家可以去参考一些那些书籍。我这个文章主要关于实际的一些问题。同时也算是我以后复习的资料吧,。还请大家多多指教。 同时希望多结交一些技术上的朋友。谢谢。

    在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口. 以下就是我们常见的问题了: 1. 为什么我们不能直接调用run()方法呢? 我的理解是:线程的运行 ...

随机推荐

  1. jQuery 获取有多个class名的元素

    HTML内容: <div class="write-upload"> <p class="write-files old-files"> ...

  2. L91

    Make Healthy Choices Easier Options Telling people to change unhealthy behaviors doesn't work. Other ...

  3. u3d 多线程 网络

    开启一个线程做网络连接,和接收数据, 用event进行广播 using UnityEngine; using System; using System.Threading; using System. ...

  4. codeforces 660C C. Hard Process(二分)

    题目链接: C. Hard Process time limit per test 1 second memory limit per test 256 megabytes input standar ...

  5. POJ1195Mobile phones (从二维树状数组到cdq分治)

    Suppose that the fourth generation mobile phone base stations in the Tampere area operate as follows ...

  6. 【Lintcode】119.Edit Distance

    题目: Given two words word1 and word2, find the minimum number of steps required to convert word1 to w ...

  7. Spring boot 2.0 学习

    Spring boot 2.0出来了,支持java 9, 好多新特性,应该学习.   待续... ...

  8. go http 下载视频(TS码流文件)(推荐一个网站学习 go example)

    视频  http下载代码 dn.go(注意:代码很ugly,没怎么花时间) 总体感觉特别简单,网上看了下 net/http ,io这2个库的使用, 几分钟就写完了,感觉cpp 在做工具这块 开发效率的 ...

  9. shell里的` ` $( ) ${ } expr $(( ))

    转自:http://blog.sina.com.cn/s/blog_6151984a0100ekz2.html 所有UNIX命令,要取结果或输出,都要用$( )或反引号` ` tt=` file te ...

  10. Windows窗体间的数据交互

    轻松掌握Windows窗体间的数据交互                                     作者:郑佐 2004-04-05 Windows 窗体是用于 Microsoft Win ...