4.2.1.垃圾回收机制简述

在java中,程序员是不需要显式的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫描那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。

4.2.2.GC是什么?为什么要GC

GC 是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放已分配内存的显示操作方法。

4.2.3.垃圾回收的优点

优点:

  • java语言最显著的特点就是引入了垃圾回收机制,它使java程序员在编写程序时 不再考虑内存管理的问题。
  • 由于有这个垃圾回收机制,java中的对象不再有“作用域”的概念,只有引用的对象才有“作用域”。 垃圾回收机制有效的防止了内存泄露,可以有效的使用可使用的内存。

垃圾回收器通常作为一个单独的低级别的线程运行,在不可预知的情况下对内存堆中已经死亡的或很长时间没有用过的对象进行清除和回收。 程序员不能实时的对某个对象或所有对象调用垃圾回收器进行垃圾回收。 垃圾回收有分代复制垃圾回收、标记垃圾回收、增量垃圾回收。

4.2.4.垃圾回收可以马上回收内存吗?

对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。 通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是”可达的”,哪些对象是”不可达的”。当GC确定一些对象为”不可达”时,GC就有责任回收这些内存空间。 所以垃圾回收是可以马上回收内存的。

4.2.5.主动通知程序垃圾回收的方法

程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。

4.2.6.如何判断对象是否可以回收

垃圾收集器在做垃圾回收的时候,首先需要判定的就是哪些内存是需要被回收的,哪些对象是「存活」的,是不可以被回收的;哪些对象已经「死掉」了,需要被回收。
一般有两种判断方法:

  • 引用计数器法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为0时就可以被回收。
    • 优点:实现简单,判定效率也很高
    • 缺点:很难解决对象之间相互循环引用的问题,基本上被抛弃
  • 可达性分析法:通过一系列的成为“GC Roots”(活动线程相关的各种引用,虚拟机栈帧引用,静态变量引用,JNI引用)的对象作为起始点,从这些节点ReferenceChains开始向下搜索,搜索所走过的路径成为引用链,当一个对象到GC ROOTS没有任何引用链相连时,则证明此对象时不可用的;

    4.2.7.对象什么时候可以进行垃圾回收

    当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。要真正宣告这个对象的死亡,至少包括两次标记才可以

  • 两次标记过程:

对象被回收之前,该对象的finalize()方法会被调用;两次标记,即第一次标记不在“关系网”中的对象。第二次的话就要先判断该对象有没有实现finalize()方法了,如果没有实现就直接判断该对象可回收;如果实现了就会先放在一个队列中,并由虚拟机建立的一个低优先级的线程去执行它,随后就会进行第二次的小规模标记,在这次被标记的对象就会真正的被回收了。

4.2.8.永久代会发生垃圾回收吗

方法区是一种概念,而永久代、元空间是他的一个具体的实现;

jdk8以前方法区的实现叫做永久代,这个永久代里包含了这样几部分内容:它可以存储类的信息(也就是类里面的成员变量,成员方法,构造器,还有类加载器),还有一块叫运行时常量池,常量池里有一个比较重要的就是我们通常俗称叫串池的东西,它正式的名称叫StringTable(字符串表名)。

jdk8使用元空间作为方法区的实现,这个元空间里会存储类,类加载器,常量池的信息,不过它已经不占用我们的堆内存了,也就是说它已经不是由jvm来管理它的内存结构了,它已经被移出到本地内存当中,本地内存就是操作系统内存stringTable它不再放在方法区的实现里(元空间),它被移到了堆里面

而1.7以前的jdk采用的是永久代作为方法区的实现,在1.7及以前的jdk版本,永久代的空间不足也会导致fullGC,
1.7以前,永久代空间如果设小了,就会触发整个堆的一次full GC,经过这样的一次定位就初步定位到了是由于永久代空间不足导致了堆的full GC

1.8以后由于改成了元空间,它的垃圾回收就不是由java来控制了,元空间的默认情况下内存空间是使用的操作系统的内存空间,所以空间的容量是比较充裕的,不会发生元空间的空间不足问题。

4.2.9.垃圾回收算法

垃圾回收算法:复制算法、标记清除、标记整理、分代收集

  • 标记清除:(cms)

标记出所有需要回收的对象,在标记完成后统⼀回收所有被标记的对象

  • 复制算法:(young)

将内存分为⼤⼩相同的两块,每次使⽤其中的⼀块。当这⼀块的内存使⽤完后,就将还存活的对象复制到另⼀块去,然后再把使⽤的空间⼀次清理掉。这样就使每次的内存回收都是对内存区间的⼀半进⾏回收;

  • 标记整理:(old)

标记过程仍然与“标记-清除”算法⼀样,再让所有存活的对象向⼀端移动,然后直接清理掉端边界以外的内存;解决了产生大量不连续碎片问题

  • 分代收集:

根据对象存活周期的不同将内存划分为几块,一般是新生代和老年 代,新生代基本采用复制算法,老年代采用标记整理算法。

4.2.9.1.标记清除

标记无用对象,然后进行清除回收。
标记-清除算法(Mark-Sweep)是一种常见的基础垃圾收集算法,它将垃圾收集分为两个阶段:

  • 标记阶段:标记出可以回收的对象。
  • 清除阶段:回收被标记的对象所占用的空间。

缺点:效率低,标记清除后会产⽣⼤量不连续的碎⽚,需要预留空间给分配阶段的浮动垃圾

4.2.9.2.复制算法

为了解决标记-清除算法的效率不高的问题,产生了复制算法。它把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾收集时,遍历当前使用的区域,把存活对象复制到另外一个区域中,最后将当前使用的区域的可回收的对象进行回收。
优点:实现简单,内存效率高,不易产生碎片
缺点:内存压缩了一半,倘若存活对象多,Copying 算法的效率会大大降低

4.2.9.3.标记整理

在新生代中可以使用复制算法,但是在老年代就不能选择复制算法了,因为老年代的对象存活率会较高,这样会有较多的复制操作,导致效率变低。标记-清除算法可以应用在老年代中,但是它效率不高,在内存回收后容易产生大量内存碎片。因此就出现了一种标记-整理算法(Mark-Compact)算法,与标记-整理算法不同的是,在标记可回收的对象后将所有存活的对象压缩到内存的一端,使他们紧凑的排列在一起,然后对端边界以外的内存进行回收。回收后,已用和未用的内存都各自一边。
优点:解决了标记-清理算法存在的内存碎片问题。
缺点:仍需要进行局部对象移动,一定程度上降低了效率。

4.2.9.4.分代收集

根据各个年代的特点选择合适的垃圾收集算法。
新生代采用复制算法,新生代每次垃圾回收都要回收大部分对象,存活对象较少,即要复制的操作比较少,一般将新生代划分为一块较大的 Eden 空间和两个较小的 Survivor 空间(From Space, To Space),每次使用Eden 空间和其中的一块 Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另一块 Survivor 空间中。
老年代的对象存活⼏率是⽐较⾼的,⽽且没有额外的空间对它进⾏分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进⾏垃圾收集。
Safepoint 当发生 GC 时,用户线程必须全部停下来,才可以进行垃圾回收,这个状态我们可以认为 JVM 是安全的(safe),整个堆的状态是稳定的。如果在 GC 前,有线程迟迟进入不了 safepoint,那么整个 JVM 都在等待这个阻塞的线程,造成了整体 GC 的时间变长
image.png

4.2.10.MinorGC、MajorGC、FullGC

  • MinorGC 在年轻代空间不足的时候发生,
  • MajorGC 指的是老年代的 GC,出现 MajorGC 一般经常伴有 MinorGC。
  • FullGC
    • 1、当老年代无法再分配内存的时候;
    • 2、元空间不足的时候;
    • 3、显示调用 System.gc 的时候。另外,像 CMS 一类的垃圾回收器,在 MinorGC 出现 promotion failure 的时候也会发生 FullGC。

对象优先在 Eden 区分配
大多数情况下,对象在新生代 Eden 区分配,当 Eden 区空间不够时,发起 Minor GC。
大对象直接进入老年代
大对象是指需要连续内存空间的对象,比如很长的字符串以及数组。老年代直接分配的目的是避免在 Eden 区和 Survivor 区之间出现大量内存复制。
长期存活的对象进入老年代
虚拟机给每个对象定义了年龄计数器,对象在 Eden 区出生之后,如果经过一次 Minor GC 之后,将进入 Survivor 区,同时对象年龄变为 1,增加到一定阈值时则进入老年代(阈值默认为 15)
动态对象年龄判定
为了能更好地适应不同程序的内存状况,虚拟机并不总是要求对象的年龄必须达到阈值才能进入老年代。如果在 Survivor 区中相同年龄的所有对象的空间总和大于 Survivor 区空间的一半,则年龄大于或等于该年龄的对象直接进入老年代。
空间分配担保
在发生 Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象的空间总和,如果这个条件成立,那么 Minor GC 可以确保是安全的。如果不成立则进行 Full GC。