学习笔记 1.高性能编程 3.1.2 垃圾回收机制

自动垃圾收集

自动垃圾收集是查看堆内存,识别正在使用那些对象以及哪些对象未被删除以及删除未使用对象的过程。

使用中的对象或者引用的对象意味着程序的某些部分仍然维护指向该对象的指针。程序的任何部分都不再引用未使用的对象或未引用的对象,因此可以回收未引用对象的内存。

像c这样的编程语言,分配和释放内存是一个手动过程。在java中解除分配内存的过程由垃圾收集器自动处理。

确定哪些对象没有使用,确定这个对象什么时候被删除,什么时候删除,这个过程需要先确认这个内存需要被回收

第一步是标记。这是垃圾收集器识别哪些内存正在使用而哪些不在使用的地方

学习笔记 1.高性能编程 3.1.2 垃圾回收机制_第1张图片

不同类型内存的判断方式:

  1. 对象回收--引用计数
  2. 对象回收--可达性分析
  3. 方法区回收

1.引用计数

在netty中也有引用计数,原理就是在这个对象中添加一个计数器,当有地方用到这个对象的时候,计数器就会加一,用完以后就会减一。但是在java中不会使用,因为在java中会有循环引用的问题,相互引用一个简单的例子就是,obja和objb两个对象之间相互引用,虽然在别的地方没有对这两个对象的引用,但是因为相互引用的关系,导致计数器不会归零,无法进行垃圾回收。

2.可达性分析算法

简单来说,将对象及其引用关系看作一个图,选定活动的对象作为GC Roots;然后跟踪引用链条,如果一个对象和GC Roots之间不可达,也就是不存在引用,那么即可认为是可回收对象,GC Root比如说:创建线程后,线程中所用到的对象或者线程栈中所用到的本地变量学习笔记 1.高性能编程 3.1.2 垃圾回收机制_第2张图片

垃圾回收的时候首先找出来所有的可以被作为GC Root的对象,全部找出来以后沿着GC Root往下找,将所有正在使用的标记出来,没有被标记为正在使用的对象则是要被回收,而在可达性分析中的引用在java中也分为很多种,如下所示:

  1. 强引用(StrongReference):最常见的普通对象引用,只要还有一个强引指向一个对象,就不会被回收。例如:Object a = new Object();
  2. 软引用(SoftReference):JVM认为内存不足时,才会去尝试回收软引用指向的对象。(缓存场景)
  3. 弱引用(WeakReference):虽然是引用,但是随时可能会被回收掉。
  4. 虚引用(PhantomReference):不能通过它访问对象。提供了对象被finalize以后,执行指定逻辑的机制(堆外内存的回收cleaner)

可达性级别:

  1. 强可达(Strongly Reachable):一个对象可以有一个或者多个线程可以不通过任何引用访问到的情况。
  2. 软可达(Softly Reachable):就是我们只能通过软引用才能访问到对象的状态。
  3. 弱可达(Weakly Reachable):只能通过弱引用访问时的状态。当弱引用被清除时,就符合销毁条件。
  4. 幻象可达(Phantom Reachable):不存在其他引用,并且finalize过了,只有幻象引用指向这个对象。
  5. 不可达(unreachable):意味着对象可以被清除了。

上面是对对象进行标记,分出可回收的对象和不可回收的对象。然后就应该对对象进行回收,回收算法如下:

  1. 标记-清除(Mark-Sweep)算法:首先标识出所有要回收的对象,然后进行清除。标记、清除过程效率有限,有内存碎片化问题,不适合特别大的堆;收集算法基本基于标记-清除的思路进行改进。
  2. 复制(Copying)算法:划分两块同等大小的区域,收集时将活着的对象复制到另一块区域。拷贝过程中将对象顺序放置,就可以避免内存碎片化。复制+预留内存有一定浪费。
  3. 标记-整理(Mark-Compact):类似于标记-清除,单位避免内存碎片化,它会在清理过程中将对象移动,以确保移动后的对象占用连续的内存空间。

上面三种算法有各自的优缺点,但是都无法满足我们对垃圾回收的要求,所以看看JVM是如何进行垃圾回收的。

分代收集

既然说垃圾收集算法有好有坏,根据对象的存活周期,将内存划分为几个区域,不同的区域采用合适的垃圾收集算法。新对象会被分配到Eden,如果超过-XX:+PretenureSizeThreshold:设置大对象直接进入老年代的阀值

内存大小:新生代:老年代 ->1:2  ;  新生代中 S0:S1:Eden ->  1:1:8

在新生代中采用的是复制算法,新创建的对象首先进入Eden区,当Eden区满了以后会进行一次垃圾回收,利用复制算法,将存活的对象复制到S0,并记录一次垃圾回收的次数,剩余的全部清除,当Eden区再次进行垃圾回收的时候,Eden区和S0的对象将会被标记,将Eden和S0区中存活的对象复制到S1中,同时增加垃圾回收的次数,然后清除未存活的对象,当参与清除的存活的对象达到一定次数的时候会将其移动到老年代中,同时假如新生代中内存不足时,也会将新创建的对象存入老年代中,或者大对象创建时直接进入老年代,新生代中之所以使用复制算法,是因为在新生代中的对象一般存活时间都比较短,可以快速的清理。

老年代采用的是标记整理算法。

学习笔记 1.高性能编程 3.1.2 垃圾回收机制_第3张图片

3.方法区回收

很多人习惯称方法区为永久代(hotspot以永久代来实现方法区)

java虚拟机规范中提到:可以不要求虚拟机在方法区实现垃圾收集。而且在方法区的垃圾回收“性价比”一般比较低。在堆中,尤其是在年轻的,一次垃圾回收一般可以回收70-95%的空间

永久代的垃圾回收分为两部分内容:废弃常量、无用的类     
回收常量与java堆的对象回收非常相似。“没有地方引用”
类回收需要满足下面三个条件
               该类的所有实例均已回收,也就是该java堆中不存在该类的实例对象
               加载该类的classLoader已被回收
               该类对应的java.lang.class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
          虚拟机可以对满足以上三个条件的类进行回收。而且这里说的仅仅是可以,而不是跟对象回收一样,不使用了就必然回收。
          HotSpot虚拟机提供了-Xnoclassgc参数进行控制
          还可以使用以下参数查看类的加载和卸载信息
          -XX:+TraceClassLoading :跟踪类加载的信息(诊断内存泄露很有用)
          -XX:+TraceClassUnloading :跟踪类卸载的信息(诊断内存泄露很有用)

当这些策略已经准备就绪了就需要一个执行者了----垃圾收集器

在java中有很多类型的垃圾收集器

  1. 串行收集器---Serial GC -XX:+UseSerialGC                                                                                                                        
    单个线程来执行所有垃圾收集工作,适合单处理器机器。客户端(Client)模式下JVM的默认选项
  2. 串行收集器---Serial Old -XX:+UseSerialOldGC                                                                                                                         可以在老年代使用,它采用标记整理算法(Mark-Compact),区别于新生代的复制算法。串行收集器在工作的时候会停止线程,不适合用于服务器上。学习笔记 1.高性能编程 3.1.2 垃圾回收机制_第4张图片
  3. 并行收集器---Parallel GC -XX:+UseParallelGC   
  4. 并行收集器---Parallel Old GC -XX:+UseParallelOldGC                                                                                                             server模式JVM默认的GC选择,整体算法和Serial比较相似,区别是新生代和老年代GC都是并行进行的。可以设置GC时间和吞吐量等值,可以自动进行适应性调整Eden,Survivor大小和MaxTenuringThreshold的值。                                          也成为了吞吐量优先的GC:吞吐量=用户代码运行时间/(用户代码运行时间+GC时间)                                                                        -XX:ParallelGCThreads:设置用于垃圾回收的线程数。通常情况下可以和CPU数量相等。                                                          -XX:MaxGCPauseMills: 设置最大垃圾收集停顿时间。它的值是一个0-100之间的整数。                                                              -XX:GCTimeRatio:设置吞吐量大小,它的值是一个0-100之间的整数。                                                                                            -XX:+UseAdptiveSizePolicy:打开自适应GC策略。已达到在堆大小、吞吐量和停顿时间之间的平衡点。 
  5. 并发收集器---CMS(Concurrent Mark Sweep)GC -XX:UseConMarkSweepGC                                                                             专用老年代,基于标记-清除算法(Mark-Sweep),设计目标是尽量减少停顿时间。采用的标记-清除算法,存在内存碎片化问题。长时间运行等情况下发生full GC,导致恶略的停顿。CMS会占用更多的CPU资源,并和用户线程争抢。减少了停顿时间,这一点对于互联网web等对时间敏感的系统非常重要,一直到今天,任然有很多系统使用CMSGC。学习笔记 1.高性能编程 3.1.2 垃圾回收机制_第5张图片
  6. 并行收集器---ParNew GC -XX:+UseParNewGC                                                                                                                       新生代GC实现,它实际是Serial GC的多线程版本。可以控制线程数量,参数:-XX:ParallelGCThreads;最常见的应用场景是配合老年代的CMS GC工作。参数:-XX:+UseConcMarkSweepGC学习笔记 1.高性能编程 3.1.2 垃圾回收机制_第6张图片
  7. 并发收集器--- G1 -XX:UseG1GC                                                                                                                                               针对大堆内存设计的收集器,兼顾吞吐量和停顿时间,JDK1.9后为默认选项,目标是替代CMS GC;G1将堆分为固定大小的区域,Region之间是复制算法,但是整体上是可以看做是标记-整理(Mark-Sweep)算法,可以有效地避免内存碎片。红色新生代(Eden和Surivor),淡蓝色老年代。找不到大内存时执行FullGC。下图右侧圆圈代表停顿,蓝色是新生代收集,黄色是标记,红色是混合收集学习笔记 1.高性能编程 3.1.2 垃圾回收机制_第7张图片

上面介绍了串行,并行,并发三大类垃圾收集器,通常会有以下组合,我们常用的组合有ParNew+CMS、Parallel Scavenge+Parallel、G1,通常情况下采用JVM默认的就可以了。学习笔记 1.高性能编程 3.1.2 垃圾回收机制_第8张图片

 

 

 

 

你可能感兴趣的:(网易微课堂)