【java学习】垃圾回收机制(GC)

来源:互联网 发布:网络机柜设备布置图 编辑:程序博客网 时间:2024/04/29 04:08

1,概念

1)垃圾

在Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾。

有2种情况:
①一个或一组对象为不可到达状态(即一个对象不被任何引用所指向)。
②一组对象中只包含互相的引用,而没有来自它们外部的引用。

2)GC功能

a.释放没用的对象
JVM的一个系统级线程会自动释放该垃圾内存块
b.清除内存纪录碎片

3)内存泄漏(OOM)

2,GC优缺点

1)优点

①自动释放内存空间,减轻编程的负担
②保护程序的完整性, 防止内存泄露

2)缺点

①影响程序性能。
Java虚拟机必须追踪运行程序中有用的对象,而且最终释放没用的对象。这一个过程需要花费处理器的时间。
②并不能保证100%收集到所有的废弃内存。
在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象。而java的垃圾回收机制虽然解决了C++中令人头痛的垃圾回收问题,但同时由于它不是主动性的行为,完备性欠缺。另外,也有垃圾回收器不能处理的垃圾回收情况。

3,过程

垃圾回收机制的算法有很多。

1)引用计数法

①概念

GC中早期策略。
堆中每个对象实例都有一个引用计数,当计数为0时GC。
当一个对象被创建时且分配给一个变量时,该变量计数设置为1。当任何其他变量被赋值这个对象的引用时,计数+1。
当某个引用超过了声明周期或者被设置为一个新值,则-1。

②优缺点

优点:快速。
对程序需要不被长时间打断的实时环境比较有利。

缺点:无法检测出循环引用。
如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.

2)tracing算法(标记清除算法 mark and sweep)

①概念

把所有的引用关系看作图。
从一个节点GC Root开始,寻找对应的节点,找到这个节点以后,继续寻找这个节点的引用节点并标记,当所有节点被寻找完毕后,剩余的节点没有被引用到的节点,即无标记的节点,是无用的节点。

可做GC Root的对象有:
JVM栈中引用的对象(本地变量表)
方法区中静态属性引用的对象
方法区中常量引用的对象
本地方法栈中引用的对象(Native对象)

②场景

标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片。

3)compacting算法(标记整理法)

①概念

与标记清除法一样,不同的是,在清楚后,回收不存活对象的控件,并移动存活对象往左端空闲空间移动,并更新指针。

②优缺点

进行了对象的移动,成本更高,但是却解决了内存碎片的问题。

4)copying算法

将每个活对象赋值到空闲面,空闲面做对象面,对象面变成空闲面,程序在新的对象面分配内存。

5)generation算法(分代回收)

①年轻代:
新生的对象。
尽可能的快速的收集那些生命周期短的对象。

②年老代:
在年轻代中经历了N次GC仍然存活的对象。
主要存放一些生命周期较长的对象。

③持久代:
用于存放静态文件,如Java类、方法。

4,主动GC

1)System.gc()方法

调用System.gc()可以主动请求垃圾回收机制,但也仅仅是一个请求。JVM接受这个消息后,并不是立即做垃圾回收,而只是对几个垃圾回收算法做了加权,使垃圾回收操作容易发生,或提早发生,或回收较多而已。

良好的编程习惯是:
GC调用不宜过多,保持代码健壮(记得将不用的变量置为null、资源使用完后释放),让虚拟机去管理内存。因为System.gc()方法会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。

2)finalize()方法

一旦垃圾回收器准备好释放对象占用的存储空间,首先会去调用finalize()方法进行一些必要的清理工作。只有到下一次再进行垃圾回收动作的时候,才会真正释放这个对象所占用的内存空间(所以调用finalize()并不一点会GC)。
另外finalize()函数是在垃圾回收器准备释放对象占用的存储空间的时候被调用的,绝对不能直接调用finalize(),所以应尽量避免用它。

finalize生命周期流程:
当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。在C++中所有的对象运用delete()一定会被销毁,而JAVA里的对象并非总会被垃圾回收器回收。

5,触发主GC(Garbage Collector)的条件

①当应用程序空闲时,即没有应用线程在运行。
因为GC在优先级最低的守护线程中进行,所以当应用忙时,GC线程就不会被调用,但以下条件除外。
②Java堆内存不足。
主GC的运行具有不确定性,无法预计它何时必然出现,但可以确定的是对一个长期运行的应用来说,其主GC是反复进行的。

6,其他减少GC开销的措施

1)尽量减少临时对象的使用

临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,减少了主GC的机会。

2)尽量使用StringBuffer,而不用String来累加字符串

由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,如Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。

3)能用基本类型如Int,Long,就不用Integer,Long对象

基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。

4)尽量少用静态对象变量

静态变量属于全局变量,不会被GC回收,它们会一直占用内存。

5)分散对象创建或删除的时间

集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行主GC,以回收内存或整合内存碎片,从而增加主GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。

7,与其他语言对比

1)C

①NET Framework类库具有垃圾回收功能,当某个类的实例被认为不再有效时,并符合析构条件,此时启动垃圾回收功能,调用该类的析构函数。
②写法:~类名(){}
③特点:一个类只有一个析构函数且无法调用,它是被自动调用的。

原创粉丝点击