深入理解Java虚拟机——Java垃圾回收器

深入理解Java虚拟机——Java内存区域——史上最烂的图文并茂结合
深入理解Java虚拟机——Java垃圾回收器——史上最烂的图文并茂结合

1:垃圾回收机制概述

  • 垃圾回收机制不定时,向堆内存清理不可达对象
    误区:不可达的对象并不会马上就会被直接回收,而是至少要经过两次标记的过程第一次被标记过的对象,会检查该对象是否重写了finalize() 方法。如果重写了该方法,则将其放入一个F-Query队列中,否则,直接将对象加入“即将回收留集合。在第二次标记之前,F-Query队列中的所有对象会逐个执行finalize() 方法,但是不保证该队列中所有对象的finalize() 方法都能被执行,这是因为JVM创建一个低优先级的线程去运行此队列中的方法,很可能在没有遍历完之前,就已经被剥夺了运行的权利。

  • 那么运行finalize()方法的意义何在呢?
    这是对象避免自己被清理的最后手段,如果在执行finalize() 方法的过程中 , 使得此对象重新与GG Roots引用链相连,则会在第二次标记过程中将此对象从F-Query队列中清除,避免在这次回收中被清除,恢复成了一个“正常”的对象。 但显然这种好事不能无限的发生,对于曾经执行过一次finalize()的对象来说,之后如果再被标记,则不会再执行finalize()方法,只能等待被清除的命运。 之后,GC将对F-Queue中的对象进行第二次小规模的标记,将队列中重新与GC Roots引用链恢复连接的对象清除出“即将回收”集合。所有此集合中的内容将被回收。

我们举一个例子来简单说明上述的意思

public class DustbinSave {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//初始化堆
		DustbinSave dustbinSave=new DustbinSave();
		//dustbinSave=null;
        dustbinSave=null;

		System.gc();//手动回收垃圾

	}

	@Override
	protected void finalize() throws Throwable{
		
		//gc回收垃圾之前调用
		System.out.println("垃圾回收机制之前。..调用");
	}
}

在这里插入图片描述

2:如何判断对象为垃圾对象?

有了垃圾回收机制我们来了解下如何判断对象为垃圾对象

2.1 引用计数法

在对象值添加一个引用计数器,当有地方引用这个对象的时候,引用计数器的值就+1,当引用失效的时候,计数器的值就-1,任何时刻计数器都为0的对象就是不再被使用的,垃圾收集器将回收该对象使用的内存。
在这里插入图片描述

我们来写一段代码来验证是否是使用引用计数法来判断为垃圾对象

public class Main {

	private Object instance;
	public Main() {
		//byte[] bytes=new byte[20*1024*1024];
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Main main=new Main();
		Main main2=new Main();
		
		main.instance=main2;
		main2.instance=main;
		
		main=null;
		main2=null;
		System.gc();
		////parallel垃圾回收器
	}
}

原理图
在这里插入图片描述
运行结果:
在这里插入图片描述

-verbose:gc
-xx:+PrintGCDetail
必须配置上面两个参数,才可以打印出垃圾回收的日志信息

2.2 可达性分析法

原理图
在这里插入图片描述
那些地方可以作为GCRoots的对象?

  • 虚拟机栈
  • 方法区的类属性所引用的对象
  • 方法区中常量所引用的对象
  • 本地方法栈中所引用的对象

3:垃圾回收算法

知道如何判断谁为垃圾对象后,我们就要知道怎么清除它,所以讲到垃圾回收算法

3.1 标记清除算法

  • 标记—清除算法,是以下所有垃圾回收算法的基础。
  • 什么是标记—清除呢?
    标记-清除算法其实就是有两个过程,第一个过程就是标记,第二个过程就是进行清除, 所谓的标记,就是标记出所需要回收的对象,通过可达性分析法(上一章如何判断是垃圾回收对象?)分析出来了这个对象是一个没有任何引用指向它的对象,那么,这个时候,这个对象就会被标记,标记完了之后,有一个专门的清除程序来进行把这些不用的这些对象就给清除掉了。
  • 性能分析
    • 标记和清除两个过程,它的执行性能不是特别高,也就是所谓的效率问题
    • 清除之后内存会产生大量碎片(所以碎片这个问题还得处理,怎么处理,看标记-整理算法。)
      原理图
      在这里插入图片描述

3.2 复制算法

  • java虚拟机内存结构
    线程共享区

    • 堆内存
    • 方法区

    线程独占区

    • 程序计数器
  • java虚拟机内存结构中的堆内存可继续划分
    在这里插入图片描述
    复制算法含义:

S0和s1将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉。这样使得每次都是对其中一块内存进行回收,内存分配时不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
复制算法的缺点显而易见,可使用的内存降为原来一半。
复制算法用于在新生代垃圾回收

复制算法原理图
在这里插入图片描述
上面造成的问题:内存浪费
改进后的算法
复制算法
含义:
在这里插入图片描述
原理:
在这里插入图片描述

3.3 标记-整理算法

  • 标记-整理算法标记清除基础之上做了优化,把存活的对象压缩到内存一端,而后进行垃圾清理。(java中老年代使用的就是标记压缩法)
    在这里插入图片描述

3.4 分代收集算法

  • 根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老代中创建。
    对于新生代和老年代来说,新生代回收频率很高,但是每次回收耗时很短,而老年代回收频率较低,但是耗时会相对较长,所以应该尽量减少老年代的GC.

3.5 垃圾回收时的停顿现象(为什么垃圾回收频繁执行会降低程序效率?)

垃圾回收的任务是识别和回收垃圾对象进行内存清理,为了让垃圾回收器可以更高效的执行,大部分情况下,会要求系统进行一个停顿的状态停顿的目的是为了终止所有的应用线程,只有这样的系统才不会有新垃圾的产生。同时停顿保证了系统状态在某一个瞬间的一致性,也有利于更好的标记垃圾对象。因此在垃圾回收时,都会产生应用程序的停顿。

4:垃圾回收器

垃圾回收器概述

  • Java垃圾回收器是Java虚拟机(JVM)的三个重要模块(另外两个是解释器和多线程机制)之一,为应用程序提供内存的自动分配(Memory Allocation)、自动回收(Garbage Collect)功能,这两个操作都发生在Java堆上(一段内存快)。某一个时点,一个对象如果有一个以上的引用(Rreference)指向它,那么该对象就为活着的(Live),否则死亡(Dead),视为垃圾,可被垃圾回收器回收再利用。垃圾回收操作需要消耗CPU、线程、时间等资源,所以容易理解的是垃圾回收操作不是实时的发生(对象死亡马上释放)当内存消耗完或者是达到某一个指标(Threshold,使用内存占总内存的比列,比如0.75)时,触发垃圾回收操作。有一个对象死亡的例外,java.lang.Thread类型的对象即使没有引用,只要线程还在运行,就不会

4.1 Serial 垃圾回收器(串行回收器)

  • 单线程执行回收操作,回收期间暂停所有应用线程的执行,client模式下的默认回收器。
  • 通过 -XX:+UseSerialGC命令行可选项强制指定。参数可以设置使用新生代串行和老年代串行回收器
    年轻代的回收算法(Minor Collection)
    把Eden区的存活对象移到To区,To区装不下直接移到年老代,把From区的移到To区,To区装不下直接移到年老代,From区里面年龄很大的升级到年老代。 回收结束之后,Eden和From区都为空,此时把From和To的功能互换,From变To,To变From,每一轮回收之前To都是空的。设计的选型为复制。
    年老代的回收算法(Full Collection)
    年老代的回收分为三个步骤,标记(Mark)、清除(Sweep)、合并(Compact)。标记阶段把所有存活的对象标记出来,清除阶段释放所有死亡的对象,合并阶段 把所有活着的对象合并到年老代的前部分,把空闲的片段都留到后面。设计的选型为合并,减少内存的碎片。

原理图
在这里插入图片描述

4.2 parnew 垃圾回收器(并行回收器)

  • 并行回收器在串行回收器基础上做了改进,他可以使用多个线程同时进行垃
    圾回收
    ,对于计算能力强的计算机而言,可以有效的缩短垃圾回收所需的尖
    际时间。
    ParNew回收器是一个工作在新生代的垃圾收集器,他只是简单的将串行回收
    器多线程快他的回收策略和算法和串行回收器一样。

    使用XX:+UseParNewGC 新生代ParNew回收器,老年代则使用市行回收器
    ParNew回收器工作时的线程数量可以使用XX:ParaleiGCThreads参数指
    定,一般最好和计算机的CPU相当,避免过多的栽程影响性能。

原理图
在这里插入图片描述

4.3 Parallel Scavenge 垃圾回收器(并行回收器)

  • 老年代ParallelOldGC回收器也是一种多线程的回收器,和新生代的
    ParallelGC回收器一样,也是一种关往吞吐量的回收器,他使用了标记压缩
    算法进行实现。

    -XX:+UseParallelOldGC 进行设置
    -XX:+ParallelCThread也可以设置垃圾收集时的线程教量。

  • 复制算法

  • 多线程垃圾回收器

  • 达到可控制吞吐量

    • 吞吐量:CPU用于运行用户代码的时间 与CPU消耗的总时间的比值
    • 吞吐量=(执行用户代码时间)/(执行用户代码的时间+垃圾回收所占用的时间)
  • -XX:MaxGFPauseMillis 垃圾收集器停顿时间1

  • -XX:CGTimeRatio 吞吐量大小

4.4 cms 垃圾回收器(并发GC垃圾回收器)

  • CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。CMS收集器是基于“标记-清除”算法实现的,整个收集过程大致分为4个步骤:

  • 初始标记(CMS initial mark)

  • 并发标记(CMS concurrenr mark)

  • 重新标记(CMS remark)

  • 并发清除(CMS concurrent sweep)

    其中初始标记、重新标记这两个步骤任然需要停顿其他用户线程。初始标记仅仅只是标记出GC ROOTS能直接关联到的对象,速度很快,并发标记阶段是进行GC ROOTS 根搜索算法阶段,会判定对象是否存活。而重新标记阶段则是为了修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间会被初始标记阶段稍长,但比并发标记阶段要短。

    • 由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以整体来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
      CMS收集器的优点:并发收集、低停顿,但是CMS还远远达不到完美,其主要有四个显著缺点:
  • CMS收集器对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降。CMS默认启动的回收线程数是:(CPU数量+3) / 4。

  • CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure“,失败后而导致另一次Full GC的产生。由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自热会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。这一部分垃圾称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,即需要预留足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分内存空间提供并发收集时的程序运作使用。

  • 在默认设置下,CMS收集器在老年代使用了68%的空间时就会被激活,也可以通过参数 -XX:CMSInitiatingOccupancyFraction的值来提供触发百分比,以降低内存回收次数提高性能。要是CMS运行期间预留的内存无法满足程序其他线程需要,就会出现“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置的过高将会很容易导致“Concurrent Mode Failure”失败,性能反而降低。

  • CMS是基于“标记-清除”算法实现的收集器,使用“标记-清除”算法收集后,会产生大量碎片。空间碎片太多时,将会给对象分配带来很多麻烦,比如说大对象,内存空间找不到连续的空间来分配不得不提前触发一次Full GC。为了解决这个问题,CMS收集器提供了一个 -XX:UseCMSCompactAtFullCollection开关参数 ,用于在Full GC之后增加一个碎片整理过程,还可通过 -XX:CMSFullGCBeforeCompaction参数设置执行多少次不压缩的Full GC之后,跟着来一次碎片整理过程。

原理
在这里插入图片描述

4.5 最牛的垃圾回收器——G1垃圾回收器

G1回收器(Garbage-First)实在]dk1.7中提出的垃圾回收器,从长期目标来看是为了取代CMS回收器,G1回收器拥有独特的垃圾回收策略,G1属于分代垃圾回收器,区分新生代和老年代,依然有eden和from/to区,它并不要求整个eden区或者新生代、老年代的空间都连续,它使用了分区算法。
并行性: G1回收期间可多线程同时工作。
井发性G1拥有与应用程序交替执行能力,部分工作可与应用程序同时执行,在整个
GC期间不会完全阻塞应用程序。

分代GC:G1依然是一个分代的收集器,但是它是非两新生代和老年代一杯政的杂尊。
空间基理,G1在国收过程中,不会微CMS那样在若千tacAy 要进行碎片整理。
G1
来用了有效复制对象的方式,减少空间碎片。
利得程,用于分区的原因,G可以贝造取都分区城进行回收,帽小了国收的格想,
提升了性能。
使用.XXX:+UseG1GC 应用G1收集器,
Mills指定最大停顿时间
使用-XX:MaxGCPausel
设置并行回收的线程数量
使用-XX:ParallelGCThreads

优点:

  • 并行与并发
  • 分代收集
  • 空间整合
  • 可预测停顿

步骤:

  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收
    在这里插入图片描述
发布了167 篇原创文章 · 获赞 119 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_44891295/article/details/104074834