java垃圾收集器与内存分配策略(1)

来源:互联网 发布:python 打开html文件 编辑:程序博客网 时间:2024/05/10 08:32

概述

让我们先想一想,如果让我们完成垃圾收集(Grabage Collection, GC),我们需要考虑哪些事情?
(1)首先肯定需要先知道哪些是垃圾,哪些不是。(2)然后再确定什么时候回收垃圾。(3)最后就是如何去回收。
还需要注意的一点是:
程序计数器,虚拟机栈、本地方法栈都是线程所私有的,它们随线程而生,也随线程而灭。当方法结束或线程结束时,对应的内存自然就跟随着回收了。这三个区域的内存分配与回收都具备确定性。所以不需要过多考虑。
而java堆和方法区不一样,它们是线程所共享的,只有在程序运行期间才能知道哪些对象被创建。这部分内存的分配与回收都是动态的。垃圾收集所关注的就是这部分内存。

对象已死?

首先我们先要决定哪些内存是垃圾,哪些不是。那些还“活着的“对象就不是垃圾,“死了的“(不可能再通过任何途径使用的对象)就是垃圾。

引用计数法

  • 原理
    给每一个对象添加一个引用计数器,每当有一个地方引用它时,计数器值加一;当引用失效时,计数器值减一。任何时刻计数器值为0的对象就是不可能再被使用的。
  • 优缺点
    优点:实现简单、判定效率高。
    缺点:很难解决对象之间的相互循环引用的问题。如:
public class RefenceCountingGC {    public Object instance = null;    private static final int  _1MB = 1024*1024;    private byte[] data = new byte[2 * _1MB];    public static void main(String[] args) {        RefenceCountingGC obj1 = new RefenceCountingGC();        RefenceCountingGC obj2 = new RefenceCountingGC();        obj1.instance = obj2;        obj2.instance = obj1;        obj1 = null;        obj2 = null;        System.gc();    }}

如果用引用计数法,obj1和obj2都被置为null之后,两个对象都无法再被使用。本应该作为垃圾回收,但是因为obj1和obj2相互引用,使得这两个对象的引用计数都不为0,GC收集器无法回收它们。

根搜索算法

  • 原理
    通过一系列名为“GC Roots“的对象作为起始点开始搜素,当GC Roots到某个对象不可达时,则说明此对象是不可用的,就被判定为垃圾。
    GC Roots对象包括:
    虚拟机栈中所引用的对象。
    方法区中的类的静态属性所引用的对象。
    方法区中的常量引用的对象。
    本地方法栈中JNI(即一般说的Native方法)的引用的对象。

再谈引用

上述两种算法都涉及对象的“引用“。在java中,引用有四种类型。分别为:
强引用、软引用、弱引用、虚引用。

回收方法区

有时方法区也称为永久代。一般来说java堆中的新生代垃圾回收比例较高,一般一次可以回收70%-95%的空间;java堆中的老年代比例较低;永久代最低。但是还是用必要对老年代和永久代进行垃圾回收。
永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。
- 回收废弃的常量
原理:以常量池中的字面量为例。假如一个字符串“abc“进入常量池后,过段时间之后没有任何一个String对象叫做“abc“,那么常量池中的“abc“就是废弃的常量。
- 回收无用的类
满足以下三个条件的类:
(1)java堆中不存在该类的任何实例。
(2)加载该类的ClassLoader已经被回收。
(3)没有通过反射访问。

垃圾收集算法

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

  • 原理:
    先标记所有需要回收的对象,在标记完成后统一回收所以被标记的对象。
  • 缺点:
    (1)效率问题:标记和清除的效率都不高
    (2)空间问题:会产生大量不连续的内存碎片。可能分配大的对象时无法找到足够的连续内存,不得不提前触发另一次垃圾收集。
  • 适用于:老年代或永久代

复制算法(copying)

  • 基本原理
    可以把内存分为大小相等的两块,每次使用一块,这一块内存用完之后就把活着的对象复制到另一块上,然后清理掉这块内存。
  • 优缺点
    优点:不会有内存碎片,简单高效。
    缺点:可用内存缩小为原来的一半。
  • 适用于:新生代
  • 改进
    因为新生代大部分对象都是“朝生夕灭“的,所以可以将新生代内存分为三块:一块较大的Eden空间,两块较小的Survivor空间。每次使用Eden和一块Survivor空间,回收时将活着的对象(很少)拷贝到另一块Survivor空间。当Survivor空间不够用时,需要依赖其他内存(老年代)进行分配担保。

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

  • 原理
    先标记可回收对象,完成之后将所有存活的对象向内存的一端移动,然后直接清理掉端边界以外的内存。
  • 优点
    相比于标记-清除算法,它不会产生内存碎片。
  • 适用于:老年代、永久代