您的位置:首页 > 编程语言 > Java开发

Notes: Garbage Collection in Java(Java的自动垃圾回收机制)

2016-07-29 23:36 323 查看
Java与C++的规定不同的地方:

1)C++不允许在类成员定义的时候初始化;Java可以

2)C++允许创建局部对象(存在栈上的对象),函数调用结束后回收;Java只能创建堆的对象,以为Java只能用new()来创建。

3) C++需要自己去free之前new()出来的对象,否则就算指向它的指针被回收/ 销毁了,它还会一直在那里。但Java,JVM有自动的回收机制。你可以用System.gc()去激活它,或者让JVM在内存不够的时候主动去激活它。注意,一般内存够用的时候,JVM并不会主动去激活它。回收的对象是不再有有效引用指向它的对象。为什么说是有效引用?是为了排除循环引用的情况(我的引用作为你的类成员,而我的类成员有你的引用,但除此之外,再也没有别的引用)。

这篇文章详细介绍JVM的自动垃圾回收机制:

Java调用垃圾回收的策略是只要程序没有到濒临内存耗尽的情况,Java就不去激活自动垃圾回收。而待程序运行结束后,统一将这些内存资源交还给系统。这个策略的好处在于,垃圾回收也是需要耗用资源的,这样能减少它对资源的使用。

但有一点需要注意的,垃圾回收的对象仅仅在于回收new出来的没有有效引用的对象内存,无法回收那些并非通过new创建的特殊内存。

垃圾的步骤是:

调用finalize()方法,然后在下一次的垃圾回收动作发生时,才真正回收对象占用的内存。

finalize()的意义是:

1)调用finalize的目的是让程序员自定义销毁并非通过new创建的特殊内存。这些特殊内存指的是,采用了类似C语言的方法(本地)去分配的内存。本地方法是Java调用非Java代码的方式。支持C和C++。

2)在销毁对象前做的检查,例如确认所有文件I/O已关闭。

P.S. finalize是定义在Object的一个protected的方法。任何自定义类都可以将其Override. 

这里提一个良好的编程习惯:我们应该总是假设基类版本的finialize()也在做某些重要的事情,所以要用super来调用基类的finialize()函数。但需要添加异常处理来添加这个句子。

垃圾检测方法:

1)引用计数法:简单,但速度慢。每个对象赋予一个引用计数器,多一个引用指向它就+1. 当引用被销毁(离开作用域,方法中的局部变量)或者置Null的时候,-1. 在程序的整个生命周期都需要维护这个引用数列表,不停地遍历这个表,发现有0的时候,立即释放对象。同时,这个方法无法应对“循环引用”的问题。

2)可达性分析算法:对任何一个“活”的对象,必能在堆、栈以及静态区中找到指向它的引用。所以,反之而言,我们可以直接遍历堆、栈以及静态区的所有引用,找到它们指向的对象,都列为活对象。具体而言,从一系列成为“GC Roots"的引用作为起点,向下搜索,能被这些引用链设计的对象都是活的。反之,则是可回收的。

垃圾回收方法:

1) 停止-复制 (stop - copy),它不是后台回收模式,因为它需要暂停程序。

先暂停程序的运行,将所有检测到的”活“对象都复制到另一个堆空间(块),没被复制的都是垃圾。当对象被复制到新堆时,他们必须是紧凑的(整理)。然而,这个方法需要维护两个堆,而且复制之后,还需要遍历修改引用,比较麻烦。如果产生的垃圾很少的时候,这个方法显得笨重。

2)所以,一般应对垃圾较少的时候,JVM采用的另一种回收办法:标记-清扫(mark - and - sweep)。它遇到很多垃圾的时候性能不如停止-复制。但是,少量垃圾时,它是教好的选择。方法简述为,遍历在堆、栈与静态区的引用,找出活对象时,标记一下,不做任何回收。完成遍历后,开始清理,得到一个不连续的堆空间,再对堆空间进行整理 (操作系统领域称之为内存压缩)。这个方法也需要暂停程序,所以不是后台回收模式。

今天在这个博客看到了一些JVM回收的有意思的说法。众所周知,JVM对对象进行分代回收。对不同代的对象有不同的收集器去回收。那么怎么分代呢?

假设你是一个普通的 Java 对象,你出生在 Eden 区,在 Eden 区有许多和你差不多的小兄弟、小姐妹,可以把 Eden 区当成幼儿园,在这个幼儿园里大家玩了很长时间。Eden 区不能无休止地放你们在里面,所以当年纪稍大,你就要被送到学校去上学,这里假设从小学到高中都称为 Survivor 区。开始的时候你在 Survivor 区里面划分出来的的“From”区,读到高年级了,就进了 Survivor 区的“To”区,中间由于学习成绩不稳定,还经常来回折腾。直到你 18 岁的时候,高中毕业了,该去社会上闯闯了。于是你就去了年老代,年老代里面人也很多。在年老代里,你生活了
20 年 (每次 GC 加一岁),最后寿终正寝,被 GC 回收。有一点没有提,你在年老代遇到了一个同学,他的名字叫爱德华 (慕光之城里的帅哥吸血鬼),他以及他的家族永远不会死,那么他们就生活在永生代。

-- 引用 https://www.ibm.com/developerworks/cn/java/j-lo-jvm-optimize-experience/index.html

关于JVM的调优,可以记住这两个参数和方法:

-XX:MinHeapFreeRatio 参数用来设置堆空间最小空闲比例,默认值是 40。当堆空间的空闲内存小于这个数值时,JVM 便会扩展堆空间。

-XX:MaxHeapFreeRatio 参数用来设置堆空间最大空闲比例,默认值是 70。当堆空间的空闲内存大于这个数值时,便会压缩堆空间,得到一个较小的堆。

-Xms 最小堆大小

-Xmx 最大堆大小

如何获得一个稳定堆?

获得一个稳定的堆大小的方法是使-Xms 和-Xmx 的大小一致,即最大堆和最小堆 (初始堆) 一样。如果这样设置,系统在运行时堆大小理论上是恒定的,稳定的堆空间可以减少 GC 的次数。

除此之外,还可以适当的调整年老代的阈值、调大年轻代的内存(使对象更多的留在年轻代,方便回收)。

堆中的每一个对象都有自己的年龄。一般情况下,年轻对象存放在年轻代,年老对象存放在年老代。为了做到这点,虚拟机为每个对象都维护一个年龄。如果对象在 Eden 区,经过一次 GC 后依然存活,则被移动到 Survivor 区中,对象年龄加 1。以后,如果对象每经过一次 GC 依然存活,则年龄再加 1。当对象年龄达到阈值时,就移入年老代,成为老年对象。

这个阈值的最大值可以通过参数-XX:MaxTenuringThreshold 来设置,默认值是 15。

-Xmn 2g:设置年轻代区域大小为 2GB;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  jvm 垃圾回收 java