Java GC机制和对象Finalize方法的一点总结

  • GC是什么? 为何要有GC?

GC是垃圾收集的意思(Garbage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会致使程序或系统的不稳定甚至崩溃,Java提供的GC功能能够自动监测对象是否超过做用域从而达到自动回收内存的目的。java

 

  • 有向图垃圾回收机制

.NET的垃圾回收采用引用计数,java的垃圾回收机制采起的是有向图的方式来实现,具体的说,java程序中的每一个线程对象就能够看做是一个有向图的起点,有向边从栈中的引用者指向堆中的引用对象。在这个有向图中,若是一个对象和根节点之间是可达的,那么这个对象就是有效的,反之,这个对象就是能够被回收的。采起这样一种机制的优势是能够有效的避免循环引用。程序员

当程序员建立对象时,GC就开始监控这个对象的地址、大小以及使用状况。经过有向图机制肯定哪些对象是"可达的",哪些对象是"不可达的".当GC肯定一些对象为"不可达"时,GC就有责任回收这些内存空间。算法

 

  • GC在JVM中一般是由一个或一组线程来实现的,它自己也和用户程序同样占用heap空间,运行时也占用CPU.当GC进程运行时,应用程序中止运行。为了防止finalize函数抛出的异常影响到垃圾回收线程的运做,垃圾回收线程会在调用每个finalize函数时进行try catch,若是捕获到异常,就直接丢弃,而后接着处理下一个失效对象的finalize函数。因此finalize函数内通常须要本身处理抛出的异常,防止发生未处理异常状况。

 

  • 当GC运行时间较长时,用户可以感到 Java程序的停顿,另一方面,若是GC运行时间过短,则可能对象回收率过低,这意味着还有不少应该回收的对象没有被回收,仍然占用大量内存。所以一种折中的方案就是每次GC处理必定比例的对象,分红屡次进行,这就叫增量式GC。

 

  • GC的分代

1) 在Young Generation中,有一个叫Eden Space的空间,主要是用来存放新生的对象,还有两个Survivor Spaces(from、to),它们的大小老是同样,它们用来存放每次垃圾回收后存活下来的对象。数据库

2) 在Old Generation中,主要存放应用程序中生命周期长的内存对象。编程

3) 在Young Generation块中,垃圾回收通常用Copying的算法,速度快。每次GC的时候,存活下来的对象首先由Eden拷贝到某个SurvivorSpace当Survivor Space空间满了后剩下的live对象就被直接拷贝到OldGeneration中去。所以,每次GC后,Eden内存块会被清空。安全

4) 在Old Generation块中,垃圾回收通常用mark-compact的算法,速度慢些,但减小内存要求。性能优化

5) 垃圾回收分多级,0级为所有(Full)的垃圾回收,会回收OLD段中的垃圾;1级或以上为部分垃圾回收,只会回收Young中的垃圾,内存溢出一般发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的状况。网络

 

 

  • GC只回收堆区的内存,即处理java new出来的对象,但没法关闭其余资源,也没法处理java调用C或其余语言分配出的内存。

 

  • 若是调用对象的finalize函数,对象处于不可达状态,而且GC准备回收该对象的内存,即finalize函数的调用发生在回收内存以前。

 

  • JVM不保证finalize函数必定会被调用。System.runFinalizersOnExit方法不安全,已经废弃,全部并不能保证程序退出时必定调用finalize函数。另外,规范还保证finalize函数最多运行一次。

 

  • System.gc并不保证GC执行,只是向JVM发送建议,并非命令。

 

  • 对象不可达,可是调用finalize以后又变得可达的状况存在,在finalize函数中经过this指针让其余句柄执行自己便可,可是再下次回收时不会再调用finalize,由于只能调用一次。
protected void finalize()
{
     main.ref=this;  // 恢复本对象,让本对象可达
}

 

  • 垃圾回收器不能对用Java之外的代码编写的Class(好比JNI,C++的new方法分配的内存)进行正确的回收,这时就须要覆盖默认finalize的方法来实现对这部份内存的正确释放和回收(好比C++须要delete)。 

 

  • finalize不能等同于C++对象的析构函数,C++析构函数在在对象生命周期结束时会肯定执行,而finalize函数的调用具备很大的不肯定性。

调用时间不肯定——有资源浪费的风险函数

若是把某些稀缺资源放到finalize()中释放,可能会致使该稀缺资源等上好久好久之后才被释放。形成资源的浪费!另外,某些类对象所携带的资源(好比某些JDBC的类)可能自己就很耗费内存,这些资源的延迟释放会形成很大的性能问题。性能

可能不被调用——有资源泄漏的风险

在某些状况下,finalize()压根儿不被调用。好比在JVM退出的当口,内存中那些对象的finalize函数可能就不会被调用了。

所以一些清理工做如文件的关闭,链接的关闭等不要放到finalize函数中,要在程序中单独进行管理,通常finalize只作C/C++内存的回收。

 

  • 即便有GC机制,Java仍是存在内存泄露的问题

1.静态集合类造成的对象引用

Static Vector v = new Vector(); 
for (int i = 1; i<100; i++) 
{ 
    Object o = new Object(); 
    v.add(o); 
    o = null; 
}

2.当集合里面的对象属性被修改后,再调用remove()方法时不起做用

3.各类链接,数据库链接,网络链接,IO链接等,显示调用close关闭后才能被GC回收

 

  • Java语言中的对象引用分为如下几种:强引用,软引用,弱引用,虚引用

强引用就是平时最经常使用的引用,当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具备强引用的对象来解决内存不足的问题

若是一个对象只具备软引用,则内存空间足够,垃圾回收器就不会回收它;若是内存空间不足了,就会回收这些对象的内存

只具备弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程当中,一旦发现了只具备弱引用的对象,无论当前内存空间足够与否,都会回收它的内存

虚引用,这种引用不经常使用,主要用途是结果引用关联对象,实现对对象引用关系追踪。虚引用并不会决定对象的生命周期。若是一个对象仅持有虚引用,那么它就和没有任何引用同样,在任什么时候候均可能被垃圾回收器回收

几种引用分别位于java.lang.ref.SoftReference;    java.lang.ref.WeakReference; 和 java.lang.ref.PhantomReference;

 

  • 关于句柄和对象的一点记录

String s = new String("abc") ; 在编译阶段在文字池中建立“abc”对象,运行new时,将pool中的对象复制一份放到heap中,而且把heap中的这个对象的引用交给s持有,所以这条语句建立了2个String对象。

String s1 = new String("abc") ; String s2 = new String("abc") ; 共建立三个对象,pool中一个,heap中2个。

句柄能够不依赖于对象而存在,例如String s,建立了句柄s,在栈中保存,但并无任何对象相关联。句柄能够考虑为遥控器,对象为电视,拥有了遥控器,就能够操控电视,可是没有电视,同样能够有遥控器。

因为字符串对象的大量使用(它是一个对象,通常而言对象老是在heap分配内存),Java中为了节省内存空间和运行时间,在编译阶段就把全部的字符串文字放到一个文字池(pool of literal strings)中,而运行时文字池成为常量池的一部分。文字池的好处,就是该池中全部相同的字符串常量被合并,只占用一个空间。因此上述的 "abc" 只占一份空间。

String s1 = "abc" ;
String s2 = "abc" ;
这里 s1 == s2 成立

String s1 = new String("abc") ;
String s2 = new String("abc") ;
这里 s1 == s2 不成立,s1.equals(s2)成立

 

  • 垃圾回收算法

Java语言规范没有明确地说明JVM使用哪一种垃圾回收算法,可是任何一种垃圾收集算法通常要作2件基本的事情:(1)发现无用信息对象;(2)回收被无用对象占用的内存空间,使该空间可被程序再次使用。

大多数垃圾回收算法使用了根集(root set)这个概念;所谓根集就量正在执行的Java程序能够访问的引用变量的集合(包括局部变量、参数、类变量),程序可使用引用变量访问对象的属性和调用对象的方法。垃圾收集首选须要肯定从根开始哪些是可达的和哪些是不可达的,从根集可达的对象都是活动对象,它们不能做为垃圾被回收,这也包括从根集间接可达的对象。而根集经过任意路径不可达的对象符合垃圾收集的条件,应该被回收。下面介绍几个经常使用的算法。

一、 引用计数法(Reference Counting Collector)

引用计数法是惟一没有使用根集的垃圾回收的法,该算法使用引用计数器来区分存活对象和再也不使用的对象。通常来讲,堆中的每一个对象对应一个引用计数器。当每一次建立一个对象并赋给一个变量时,引用计数器置为1。当对象被赋给任意变量时,引用计数器每次加1当对象出了做用域后(该对象丢弃再也不使用),引用计数器减1,一旦引用计数器为0,对象就知足了垃圾收集的条件。

基于引用计数器的垃圾收集器运行较快,不会长时间中断程序执行,适宜地必须 实时运行的程序。但引用计数器增长了程序执行的开销,由于每次对象赋给新的变量,计数器加1,而每次现有对象出了做用域生,计数器减1。

二、tracing算法(Tracing Collector)

tracing算法是为了解决引用计数法的问题而提出,它使用了根集的概念。基于tracing算法的垃圾收集器从根集开始扫描,识别出哪些对象可达,哪些对象不可达,并用某种方式标记可达对象,例如对每一个可达对象设置一个或多个位。在扫描识别过程当中,基于tracing算法的垃圾收集也称为标记和清除(mark-and-sweep)垃圾收集器.

三、compacting算法(Compacting Collector)

为了解决堆碎片问题,基于tracing的垃圾回收吸取了Compacting算法的思想,在清除的过程当中,算法将全部的对象移到堆的一端,堆的另外一端就变成了一个相邻的空闲内存区,收集器会对它移动的全部对象的全部引用进行更新,使得这些引用在新的位置能识别原来 的对象。在基于Compacting算法的收集器的实现中,通常增长句柄和句柄表。

四、copying算法(Coping Collector)

该算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收。它开始时把堆分红 一个对象 面和多个空闲面, 程序从对象面为对象分配空间,当对象满了,基于coping算法的垃圾 收集就从根集中扫描活动对象,并将每一个 活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。

一种典型的基于coping算法的垃圾回收是stop-and-copy算法,它将堆分红对象面和空闲区域面,在对象面与空闲区域面的切换过程当中,程序暂停执行。

五、generation算法(Generational Collector)

stop-and-copy垃圾收集器的一个缺陷是收集器必须复制全部的活动对象,这增长了程序等待时间,这是coping算法低效的缘由。在程序设计中有这样的规律:多数对象存在的时间比较短,少数的存在时间比较长。所以,generation算法将堆分红两个或多个,每一个子堆做为对象的一代(generation)。因为多数对象存在的时间比较短,随着程序丢弃不使用的对象,垃圾收集器将从最年轻的子堆中收集这些对象。在分代式的垃圾收集器运行后,上次运行存活下来的对象移到下一最高代的子堆中,因为老一代的子堆不会常常被回收,于是节省了时间。

六、adaptive算法(Adaptive Collector)

在特定的状况下,一些垃圾收集算法会优于其它算法。基于Adaptive算法的垃圾收集器就是监控当前堆的使用状况,并将选择适当算法的垃圾收集器。

 

参考:

http://blog.csdn.net/program_think/article/details/4302366  编程随想  Java性能优化[4]:关于finalize函数

http://developer.51cto.com/art/201103/248642.htm  详解Java GC的工做原理

http://tech.qq.com/a/20060726/000329.htm 全面分析Java的垃圾回收机制

相关文章
相关标签/搜索