Java垃圾回收与算法
作者:Vic ,分类:Java 发布于 2020-04-15 20:34:21

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

  • 方法区 , 这部分的内存分配和回收是动态的 , 也是线程之间共享区域.

2.如何确定为垃圾?

引用计数法

    • 其他教科书讲判断对象和引用是否有关联 , 使用引用计数法,要先给每一个对象中添加一个计数器,一旦有地方引用了此对象,则该对象的计数器加1,如果引用失效了,则计数器减1。这样当计数器为0时,就代表此对象没有被任何地方引用。
      • 优点 :判断效率高 , 不打断程序运行环境
      • 缺点 :其相互循环引用情况下 , 对象计数始终不为0 , 无法被回收

可达性分析法

    • Jvm使用的是可达性分析法又叫(跟搜索法) , 从一个节点GC root开始 , 搜寻其引用的节点 , 找到节点 , 在继续搜寻其引用的节点 。剩余的节点则被认为是没有被引用到的节点将会被标记为是可回收的对象。要真正宣告一个对象死亡,至少要经历两次标记过程 , Java语言中,可作为GC Roots的对象包括下面几种:
      • 1.虚拟机栈中引用的对象(栈帧中的本地变量表)
      • 2.方法区中静态属性引用的对象
      • 3.方法区中常量引用的对象
      • 4.本地方法栈中JNI(Native方法)引用的对象。
      • 优点 : 解决了引用计数法中循环引用的缺点

3.垃圾回收算法

1.标记–清除算法(Mark-Sweep)

    • 标记:遍历内存区域,对需要回收的对象打上标记。
    • 清除:再次遍历内存,对已经标记过的内存进行回收。
      • 优点 : 时间问题 , 遍历了两次内存空间 , 存活对象较多的情况下比较高效, 适用于年老代
      • 缺点 : 空间问题, 容易产生大量内存碎片,当再需要一块比较大的内存时,无法找到一块满足要求的,因而不得不再次出发GC。

2.复制算法(copying)

    • 为了解决 标记–清除算法 算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉 .
      • 优点 : 相对于标记–清理算法解决了内存的碎片化问题 , 效率更高, 高效性是建立在存活对象少、垃圾对象多的前提下 , 这种情况在新生代经常发生
      • 缺点 : 内存利用率不高,每次只能使用一半内存 , 但是在老年代更常见的情况是大部分对象都是存活对象。如果依然使用复制算法,由于存活的对象较多,复制的成本也将很高

3.标记–整理算法(Mark-Compact)

    • 因为前面的复制算法当对象的存活率比较高时,这样一直复制过来,复制过去,没啥意义,且浪费时间。所以针对老年代提出了“标记整理”算法 , 结合了以上两个算法,为了避免缺陷而提出。标记阶段和 标记–清除 算法相同,标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象
    • 标记:对需要回收的进行标记
    • 整理:让存活的对象,向内存的一端移动,然后直接清理掉没有用的内存。

4.分代收集算法

    • 分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(YoungGeneration)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。
    • 新生代复制算法
      • 目前大部分 JVM 的 GC 对于新生代都采取 Copying 算法,因为新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比较少,但通常并不是按照 1:1 来划分新生代。一般将新生代划分为一块较大的 Eden 空间和两个较小的 Survivor 空间(From Space, To Space),每次使用Eden 空间和其中的一块 Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另一块 Survivor 空间中。
    • 老年代标记整理算法
      • 因为对象存活率高、没有额外空间对它进行分配担保, 就必须采用“标记—清理”或“标记—整理”算法来进行回收, 不必进行内存复制, 且直接腾出空闲内存.
      • 1. JAVA 虚拟机提到过的处于方法区的永生代(Permanet Generation),它用来存储 class 类,常量,方法描述等。对永生代的回收主要包括废弃常量和无用的类。
      • 2. 对象的内存分配主要在新生代的 Eden Space 和 Survivor Space 的 From Space(Survivor 目前存放对象的那一块),少数情况会直接分配到老生代。
      • 3. 当新生代的 Eden Space 和 From Space 空间不足时就会发生一次 GC,进行 GC 后,Eden Space 和 From Space 区的存活对象会被挪到 To Space,然后将 Eden Space 和 From Space 进行清理。
      • 4. 如果 To Space 无法足够存储某个对象,则将这个对象存储到老生代。
      • 5. 在进行 GC 后,使用的便是 Eden Space 和 To Space 了,如此反复循环。
      • 6. 当对象在 Survivor 区躲过一次 GC 后,其年龄就会+1。默认情况下年龄到达 15 的对象会被移到老生代中。

4.GC垃圾收集器

垃圾收集器概述

    • 虚拟机中的7种垃圾收集器
      • 适用于新生代的收集器 :Serial ,ParNew , Parallel Scavenge
      • 适用于老年代的收集器 : Serial Old , Parallel Old ,CMS
      • 适用于整堆的收集器 : G1

收集器

垃圾收集器 线程方式 收集算法 GC停顿 优点 缺点 使用 其他
Serial 单线程 复制算法 适用于Client 模式下的新生代, 简单高效(相比于其他单线程) 会暂停用户线程导致GC停顿 -XX:+UseSerialGC -
ParNew 多线程 复制算法 适用于Server 模式下, 支持多线程和指定线程数量控制 , 可以和CMS配合使用 会暂停用户线程导致GC停顿 , 单CPU下 不会别Serial好 -XX:+UseParNewGC -XX:ParallelGCThreads设置收集器线程数量, 默认开启和cpu数目相同数量的线程
Parallel Scavenge 多线程 复制算法 可控制的吞吐量和自适应调节策略 会暂停用户线程导致GC停顿 -XX:UseParallelGC -XX:MaxGCPauseMillis设置收集停顿时间,大于0的毫秒数 , 设置的越小 , 停顿时间越短 , 吞吐量下降, gc发生频繁 . -XX:GCTimeRatio设置垃圾收集时间占总时间的比率 , 值为0<n<100的整数 , 吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
Serial Old 单线程 标记-整理算法 适用于Client 模式下单线程老年代最优 会暂停用户线程导致GC停顿 年轻代使用Parallel Scavenge, Linux下1.6,1.7,1.8默认开启老年代SerialOld垃圾收集器 CMS收集器的后备预案
Parallel Old 多线程 标记-整理算法 适用于Server 模式下, 支持多线程和指定线程数量控制 会暂停用户线程导致GC停顿 -XX:+UseParallelOldGC DK1.6才开始提供 , 在年老代同样提供吞吐量优先的垃圾收集器 , 可以和CMS配合使用
CMS (Concurrent Mark Sweep) 多线程 标记-清除 基本上垃圾收集线程与用户线程同时工作 获取最短垃圾回收停顿时间 , 并发收集、低停顿 需要更多的内存,产生大量内存碎片 -XX:+UseConcMarkSweepGC 收集运作过程: 1.初始标记(期间停顿) 2.并发标记 3.重新标记(期间停顿) 4. 并发清除
G1 (Garbage first ) 多线程 标记-整理 低停顿 不产生内存碎片, 分代收集 独立管理整个GC堆(新生代和老年代) 需要更多的内存,产生大量内存碎片 -XX:+UseG1GC收集运作过程:把堆内存划分为大小固定的几个独立区域 , 步骤:1.初始标记(期间停顿) 2.并发标记 3.最终标记(期间停顿) 4. 筛选回收(回收时用"复制"算法,从一个或多个Region复制存活对象到堆上的另一个空的Region, 在此过程中压缩和释放内存) . 参数: -XX:InitiatingHeapOccupancyPercent设置当整个Java堆的占用率达到参数值时,开始并发标记阶段;默认为45 . -XX:MaxGCPauseMillis设置暂停时间目标,默认值为200毫秒 . -XX:G1HeapRegionSize设置每个Region大小,范围1MB到32MB