JVM垃圾回收

来源:互联网 发布:陆小凤传奇知乎 编辑:程序博客网 时间:2024/06/07 20:25

1 What:哪些内存区域需要回收

  • 程序计数器,虚拟机栈,本地方法栈随线程而生,随线程而灭。栈中栈帧随着方法的调用和返回自动的入栈和出栈,并且栈帧需要多大在编译期就是可知的。因此这些区域的内存分配和回收是确定的,故不需要GC来参与。

  • 堆:对象和数组基本都在这个区域,是GC的主力

  • 方法区:无用的类型信息和const都是回收的目标,但一般他们是垃圾的概率不大,故这个区域GC的性价比不高

2 What:堆区哪些对象需要被回收

  • 确定哪些对象需要回收

    1. 引用计数法:

          使用简单,效率很高。但解决不了相互循环引用的问题。Python和其他一些语言采用的这个方法。
    2. 可达性分析法:

          1) 从GC Roots开始,向下搜索节点,搜索的路径就是引用链。不在引用链中的对象就是无用的对象。    2) GC Roots:        a) JVM栈中引用的对象        b) 类static引用,final引用        c) 本地方法栈中引用的对象

      这里写图片描述

  • 引用类型

        i. 强引用:只要引用存在,JVM肯定不会回收它指向的对象    ii. 软件用:要发生OOM时,JVM会回收只有软引用指向的对象    iii. 弱引用:GC时,JVM会回收只有弱引用指向的对象,不论是否内存紧张    iv. 虚引用:不能通过它取得指向的对象实例,它的唯一作用是对象被回收时的系统通知。
  • finalize(): 临终遗言,有一次越狱机会

    • 可达性分析完成后,不在引用链中的对象可以被标记出来了。它们会进行一次筛选,override了finalize方法的对象,且之前此对象的finalize方法没有被调用过,则在回收对象之前,需要调用它的finalize方法。

    • finalize方法的调用会在Finalizer线程中进行,这个线程优先级很低。JVM会等待finalize方法执行,然后在进行回收。但JVM会设置执行超时时长,如果timeout,则不等待finalize方法执行完,而将对象回收。这样做目的是防止finalize中执行耗时操作,极端情况下,finalize中发生了死锁,会导致对象回收不了。

    • finalize方法其实提供了一次死里逃生的机会。如果对象在finalize中重新与引用链建立了关联,比如将this引用赋给了类static变量,则JVM会将此对象移除出回收list。但这个机会只有一次,因为finalize方法只会调用一次。当此对象第二次要被回收时,就不会执行finalize方法了。

3 How:如何回收

3.1 标记-清除法:

  • 将无用对象标记出来,然后释放掉它们的内存。释放内存是通过将此逻辑地址区域在进程内存分配页表中去除而实现。

  • 缺点:效率不高,清除时需要反复修改页表。容易产生内存碎片,导致之后的对象内存分配效率变低,甚至找不到一块内存可以放下对象。

标记清除法

3.2 复制

  • 将堆分为两半,每次对象分配时,只在其中一半上分配。GC时,将有用对象复制到另外一半上。
  • 优点:避免了内存碎片问题。且在有用对象较少时,效率很高。
  • 缺点:浪费了一半内存

这里写图片描述

3.3 标记-整理法

 和标记清除类似,只是标记完后,将有用对象移动到上一个有用对象的末尾。这样可以使得所有有用对象挨在一起,即没有内存碎片。

这里写图片描述

3.4 JVM中采用的算法:

  • 基本上所有的JVM都采用了分代算法,即新生代使用复制算法,老生代使用标记整理算法。

  • 新生代:垃圾概率比较大,一般将刚刚new出来的对象放到新生代。

    • 分为Eden + Survivor + Survivor,默认按照8:1:1的大小配置(比例可以自己配置)
    • 刚new出来的放到Eden区
    • 当新对象在Eden区放不下时,会触发新生代内存的GC,称为Minor
      GC。将Eden和存放了对象的一个survivor中的有用对象复制到另一个空白的survivor中。如果survivor能够放得下这些有用对象,则GC完成。如果不能,则将这些有用对象直接复制到老生代中。然后新生代中就完全没有有用对象了。
    • IBM研究过,超过98%的对象都熬不到下一次GC,故新生代GC效率还是很高且很有保障的。
  • 老生代:垃圾概率比较小的区域

    • 新生代中GC时装不下的有用对象会复制到老生代
    • 大对象直接进入老生代
    • 新生代中长期存活的对象将进入老生代。对象的对象头中有一个Age计数标志,对象每熬过一次Minor
      GC,Age会加1.当Age达到阈值时(默认15),对象会晋升到老生代中。
    • 如果对象在老生代中放不下时,会触发老生代GC,也叫Major GC 或Full GC。每次Full Gc时,也会触发一次Minor Gc。Full Gc的速度一般比Minor Gc慢10倍以上。

这里写图片描述

4. When:何时触发GC

4.1. GC完全由JVM发起,不受应用控制。即使调用System.gc()也不会立刻进行GC,只是建议JVM进行垃圾回收而已

4.2 . GC时机:

  • 新对象在Eden区放不下时,触发新生代Gc
  • 对象在老生代中放不下时,会触发老生代Gc,此时,一般也会伴随一次新生代GC

5 垃圾收集器

这里写图片描述

5.1. 新生代区域

  • Serial:单条GC线程,Gc时,会停止所有的其他线程,被称为“stop the
    world”。现行的垃圾收集器都只能减少stop其他线程的时间,但还没有做到完全不stop

  • ParNew:与Serial的唯一区别在于,采用了多条GC线程。多CPU下效率提高了,但单CPU下,效率不如Serial,因为切换线程和线程同步都需要额外开销

  • Parallel Scavenge:它的特点在于,它是以最大化吞吐量为目标的。而其他的是已减少Gc时用户线程的停顿时间为目标。

        吞吐量 = 运行用户代码时间 / (运行用户代码时间 + GC时间)   停顿时间指GC时,单次GC时用户线程停止的时间

5.2 老生代区域

  • Serial Old
  • Parallel Old:parallel Scavenge只能与Parallel Old配合使用,它也是吞吐量优先收集器
  • CMS:多条线程,低停顿,最大化减少了Stop用户线程的时间。它分为4个阶段
    • 初始标记:耗时少,会stop用户线程。此时只标记能与Gc Roots直接关联的对象
    • 并发标记:耗时长,但可以与用户线程并行。此时标记Gc roots开始的引用链
    • 重新标记:耗时少,会stop用户线程。此时重新修正用户运行中产生的标记变动
    • 并发清除:耗时长,但可以与用户线程并行。此时采用标记-整理算法,清除垃圾

6 项目中debug:

  1. 查看Gc日志,异常堆栈(我们的crashHandler会打印出),heapDump,threadump等手段

这里写图片描述

这里写图片描述

 2. VisualVM:多合一故障分析工具
1 0
原创粉丝点击