概述

Java和C++相比最大的好处就在于自动的进行垃圾回收,不需要程序员手动释放,这大大的方便了我们的开发,不过作为一个合格的Java程序开发人员, 我们还是有必要了解一下内存回收的一些知识

内存结构

JVM内存结构如上图所示,每一部分都有专门的用处

  • java虚拟机栈:java虚拟机栈是线程私有的,方法在执行的时候都会创建一个栈帧,主要用来方法调用过程中的一些变量,这些变量包括:局部变量表(各种基本类型和引用类型的指针),方法出口等信息

  • 本地方法栈:和java虚拟机栈的作用是相似的,只不过本地方法栈是用来为Native方法服务的

  • 程序计数器:程序计数器用来记录程序执行的行号,是为了确保线程切换之后能恢复到正确的位置继续执行,每一个线程都有一个独立的计数器(程序计数器是线程私有的)

  • 堆内存:用来存放对象实例,也是垃圾回收算法主要回收的一块地方

  • 方法区:非堆,主要用来存放类信息、常量、静态变量(类级别的变量)等数据

    对象存活的判定

    引用计数算法

    引用技术算法是通过在给对象添加一个引用计数器,对象被引用一次就将该计数值加1,当对象的引用值为0的时候,就可以确定该对象可以回收了,这种 算法有个缺点:循环引用。 如上图所示,假设Obj A和Obj B都可以被回收的情况下,A、B相互引用,那么这两个对象都不会被回收掉。 不过引用计数法还是有一些使用场景的,它的优点就是实现简单,只是在JVM中我们没有采用这种方式来对对象存活进行判定。

    可达性分析算法

    可达性分析算法是通过一系列的GC Root的对象作为起点,判断这些GC Root对象到堆内存中的对象是否可达,如果不可达,说明对象可以被回收了,反之,对象还存活着。 值得一说的是,这些GC Root对象的选取是有条件的,必然不能是堆内存中的对象作为GC Root(试想一下,如果GC Root可以是堆内存中的对象的话,那么可达性分析算法 和上面的引用计数法就没有区别了),可以作为GC Root的对象如下:

  • 栈(包括java虚拟机栈和本地方法栈)中的局部变量

  • 方法区中的类变量、静态常量

    垃圾收集的算法

    标记清除

    标记清除会将需要回收的对象进行标记,在标记完之后触发垃圾回收操作,这种垃圾回收算法会产生以下问题:

  • 空间问题:会有很多内存碎片

  • 效率问题:当产生很多内存碎片之后,再次给对象分配内存的时候,可能会需要寻找很久才可以找到合适的空间

    复制算法

    复制算法是将内存划分为大小相等的两块,每次分配对象的时候都会将存活的对象移动到另外一块内存中,这样原来的内存空间就可以全部回收了,不过 这种回收算法有着明显的缺陷,那就是内存的使用率不高,每次都只使用了50%

标记整理

标记整理分为两个阶段,标记阶段会讲存活的对象进行标记,整理阶段会将存活的对象向内存的一端移动,然后将边界之外的内存全部回收掉

分代回收

当前主要的垃圾回收都采用分代回收的策略:将内存划分为新生代和老年代,并针对不同的内存采用不同的回收算法:

  • 新生代:新生代一般存放生命周期比较短的对象。在新生代,每次垃圾回收都会有大量的对象被回收掉,因此采用复制的算法可以大大的节约内存的浪费,将新生代的内存划分为两个surviver区和一个Eden区,每次回收都会将from surviver 和Eden区中存活的对象移动到另外一个surviver中,由于存活的对象surviver可能无法全部回收,因此在垃圾回收的时候会使用老年代进行担保。该过程我们称为minor GC
  • 老年代:老年代一般存放生命周期比较长的对象。由于老年代没有额外的担保,因此在垃圾回收的时候采用的是标记整理的算法。该过程我们称为full GC

无论是新生代还是老年代的垃圾回收都会stop the world,也就是minor、full GC被触发的话都会jvm都会停下来,直至gc完成之后才会再次触发gc。不过minor GC一般会比full GC快很多。

垃圾回收器

上面讨论的是垃圾回收的策略,不过针对不同的情况我们需要选择不同的垃圾回收器。常见的垃圾回收器有:

  • serial collector(单线程回收器):这种是单线程的垃圾回收器,缺点也很明显,那就是耗时会比较久,一般在本地调试的时候使用
  • parallel collector(多线程回收器):这种回收器相较于单线程回收器,多线程回收器会加快垃圾回收的速度,这里的代表室throughput collector,这里是并行垃圾回收器(不是并发)
    • 并发垃圾回收器,区别于并行垃圾回收器,并发垃圾回收器只会在整理对象的时候才会stw,在删除对象的时候并不会stw,这里面比较有代表性的是CMS 垃圾回收器、G1垃圾回收器(jdk11默认的垃圾回收器),并发垃圾回收会消耗大量cpu时间。CMS的缺点是老年代的垃圾回收会引入很多碎片,G1对垃圾回收的策略是将每一个generation细分为region,每次针对一个个的region进行垃圾回收,这样可以大大的改善gc的时间,进而提升系统的可用性

GC的触发条件

  • 空间不足的时候
  • System.gc()方法调用的时候,不建议使用
  • 空间分配担保失败的时候

– 附:通常我们可以使用printGcdetails、jstat来获取jvm内存视图,将其dump到磁盘生成文件之后,通过EMA的工具来进行问题的定位及分析。