Java垃圾回收机制
一、垃圾回收机制的意义
Java语言中一个显著的特点就是引入了垃圾回收机制,使得Java程序员在编写程序的时候不再需要考虑内存管理问题。由于有个垃圾回收机制,Java中的对象不再有“作用域”的概念,只有对象的引用才有“作用域”。
· Java中垃圾回收的目的
识别并且丢弃应用不再使用的对象来释放和重用资源,有效防止内存泄漏,有效的使用空闲的内存。
二、垃圾回收算法一般要做2件基本的事情
1.发现无用信息对象;
2.回收被无用对象占用的内存空间,使该空间可被程序再次使用。
三、垃圾回收机制中的算法
1. 引用计数法(Reference Counting Collector)
在这种方法中,堆中每个对象实例都有一个引用计数器。
当一个对象被创建时,且将该对象实例分配给一个变量,该变量计数设置为1。
当任何其它变量被赋值为这个对象的引用时,计数加1(a=b,则b引用的对象实例的计数器+1)
但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。
任何引用计数器为0的对象实例可以被当作垃圾收集。
当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。
优点:
引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。
缺点:
无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.
public class Main {
public static void main(String[] args) {
MyObject object1 = new MyObject();
MyObject object2 = new MyObject();
object1.object = object2;
object2.object = object1;
object1 = null;
object2 = null;
}
}
最后面两句将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数器都不为0,那么垃圾收集器就永远不会回收它们。
2. 垃圾回收其他算法
根搜索算法
从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,
当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。
java中可作为GC Root的对象有
1.虚拟机栈中引用的对象(本地变量表)
2.方法区中静态属性引用的对象
3. 方法区中常量引用的对象
4.本地方法栈中引用的对象(Native对象)
2.1. 标记-清除(Mark-Sweep)算法
标记-清除算法采用从根集合进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收
优点:
实现起来比较容易
缺点:
但是有一个比较严重的问题就是容易产生内存碎片,
碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。
2.2. 复制(Copying)算法
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,
然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。
优点:
运行高效且不容易产生内存碎片,解决Mark-Sweep算法的缺陷
缺点:
对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。
如果存活对象很多,那么Copying算法的效率将会大大降低。
2.3. 标记-整理(Mark-Compact)算法
解决了Copying算法的缺陷,充分利用内存空间
算法标记阶段和 Mark-Sweep 一样,但是在完成标记之后,它不是直接清理可回收对象,
而是将存活对象都向一端移动,然后清理掉端边界以外的内存。
2.4. 分代收集(Generational Collection)算法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。
核心思想
是根据对象存活的生命周期将内存划分为若干个不同的区域。
一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),
老年代的特点是每次垃圾收集时只有少量对象需要被回收,
而新生代的特点是每次垃圾回收时都有大量的对象需要被回收。
目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,
但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,
每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,
然后清理掉Eden和刚才使用过的Survivor空间。
而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。
注意,在堆区之外还有一个代就是永久代(Permanet Generation),
它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。
3. 垃圾回收常用的函数
(1) finalize的执行过程(生命周期)
在垃圾回收之前执行
当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。
否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。
执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。
(2) System.gc() 提醒jvm进行垃圾回收
用来提示JVM要进行垃圾回收。但是,立即开始还是延迟进行垃圾回收是取决于JVM的。
实际上是调用如下代码:
public static void gc() {
Runtime.getRuntime().gc();
}
最终调用
java.lang.Runtime.gc()