(JVM)CMS垃圾回收器详解

259 阅读6分钟

垃圾回收器

参考链接:blog.csdn.net/coderlius/a… 参考链接:www.jianshu.com/p/86e358afd…

CMS垃圾回收器

适用场景: 牺牲了系统的吞吐量来追求收集速度,适合追求垃圾收集速度的服务器上。

JVM启动参数:-XX:+UseConcMarkSweepGC

CMS收集过程:

初始标记(CMS-initial-mark)

标记存活的对象,会导致stw。

  • 标记老年代中所有的GC Roots对象

  • 标记年轻代中活着的对象引用到的老年代的对象

并发标记(CMS-concurrent-mark)

从“初始标记”阶段标记的对象开始找出所有存活的对象

  • 将在并发阶段新生代晋升到老年代的对象直接在老年代分配的对象以及引用关系发生变化的老年代对象所在的区域card标记为dirty,避免在后续阶段扫描整个老年代。

预清理(CMS-concurrent-preclean)

处理上一个阶段因为引用关系改变导致没有标记的存活对象

  • 会扫描所有标记为Dirty的区域Card

可被终止的预清理(CMS-concurrent-abortable-preclean)

尽量承担STW(stop-the-world)中重新标记阶段的工作

重新标记(CMS-remark)

完成标记整个年老代的所有的存活对象,会导致swt。

  • 重新标记的内存范围是整个堆,包含_young_gen和_old_gen。

在重新标记之前,先执行一次ygc,回收掉年轻带的对象无用的对象,并将对象放入幸存带或晋升到老年代

并发清除(CMS-concurrent-sweep)

清除没有标记的对象并且回收空间

老年代CMS每个收集周期都要经历:初始标记、并发标记、重新标记、并发清除。

初始标记以STW的方式标记所有的根对象

年轻代使用STW式的并行收集

老年代回收采用CMS进行垃圾回

Garbage First (G1)

初始标记、并发标记、重新标记、清除、转移回收。

特点

  1. G1的设计原则是 "首先收集尽可能多的垃圾(Garbage First)" 。
  • G1并不会等内存耗尽(串行、并行)或者快耗尽(CMS)的时候开始垃圾收集,而是在内部采用了启发式算法,在老年代找出具有高收集收益的分区进行收集。

  • G1 根据用户设置的暂停时间目标自动调整年轻代和总堆大小

暂停目标越短年轻代空间越小、总空间越大。

  1. G1采用内存分区(Region)的思路
  • 将内存划分为一个个相等大小的内存分区,回收时则以分区为单位进行回收

  • 存活的对象复制到另一个空闲分区中

  • 由于都是以相等大小的分区为单位进行操作,因此G1天然是一种压缩方案(局部压缩)

  1. 虽然G1也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别
  • 不需要完全独立的survivor(to space)堆做复制准备
  1. G1的收集都是STW的,但年轻代和老年代的收集界限比较模糊,采用了混合(mixed)收集的方式。
  • 即每次收集既可能只收集年轻代分区(年轻代收集),也可能在收集年轻代的同时,包含部分老年代分区(混合收集)

  • 即使堆内存很大时,也可以限制收集范围,从而降低停顿。

Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外)

JVM常见错误

OutOfMemoryError 与 StackOverflowError

OutOfMemoryError 和StackOverflowError的父类是VirtualMachineError。

OutOfMemoryError :内存不足时抛出

StackOverflowError:栈溢出时抛出

使用super()方法直接继承VirtualMachineError,而VirtualMachineError使用super()方法直接继承Error。Error继承Throwable。

/**
 * Thrown when the Java Virtual Machine cannot allocate an object
 * because it is out of memory, and no more memory could be made
 * available by the garbage collector.
 * @author  unascribed
 * @since   JDK1.0
 */
public class OutOfMemoryError extends VirtualMachineError {
    private static final long serialVersionUID = 8228564086184010517L;
    /**
     * Constructs an {@code OutOfMemoryError} with no detail message.
     */
    public OutOfMemoryError() {
        super();
    }

    /**
     * Constructs an {@code OutOfMemoryError} with the specified
     * detail message.
     *
     * @param   s   the detail message.
     */
    public OutOfMemoryError(String s) {
        super(s);
    }
}

/**
 * Thrown when a stack overflow occurs because an application
 * recurses too deeply.
 *
 * @author unascribed
 * @since   JDK1.0
 */
public class StackOverflowError extends VirtualMachineError {
    private static final long serialVersionUID = 8609175038441759607L;

    /**
     * Constructs a <code>StackOverflowError</code> with no detail message.
     */
    public StackOverflowError() {
        super();
    }

    /**
     * Constructs a <code>StackOverflowError</code> with the specified
     * detail message.
     *
     * @param   s   the detail message.
     */
    public StackOverflowError(String s) {
        super(s);
    }
}

/**
 * Thrown to indicate that the Java Virtual Machine is broken or has
 * run out of resources necessary for it to continue operating.
 *
 *
 * @author  Frank Yellin
 * @since   JDK1.0
 */
abstract public class VirtualMachineError extends Error {
    private static final long serialVersionUID = 4161983926571568670L;

    public VirtualMachineError() {
        super();
    }

    public VirtualMachineError(String message) {
        super(message);
    }

    /**
     * Constructs a {@code VirtualMachineError} with the specified
     * detail message and cause.  <p>Note that the detail message
     * associated with {@code cause} is <i>not</i> automatically
     * incorporated in this error's detail message.
     * @since  1.8
     */
    public VirtualMachineError(String message, Throwable cause) {
        super(message, cause);
    }
    
    public VirtualMachineError(Throwable cause) {
        super(cause);
    }
}

内存泄露

当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这产生了内存泄漏。

检查内存泄露

MAT工具

栈溢出

栈内存为线程私有的空间,每个线程都会创建私有的栈内存

栈空间内存设置过大,创建线程数量较多时会出现栈内存溢出StackOverflowError。

解决栈溢出

  1. 用栈把递归转换成非递归

一个函数在调用另一个函数之前, 操作如下:

a)将參数,返回地址等信息传递给被调用函数保存; b)为被调用函数的局部变量分配存储区; c)将控制转移到被调函数的入口。

从被调用函数返回调用函数之前, 操作如下:

a)保存被调函数的计算结果; b)释放被调函数的数据区; c)按照被调函数保存的返回地址将控制转移到调用函数.

  1. 使用static对象替代nonstatic局部对象

  2. 增大堆栈大小值

Java虚拟机的堆大小设置:

–Xms 128m   //JVM占用最小内存
–Xmx 512m   //JVM占用最大内存
–XX:PermSize=64m   //最小堆大小
–XX:MaxPermSize=128m //最大堆大小

JVM常见面试题

  1. 听说你对JVM有点研究,讲一讲JVM的内存模型吧(我说虚拟机栈,本地方法栈,程序计数器,堆,方法区)

  2. 如何判断一个对象的存活?(fialized()是细节)

  3. 程序计数器法的缺点,JDK采用的是哪一个方法?

  4. GC的算法有哪些?

  5. 说说你所知道的垃圾回收器,JDK8采用的是哪一个?