JVM5-垃圾接纳

[复制链接]
发表于 2026-1-26 06:48:02 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
目次
主动垃圾接纳
方法区的接纳
堆接纳
怎样判断堆上的对象可以接纳
引用计数法
可达性分析法
五种对象引用
强引用
软引用
弱引用
虚引用
闭幕器引用
垃圾接纳算法
垃圾接纳算法的汗青和分类
垃圾接纳算法的评价标准
四种垃圾接纳算法
标记打扫算法
复制算法
标记整理算法
分代垃圾接纳(分代GC)算法
垃圾接纳器
年轻代-Serial垃圾接纳器
老年代-SerialOld垃圾接纳器
年轻代-ParNew垃圾接纳器
老年代-CMS(Concurrent Mark Sweep)垃圾接纳器
年轻代-Parallel Scavenge垃圾接纳器
老年代-Parallel Old垃圾接纳器
G1垃圾接纳器
概述
G1垃圾接纳的方式
年轻代接纳
肴杂接纳
总结

主动垃圾接纳

在C/C++这类没有主动垃圾接纳机制的语言中,一个对象假如不再使用,须要手动开释,否则就会出现内存走漏,称这种开释对象的过程为垃圾接纳,而须要步伐员编写代码举行接纳的方式为手动接纳
内存走漏指的是不再使用的对象在体系中未被接纳,内存走漏的积累大概会导致内存溢出


Java中为了简化对象的开释,引入了主动的垃圾接纳(Garbage Collection简称GC)机制,通过垃圾接纳器来对不再使用的对象完成主动的接纳,垃圾接纳器重要负责对堆上的内存举行接纳,其他很多今世语言好比C#、Python、Go都拥有自己的垃圾接纳器
垃圾接纳器假如发现某个对象不再使用,就可以接纳该对象
垃圾接纳的对比:
主动垃圾接纳:主动根据对象是否使用由捏造机来接纳对象


  • 优点:低沉步伐员实现难度,低沉对象接纳bug的大概性
  • 缺点:步伐员无法控制内存接纳的及时性
手动垃圾接纳:由步伐员编程实现对象的删除


  • 优点:接纳及时性高,由步伐员把控接纳的机会
  • 缺点:编写不当容易出现悬空指针、重复开释、内存走漏等题目
方法区的接纳

线程不共享的部门,都是陪伴着线程的创建而创建,线程的烧毁而烧毁,而方法的栈帧在实行完方法之后就会主动弹出栈并开释掉对应的内存,以是这一部门不须要垃圾接纳器负责接纳

方法区中能接纳的内容重要就是不再使用的类
判断一个类可以被卸载,须要同时满足下面三个条件:

  • 此类全部实例对象都已经被接纳,在堆中不存在任何该类的实例对象以及子类对象
  • 加载该类的类加载器已经被接纳
  • 该类对应的java.lang.Class对象没有在任何地方被引用
  1. import java.net.URL;
  2. import java.net.URLClassLoader;
  3. import java.util.ArrayList;
  4. /**
  5. * 类的卸载
  6. */
  7. public class ClassUnload {
  8.     public static void main(String[] args) throws InterruptedException {
  9.         try {
  10.             ArrayList<Class<?>> classes = new ArrayList<>();
  11.             ArrayList<URLClassLoader> loaders = new ArrayList<>();
  12.             ArrayList<Object> objs = new ArrayList<>();
  13.             while (true) {
  14.                 URLClassLoader loader = new URLClassLoader(
  15.                         new URL[]{new URL("file:D:\\lib\")});
  16.                 Class<?> clazz = loader.loadClass("com.itheima.my.A");
  17.                 Object o = clazz.newInstance();
  18. //                objs.add(o);
  19. //                classes.add(clazz);
  20. //                 loaders.add(loader);
  21.                  System.gc();
  22.             }
  23.         } catch (Exception e) {
  24.             e.printStackTrace();
  25.         }
  26.     }
  27. }
复制代码
添加这两个捏造机参数举行测试:
  1. -XX:+TraceClassLoading -XX:+TraceClassUnloading
复制代码
假如表明掉代码中三句add调用,就可以同时满足3个条件,但是须要手动调用System.gc()方法,让垃圾接纳器举行接纳 
假如须要手动触发垃圾接纳,可以调用System.gc()方法
语法: System.gc()
注意事项:调用System.gc()方法并不愿定会立即接纳垃圾,仅仅是向Java捏造机发送一个垃圾接纳的哀求,具体是否须要实行垃圾接纳Java捏造时机自行判断

类卸载的应用场景:
开发中此类场景一样平常很少出现,重要在如 OSGi、JSP 的热摆设等应用场景中
每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器,重新创建类加载器,重新加载jsp文件
堆接纳

怎样判断堆上的对象可以接纳

Java中的对象是否能被接纳,是根据对象是否被引用来决定的,假如对象被引用了,阐明该对象还在使用,不允许被接纳
判断对象是否可以接纳,重要有两种方式:


  • 引用计数法
  • 可达性分析法
引用计数法

引用计数法会为每个对象维护一个引用计数器,当对象被引用时加1,取消引用时减1
引用计数法的优点是实现简单,C++中的智能指针就采取了引用计数法,但它也存在如下缺点:

  • 每次引用和取消引用都须要维护计数器,对体系性能会有肯定的影响
  • 存在循环引用题目,所谓循环引用就是当A引用B,B同时引用A时会出现对象无法接纳的题目
假如想要检察垃圾接纳的信息,可以使用-verbose:gc参数
语法: -verbose:gc

可达性分析法

Java使用的是可达性分析算法来判断对象是否可以被接纳
可达性分析将对象分为两类:垃圾接纳的根对象(GC Root)宁静常对象,对象与对象之间存在引用关系
下图中A到B再到C和D,形成了一个引用链,可达性分析算法指的是假如从GC Root对象到某个对象是可达的(雷同树的遍历),该对象就不可被接纳,假如去掉A和B之间的引用,则B、C、D都可被接纳

GC Root对象:


  • 线程Thread对象,引用线程栈帧中的方法参数、局部变量等
  • 体系类加载器加载的java.lang.Class对象,引用类中的静态变量
  • 监督器对象,用来生存同步锁synchronized关键字持有的对象
  • 当地方法调用时使用的全局对象
通过arthas和eclipse Memory Analyzer (MAT) 工具可以检察GC Root,MAT工具是eclipse推出的Java堆内存检测工具。具体操纵步调如下:

  • 使用arthas的heapdump下令将堆内存快照生存到当地磁盘中
  • 使用MAT工具打开堆内存快照文件
  • 选择GC Roots功能检察全部的GC Root

五种对象引用



  • 强引用
  • 软引用
  • 弱引用
  • 虚引用
  • 闭幕器引用
强引用

可达性算法中形貌的对象引用,一样平常指的是强引用,即GC Root对象对平常对象有引用关系,只要这层关系存在,平常对象就不会被接纳
软引用

软引用相对于强引用是一种比力弱的引用关系,假如一个对象只有软引用关联到它,当步伐内存不敷时,就会将软引用中的数据举行接纳
在JDK 1.2版之后提供了SoftReference类来实现软引用,软引用常用于缓存中

注意:软引用对象自己也须要被强引用,否则软引用对象也会被接纳掉
软引用的使用方法
软引用的实行过程如下:

  • 将对象使用软引用包装起来,new SoftReference<对象范例>(对象)
  • 内存不敷时,捏造机实验举行垃圾接纳
  • 假如垃圾接纳仍不能办理内存不敷的题目,接纳软引用中的对象
  • 假如依然内存不敷,抛出OutOfMemory非常
  1. /**
  2. * 软引用案例 - 基本使用
  3. */
  4. public class SoftReferenceDemo2 {
  5.     public static void main(String[] args) throws IOException {
  6.         byte[] bytes = new byte[1024 * 1024 * 100];
  7.         SoftReference<byte[]> softReference = new SoftReference<byte[]>(bytes);
  8.         bytes = null;
  9.         System.out.println(softReference.get());
  10.         byte[] bytes2 = new byte[1024 * 1024 * 100];
  11.         System.out.println(softReference.get());
  12. //
  13. //        byte[] bytes3 = new byte[1024 * 1024 * 100];
  14. //        softReference = null;
  15. //        System.gc();
  16. //
  17. //        System.in.read();
  18.     }
  19. }
复制代码
软引用对象自己接纳:
软引用中的对象假如在内存不敷时接纳,SoftReference对象自己也须要被接纳
SoftReference提供了一套队列机制:

  • 软引用创建时,通过构造器传入引用队列
  • 在软引用中包罗的对象被接纳时,该软引用对象会被放入引用队列
  • 通过代码遍历引用队列,将SoftReference的强引用删除
  1. /**
  2. * 软引用案例 - 引用队列使用
  3. */
  4. public class SoftReferenceDemo3 {
  5.     public static void main(String[] args) throws IOException {
  6.         ArrayList<SoftReference> softReferences = new ArrayList<>();
  7.         ReferenceQueue<byte[]> queues = new ReferenceQueue<byte[]>();
  8.         for (int i = 0; i < 10; i++) {
  9.             byte[] bytes = new byte[1024 * 1024 * 100];
  10.             SoftReference studentRef = new SoftReference<byte[]>(bytes,queues);
  11.             softReferences.add(studentRef);
  12.         }
  13.         SoftReference<byte[]> ref = null;
  14.         int count = 0;
  15.         while ((ref = (SoftReference<byte[]>) queues.poll()) != null) {
  16.             count++;
  17.         }
  18.         System.out.println(count);
  19.     }
  20. }
复制代码

软引用的缓存案例:
使用软引用实现门生信息的缓存,能支持内存不敷时整理缓存

  1. import java.lang.ref.ReferenceQueue;
  2. import java.lang.ref.SoftReference;
  3. import java.util.HashMap;
  4. import java.util.Map;
  5. /**
  6. * 软引用案例 - 学生信息的缓存
  7. */
  8. public class StudentCache {
  9.     private static StudentCache cache = new StudentCache();
  10.     public static void main(String[] args) {
  11.         for (int i = 0; ; i++) {
  12.             StudentCache.getInstance().cacheStudent(new Student(i, String.valueOf(i)));
  13.         }
  14.     }
  15.     private Map<Integer, StudentRef> StudentRefs;// 用于Cache内容的存储
  16.     private ReferenceQueue<Student> q;// 垃圾Reference的队列
  17.     // 继承SoftReference,使得每一个实例都具有可识别的标识。
  18.     // 并且该标识与其在HashMap内的key相同。
  19.     private class StudentRef extends SoftReference<Student> {
  20.         private Integer _key = null;
  21.         public StudentRef(Student em, ReferenceQueue<Student> q) {
  22.             super(em, q);
  23.             _key = em.getId();
  24.         }
  25.     }
  26.     // 构建一个缓存器实例
  27.     private StudentCache() {
  28.         StudentRefs = new HashMap<Integer, StudentRef>();
  29.         q = new ReferenceQueue<Student>();
  30.     }
  31.     // 取得缓存器实例
  32.     public static StudentCache getInstance() {
  33.         return cache;
  34.     }
  35.     // 以软引用的方式对一个Student对象的实例进行引用并保存该引用
  36.     private void cacheStudent(Student em) {
  37.         cleanCache();// 清除垃圾引用
  38.         StudentRef ref = new StudentRef(em, q);
  39.         StudentRefs.put(em.getId(), ref);
  40.         System.out.println(StudentRefs.size());
  41.     }
  42.     // 依据所指定的ID号,重新获取相应Student对象的实例
  43.     public Student getStudent(Integer id) {
  44.         Student em = null;
  45.         // 缓存中是否有该Student实例的软引用,如果有,从软引用中取得。
  46.         if (StudentRefs.containsKey(id)) {
  47.             StudentRef ref = StudentRefs.get(id);
  48.             em = ref.get();
  49.         }
  50.         // 如果没有软引用,或者从软引用中得到的实例是null,重新构建一个实例,并保存对这个新建实例的软引用
  51.         if (em == null) {
  52.             em = new Student(id, String.valueOf(id));
  53.             System.out.println("Retrieve From StudentInfoCenter. ID=" + id);
  54.             this.cacheStudent(em);
  55.         }
  56.         return em;
  57.     }
  58.     // 清除那些所软引用的Student对象已经被回收的StudentRef对象
  59.     private void cleanCache() {
  60.         StudentRef ref = null;
  61.         while ((ref = (StudentRef) q.poll()) != null) {
  62.             StudentRefs.remove(ref._key);
  63.         }
  64.     }
  65. //    // 清除Cache内的全部内容
  66. //    public void clearCache() {
  67. //        cleanCache();
  68. //        StudentRefs.clear();
  69. //        //System.gc();
  70. //        //System.runFinalization();
  71. //    }
  72. }
  73. class Student {
  74.     int id;
  75.     String name;
  76.     public Student(int id, String name) {
  77.         this.id = id;
  78.         this.name = name;
  79.     }
  80.     public int getId() {
  81.         return id;
  82.     }
  83.     public void setId(int id) {
  84.         this.id = id;
  85.     }
  86.     public String getName() {
  87.         return name;
  88.     }
  89.     public void setName(String name) {
  90.         this.name = name;
  91.     }
  92. }
复制代码
弱引用

弱引用的团体机制和软引用根本同等,区别在于弱引用包罗的对象在垃圾接纳时,不管内存够不敷都会直接被接纳
在JDK 1.2版之后提供了WeakReference类来实现弱引用,弱引用重要在ThreadLocal中使用
弱引用对象自己也可以使用引用队列举行接纳 
  1. import java.io.IOException;
  2. import java.lang.ref.WeakReference;
  3. /**
  4. * 弱引用案例 - 基本使用
  5. */
  6. public class WeakReferenceDemo2 {
  7.     public static void main(String[] args) throws IOException {
  8.         byte[] bytes = new byte[1024 * 1024 * 100];
  9.         WeakReference<byte[]> weakReference = new WeakReference<byte[]>(bytes);
  10.         bytes = null;
  11.         System.out.println(weakReference.get());
  12.         System.gc();
  13.         System.out.println(weakReference.get());
  14.     }
  15. }
复制代码
虚引用

虚引用也叫幽灵引用(幻影引用),不能通过虚引用对象获取到包罗的对象
虚引用唯一的用途是当对象被垃圾接纳器接纳时可以吸收到对应的关照
Java中使用PhantomReference实现了虚引用,直接内存中为了及时知道直接内存对象不再使用,从而接纳内存,使用了虚引用来实现
闭幕器引用

闭幕器引用指的是在对象须要被接纳时,闭幕器引用会关联对象并放置在Finalizer类中的引用队列中,之后FinalizerThread线程从队列中获取对象,然后实行对象的finalize方法,在对象第二次被接纳时,该对象才真正的被接纳,在这个过程中可以在finalize方法中再将自身对象使用强引用关联上,但是不发起如许做
  1. /**
  2. * 终结器引用案例
  3. */
  4. public class FinalizeReferenceDemo {
  5.     public static FinalizeReferenceDemo reference = null;
  6.     public void alive() {
  7.         System.out.println("当前对象还存活");
  8.     }
  9.     @Override
  10.     protected void finalize() throws Throwable {
  11.         try{
  12.             System.out.println("finalize()执行了...");
  13.             //设置强引用自救
  14.             reference = this;
  15.         }finally {
  16.             super.finalize();
  17.         }
  18.     }
  19.     public static void main(String[] args) throws Throwable {
  20.         reference = new FinalizeReferenceDemo();
  21.        test();
  22.        test();
  23.     }
  24.     private static void test() throws InterruptedException {
  25.         reference = null;
  26.         //回收对象
  27.         System.gc();
  28.         //执行finalize方法的优先级比较低,休眠500ms等待一下
  29.         Thread.sleep(500);
  30.         if (reference != null) {
  31.             reference.alive();
  32.         } else {
  33.             System.out.println("对象已被回收");
  34.         }
  35.     }
  36. }
复制代码
垃圾接纳算法

Java是怎样实现垃圾接纳的呢?简单来说,垃圾接纳要做的有两件事:

  • 找到内存中存活的对象
  • 开释不再存活对象的内存,使得步伐能再次使用这部门空间
垃圾接纳算法的汗青和分类

1960年John McCarthy发布了第一个GC算法:标记-打扫算法
1963年Marvin L. Minsky发布了复制算法
本质上后续全部的垃圾接纳算法,都是在上述两种算法的底子上优化而来

垃圾接纳算法的评价标准

Java垃圾接纳过程会通过单独的GC线程来完成,但是不管使用哪一种GC算法,都会有部门阶段须要克制全部的用户线程,这个过程被称之为Stop The World,简称STW,假如STW时间过长则会影响用户的使用
判断GC算法是否良好,可以从三个方面来思量:
1.吞吐量
吞吐量指的是 CPU 用于实行用户代码的时间与 CPU 总实行时间的比值,即吞吐量 = 实行用户代码时间 /(实行用户代码时间 + GC时间)。吞吐量数值越高,垃圾接纳的服从就越高

2.最大停息时间
最大停息时间指的是全部在垃圾接纳过程中的STW时间最大值
好比如下的图中,黄色部门的STW就是最大停息时间,显而易见上面的图比下面的图拥有更少的最大停息时间,最大停息时间越短,用户使用体系时受到的影响就越短

3.堆使用服从
差异垃圾接纳算法,对堆内存的使用方式是差异的
好比标记打扫算法,可以使用完备的堆内存;而复制算法会将堆内存一分为二,每次只能使用一半内存。从堆使用服从上来说,标记打扫算法要优于复制算法

上述三种评价标准:堆使用服从、吞吐量,以及最大停息时间不可兼得
一样平常来说,堆内存越大,最大停息时间就越长;想要淘汰最大停息时间,就会低沉吞吐量
四种垃圾接纳算法

标记打扫算法

标记清晰算法的核心头脑分为两个阶段:

  • 标记阶段:将全部存活的对象举行标记。Java中使用可达性分析算法,从GC Root开始通过引用链遍历出全部存活对象
  • 打扫阶段:从内存中删除没有被标记也就优劣存活对象
第一个阶段:从GC Root对象开始扫描,将对象A、B、C在引用链上的对象标记出来

第二个阶段:将没有标记的对象整理掉,以是对象D会被整理
优点:实现简单,只须要在第一阶段给每个对象维护标记位,第二阶段删除对象即可
缺点:


  • 碎片化题目:由于内存是连续的,以是在对象被删除之后,内存中会出现很多微小的可用内存单位。假如我们须要的是一个比力大的空间,很有大概这些内存单位的巨细过小无法举行分配
  • 分配速率慢:由于内存碎片的存在,须要维护一个空闲链表,极有大概发生每次须要遍历到链表的末了才气得到符合的内存空间
复制算法

1.准备两块空间From空间和To空间,每次在对象分配阶段,只能使用此中一块空间(From空间)
对象A起首分配在From空间:

2.在垃圾接纳GC阶段,将From中存活对象复制到To空间
在垃圾接纳阶段,假如对象A存活,就将其复制到To空间,然后将From空间直接清空

3.将两块空间的From和To名字交换
接下来将两块空间的名称交换,下次依然在From空间上创建对象

优点:


  • 吞吐量高,复制算法只须要遍历一次存活对象复制到To空间即可,比标记整理算法少了一次遍历的过程,因而性能较好,但是不如标记打扫算法,由于标记打扫算法不须要举行对象的移动
  • 不会发生碎片化,复制算法在复制之后就会将对象按次序放入To空间中,以是对象以外的地域都是可用空间,不存在碎片化内存空间
缺点:内存使用服从低,每次只能让一半的内存空间来为创建对象使用
标记整理算法

标记整理算法也叫标记压缩算法,是对标记整理算法中容易产生内存碎片题目的一种办理方案
核心头脑分为两个阶段:

  • 标记阶段:将全部存活的对象举行标记。Java中使用可达性分析算法,从GC Root开始通过引用链遍历出全部存活对象
  • 整理阶段:将存活对象移动到堆的一端。整理掉存活对象的内存空间


优点:


  • 内存使用服从高,整个堆内存都可以使用,不会像复制算法只能使用半个堆内存
  • 不会发生碎片化,在整理阶段可以将对象往内存的一侧举行移动,剩下的空间都是可以分配对象的有效空间
缺点:整理阶段的服从不高,整理算法有很多种,好比Lisp2整理算法须要对整个堆中的对象搜刮3次,团体性能不佳。可以通过Two-Finger、表格算法、ImmixGC等高效的整理算法优化此阶段的性能
分代垃圾接纳(分代GC)算法

分代垃圾接纳将整个内存地域分别为年轻代和老年代:

1.分代接纳时,创建出来的对象,起首会被放入Eden伊甸园区

2.随着对象在Eden区越来越多,假如Eden区满,新创建的对象已经无法放入,就会触发年轻代的GC,称为Minor GC大概Young GC
Minor GC会把须要eden中和From须要接纳的对象接纳,把没有接纳的对象放入To区

3.接下来,S0会酿成To区,S1酿成From区。当eden区满时再往里放入对象,依然会发生Minor GC,此时会接纳eden区和S1(from)中的对象,并把eden和from区中剩余的对象放入S0
注意:每次Minor GC中都会为对象记载它的年岁,初始值为0,每次GC完加1

4.假如Minor GC后对象的年岁到达阈值(最大15,默认值和垃圾接纳器有关),对象就会被提升至老年代


当老年代中空间不敷,无法放入新的对象时,先实验minor gc假如还是不敷,就会触发Full GC,Full GC会对整个堆举行垃圾接纳
假如Full GC依然无法接纳掉老年代的对象,那么当对象继承放入老年代时,就会抛出Out Of Memory非常

为什么分代GC算法要把堆分成年轻代和老年代?起重要知道堆内存中对象的特性:


  • 体系中的大部门对象,都是创建出来之后很快就不再使用,可以被接纳
  • 老年代中会存放长期存活的对象,好比Spring的大部门bean对象,在步伐启动之后就不会被接纳了
  • 在捏造机的默认设置中,新生代巨细要远小于老年代的巨细
分代GC算法将堆分成年轻代和老年代的重要缘故原由:


  • 可以通过调解年轻代和老年代的比例来顺应差异范例的应用步伐,进步内存的使用率和性能
  • 新生代和老年代使用差异的垃圾接纳算法,新生代一样平常选择复制算法,老年代可以选择标记打扫和标记整理算法,由步伐员来选择机动度较高
  • 分代的计划中允许只接纳新生代(minor gc),假如能满足对象分配的要求就不须要对整个堆举行接纳(full gc),STW时间就会淘汰
垃圾接纳器

垃圾接纳器是垃圾接纳算法的具体实现
由于垃圾接纳器分为年轻代和老年代,除了G1之外其他垃圾接纳器必须成对组合举利用用(三种赤色实线)

年轻代-Serial垃圾接纳器

Serial是一种单线程串行接纳年轻代的垃圾接纳器
接纳年代:年轻代
接纳算法:复制算法
优点:单CPU处置惩罚器下吞吐量非常精彩
缺点:多CPU下吞吐量不如其他垃圾接纳器,堆假如偏大会让用户线程处于长时间的等候
实用场景:Java编写的客户端步伐大概硬件设置有限的场景

老年代-SerialOld垃圾接纳器

SerialOld是Serial垃圾接纳器的老年代版本,采取单线程串行接纳
接纳年代:老年代
接纳算法:标记整理算法
优点:单CPU处置惩罚器下吞吐量非常精彩
缺点:多CPU下吞吐量不如其他垃圾接纳器,堆假如偏大会让用户线程处于长时间的等候
实用场景:与Serial垃圾接纳器搭配使用,大概在CMS特别情况下使用
-XX:+UseSerialGC 新生代、老年代都使用串行接纳器

年轻代-ParNew垃圾接纳器

ParNew垃圾接纳器本质上是对Serial在多CPU下的优化,使用多线程举行垃圾接纳
接纳年代:年轻代
接纳算法:复制算法
优点:多CPU处置惩罚器下停顿时间较短
缺点:吞吐量和停顿时间不如G1,以是在JDK9之后不发起使用
实用场景:JDK8及之前的版本中,与CMS老年代垃圾接纳器搭配使用
-XX:+UseParNewGC 新生代使用ParNew接纳器, 老年代使用串行接纳器

老年代-CMS(Concurrent Mark Sweep)垃圾接纳器

CMS垃圾接纳器关注的是体系的停息时间,允许用户线程和垃圾接纳线程在某些步调中同时实行,淘汰了用户线程的等候时间
接纳年代:老年代
接纳算法:标记打扫算法
优点:体系由于垃圾接纳出现的停顿时间较短,用户体验好
缺点:


  • 内存碎片题目
  • 退化题目
  • 浮动垃圾题目
实用场景:大型的互联网体系中用户哀求数据量大、频率高的场景,好比订单接口、商品接口等
参数:XX:+UseConcMarkSweepGC

CMS实行步调:

  • 初始标记:用极短的时间标记出GC Roots能直接关联到的对象
  • 并发标记:标记全部的对象,用户线程不须要停息
  • 重新标记:由于并发标记阶段有些对象会发生了厘革,存在错标、漏标等情况,须要重新标记
  • 并发整理:整理殒命的对象,用户线程不须要停息

缺点:


  • CMS使用了标记-打扫算法,在垃圾网络竣事之后会出现大量的内存碎片,CMS会在Full GC时举行碎片的整理。如许会导致用户线程停息,可以使用-XX:CMSFullGCsBeforeCompaction=N 参数(默认0)调解N次Full GC之后再整理
  • 无法处置惩罚在并发整理过程中产生的“浮动垃圾”,不能做到完全的垃圾接纳
  • 假如老年代内存不敷无法分配对象,CMS就会退化成SerialOld单线程接纳老年代
年轻代-Parallel Scavenge垃圾接纳器

Parallel Scavenge是JDK8默认的年轻代垃圾接纳器,多线程并行接纳,关注的是体系的吞吐量。具备主动调解堆内存巨细的特点
接纳年代:年轻代
接纳算法:复制算法
优点:吞吐量高,而且手动可控。为了进步吞吐量,捏造时机动态调解堆的参数
缺点:不能包管单次的停顿时间
实用场景:背景使命,不须要与用户交互,而且容易产生大量的对象。好比:大数据的处置惩罚,大文件导出

常用参数:
Parallel Scavenge允许手动设置最大停息时间和吞吐量。Oracle官方发起在使用这个组适时,不要设置堆内存的最大值,垃圾接纳器会根据最大停息时间和吞吐量主动调解内存巨细


  • 最大停息时间,-XX:MaxGCPauseMillis=n 设置每次垃圾接纳时的最大停顿毫秒数
  • 吞吐量,-XX:GCTimeRatio=n 设置吞吐量为n(用户线程实行时间 = n/n + 1)
  • 主动调解内存巨细,-XX:+UseAdaptiveSizePolicy设置可以让垃圾接纳器根据吞吐量和最大停顿的毫秒数主动调解内存巨细
老年代-Parallel Old垃圾接纳器

Parallel Old是为Parallel Scavenge网络器计划的老年代版本,使用多线程并发网络
接纳年代:老年代
接纳算法:标记整理算法
优点:并发网络,在多核CPU下服从较高
缺点:停息时间会比力长
实用场景:与Parallel Scavenge配套使用
参数:-XX:+UseParallelGC 或-XX:+UseParallelOldGC,可以使用Parallel Scavenge + Parallel Old这种组合

G1垃圾接纳器

概述

JDK9之后默认的垃圾接纳器是G1(Garbage First)垃圾接纳器
Parallel Scavenge关注吞吐量,允许用户设置最大停息时间 ,但是会淘汰年轻代可用空间的巨细;CMS关注停息时间,但是吞吐量方面会降落
而G1计划目的就是将上述两种垃圾接纳器的优点融合:

  • 支持巨大的堆空间接纳,并有较高的吞吐量
  • 支持多CPU并行垃圾接纳
  • 允许用户设置最大停息时间
JDK9之后剧烈发起使用G1垃圾接纳器
G1出现之前的垃圾接纳器,年轻代和老年代一样平常是连续的:

G1的整个堆会被分别成多个巨细相称的地域,称之为区Region,地域不要求是连续的
分为Eden、Survivor、Old区
Region的巨细通过堆空间巨细/2048盘算得到,也可以通过参数-XX:G1HeapRegionSize=32m指定(此中32m指定region巨细为32M),Region size必须是2的指数幂,取值范围从1M到32M

接纳年代:年轻代+老年代
接纳算法:复制算法
优点:


  • 对比力大的堆,如高出6G的堆接纳时,耽误可控
  • 不会产生内存碎片
  • 并发标记的SATB算法服从高
缺点:JDK8之前还不敷成熟
实用场景:JDK8最新版本、JDK9之后发起默认使用
参数1:-XX:+UseG1GC 打开G1的开关,JDK9之后默认不须要打开
参数2:-XX:MaxGCPauseMillis=毫秒值 最大停息的时间
G1垃圾接纳的方式

G1垃圾接纳有两种方式:

  • 年轻代接纳(Young GC)
  • 肴杂接纳(Mixed GC)
年轻代接纳

年轻代接纳(Young GC),接纳Eden区和Survivor区中不消的对象,会导致STW
G1中可以通过参数-XX:MaxGCPauseMillis=n(默认200)设置每次垃圾接纳时的最大停息时间毫秒数,G1垃圾接纳器会尽大概地包管停息时间
1.新创建的对象会存放在Eden区。当G1判断年轻代区不敷(max默认60%),无法分配对象时须要接纳时会实行Young GC
2.标记出Eden和Survivor地域中的存活对象
3.根据设置的最大停息时间选择某些地域将存活对象复制到一个新的Survivor区中(年岁+1),清空这些地域


G1在举行Young GC的过程中会去记载每次垃圾接纳时每个Eden区和Survivor区的均匀耗时,以作为下次接纳时的参考依据,如许就可以根据设置的最大停息时间盘算出本次接纳时最多能接纳多少个Region地域了
好比 -XX:MaxGCPauseMillis=n(默认200),每个Region接纳耗时40ms,那么这次接纳最多只能接纳4个Region
4.后续Young GC时与之前雷同,只不外Survivor区中存活对象会被搬运到另一个Survivor区
5.当某个存活对象的年岁到达阈值(默认15),将被放入老年代

6.部门对象假如巨细高出Region的一半,会直接放入老年代,这类老年代被称为Humongous区。好比堆内存是4G,每个Region是2M,只要一个大对象高出了1M就被放入Humongous区,假如对象过大会高出多个Region

7.多次接纳之后,会出现很多Old老年代区,此时总堆占据率到达阈值时
(-XX:InitiatingHeapOccupancyPercent默认45%)会触发肴杂接纳MixedGC,接纳全部年轻代和部门老年代的对象以及大对象区。采取复制算法来完成

肴杂接纳

肴杂接纳分为:初始标记(initial mark)、并发标记(concurrent mark)、终极标记(remark大概Finalize Marking)、并发整理(cleanup)

G1对老年代的整理会选择存活度最低的地域来举行接纳,如允许以包管接纳服从最高,这也是G1(Garbage first)名称的由来,末了整理阶段使用复制算法,不会产生内存碎片

注意:假如整理过程中发现没有富足的空Region存放转移的对象,会出现Full GC。单线程实行标记整理算法,此时会导致用户线程的停息。以是只管包管应该用的堆内存有肯定多余的空间
总结

垃圾接纳器的组合关系固然很多,但是针对几个特定的版本,比力好的组合选择如下:
JDK8及之前:


  • ParNew + CMS(关注停息时间)
  • Parallel Scavenge + Parallel Old (关注吞吐量)
  • G1(JDK8之前不发起,较大堆而且关注停息时间)
JDK9之后:G1(默认)
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表