java 垃圾回收机制

1. 哪些区域的内存需要回收。

  Java运行时内存区域:程序计数器,虚拟机栈,本地方法栈,堆,方法区。前三者是线程私有的,堆和方法区是线程共享的。

  其中,程序计数器、虚拟机栈和本地方法栈随着线程的创建而分配,随着线程的死亡回收。栈中栈帧分配的内存大小基本在类结构确定时就已知。这几个区域的内存分配和回收都具有确定性,不必考虑内存回收的问题。

  因此垃圾回收器主要考虑Java堆和方法区。

  方法区需要回收两部分的内容:无用的常量,无用的类。

  无用的类:堆中不存在该类的实例;该类对应的Class对象没有被引用;无法通过反射访问类的方法;该类的ClassLoader已经被回收。

2. 确定哪些内存需要回收

  • 引用计数

    可能存在已经没用的对象,但它们之间存在循环引用,导致这些对象所占用的内存无法被回收。

  • 利用可达性分析

    GC Roots:虚拟机栈中引用的对象;方法区中静态属性引用的变量;方法区中常量引用的变量;本地方法栈中引用的对象。 

3. 垃圾收集算法

  • “标记-清除”:标记需要回收的对象,统一回收被标记为无用的对象。由于缺乏内存整理,可能会产生大量的内存碎片;
  • “复制”:将可用内存分为两个大块,同一时刻只有一个大块负责分配内存,当垃圾回收时,将存活的对象复制到另一个大块上,清空原来的大块,这样就不用考虑内存碎片的问题。

    在Java堆的“新生代”中,将新生代分为Eden区和两个Survivor区,使用“复制”算法进行垃圾回收。

  • “标记-整理”:针对“标记-清除”算法产生内存碎片的问题,在“标记”完成后,将存活的对象向一端移动,并清理掉端边界以外的内存。

    在Java堆的“老年代”中,使用“标记-整理”算法或者“标记-清除”算法。

4. 垃圾收集器种类

“新生代”使用的垃圾收集器包括:Serial、ParNew、Parallel Scavenge,这几个垃圾回收器都是使用“复制”算法。

  • Serial:所有用户线程停止,垃圾回收器使用单线程进行垃圾回收,直到回收工作完成,才启动用户线程。
  • ParNew: 所有用户线程停止,垃圾回收器使用多线程进行垃圾回收,直到回收工作完成,才启动用户线程。其目的是减少垃圾回收时用户线程的停顿时间,适合需要有用户交互的程序。
  • Parallel Scavenge:与ParNew类型,但更关注吞吐量(用户代码运行时间/(用户代码运行时间+垃圾回收时间)),适合后台运算,交互较少的任务。

“旧生代”使用的垃圾回收器包括:Serial Old、Parallel Old、CMS收集器,前两个使用“标记-整理”算法,CMS使用“标记-清理”算法。

  • Serial Old: 所有用户线程停止,垃圾回收器使用单线程进行垃圾回收,直到回收工作完成,才启动用户线程。
  • Parallel Old:所有用户线程停止,垃圾回收器使用多线程进行垃圾回收,直到回收工作完成,才启动用户线程。与Parallel Scavenge搭配可以优化“吞吐量”。
  • CMS收集器:使用“标记-清除”算法。分为四个阶段。

    初始标记:仅标记GC Roots可以直接关联到的对象,速度较快;

    并发标记:从GC Roots进行Tracing的过程,时间较长;

    重新标记:修正并发标记过程中因用户程序继续运行导致标记产生变动的那一部分对象;

    并发清理:清理内存,会产生内存碎片。

5. 内存分配与回收策略

  • 一般情况下,新的对象分配到“新生代”的Eden区;
  • 当Eden区内存不足时,会发起一次Minor GC;
  • 大对象直接分配到“旧生代”,从而避免在Eden和Survivor区进行大量内存复制;
  • 当“新生代”的对象经过若干次Minor GC还存活时,会进入“旧生代”;
  • 在发生Minor GC前,会判断“旧生代”最大可用连续空间是否大于“新生代”所有对象的总空间,以决定本次Minor GC是否伴随风险。
  • 如果“旧生代”满了,会触发Full GC

 

参考:http://www.alidata.org/archives/1773

  《深入理解Java虚拟机》第3章

posted on 2015-03-10 21:36  linxiong1991  阅读(152)  评论(0)    收藏  举报

导航