重点知识学习(4.4)--[垃圾回收阶段算法,垃圾回收期,对象引用]

1. 垃圾回收阶段算法

标记区分了存活对象之后,就需要进行清除了;执行垃圾回收,释放堆内存;
常用算法:

  • 标记清除算法(Mark-Sweep)
  • 复制算法(Copying)
  • 标记-压缩算法(Mark-Compact)

标记清除算法(Mark-Sweep)

标记阶段:注意这里是标记所有被引用的对象; 并非标记即将被清除的垃圾;

清除阶段: 注意这里不是直接清除垃圾对象;而是将垃圾对象的地址维护到空闲的列表当中,当新对象产生时,则判断空闲列表中的对象空间是否可以存放新对象,若可以存储,则覆盖垃圾对象;

Collector 对堆内存从头到尾进行线性的遍历,如果发现某个对象在其 Header 中没有标记为可达对象,则将其回收。

在这里插入图片描述

优点:容易理解;
缺点:效率差,进行 GC 的时候,需要停止整个应用程序,清理出来的空闲内存是不连续的,产生内碎片,需要维护一个空闲列表。


复制算法 (Copying)

复制算法就比较简单了,他将内存分成大小相等的两块区域,每次使用一块即可;当垃圾回收时,就把不是垃圾的对象全部复制到另一块内存中,排放整齐,然后将之前的内存清空.

优点:这样效率是很不错的;且内存的连续性好,减少了内存碎片.

缺点:他需要两倍的内存空间, G1 这种分拆成为大量 region 的 GC,复制而不是移动,那么在维护对象引用关系时,也是比较耗时的.

在这里插入图片描述

复制算法的应用
新生代中的幸存者0区和幸存者1区使用复制算法. 存活对象少、垃圾对象多

如果系统中的垃圾对象很多,复制算法需要复制的存活对象数量并不会太大,效率较高
.
老年代大量的对象存活,那么复制的对象将会有很多,效率会很低
在新生代,对常规应用的垃圾回收,一次通常可以回收70% - 99% 的内存空间
回收性价比很高。所以现在的商业虚拟机都是用这种收集算法回收新生代。

在这里插入图片描述


标记压缩算法(Mark-Compact)

标记-清除算法的确可以应用在老年代中,但是该算法不仅执行效率低下,而且在执行完内存回收后还会产生内存碎片,所以 JVM 的设计者需要在此基础之上进行改进。

标记阶段: 从根节点开始标记所有被引用对象

压缩阶段: 将所有的存活对象压缩到内存的一端,按顺序排放。之后,清理边界外所有的空间。

在这里插入图片描述

标记清除-标记压缩区别

标记-压缩算法的最终效果等同于标记-清除算法执行完成后,再进行一次内存碎片整理

  • 清除算法是一种非移动式的回收算法(空闲列表记录位置),标记-压缩是移动式的。是否移动回收后的存活对象是一项优缺点并存的风险决策。

  • 标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉。如此一来,当我们需要给新对象分配内存时,JVM 只需要持有一个内存的起始地址即可.

  • 标记清除是不移动对象, 不会把垃圾对象清除掉(维护在一个空闲列表中)

  • 标记-压缩是要移动对象的, 要清除掉垃圾对象.


效率相对低, 对象位置移动后需要重新设置对象地址, 也会有STW

在这里插入图片描述

分代收集

不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。一般是把 Java堆分为新生代和老年代,这样就可以根据各个年代的特点使用不同的回收算法,以提高垃圾回收的效率。

业务相关对象:
比如 Http 请求中的 Session 对象、线程、Socket 连接,这类对象跟业务直接挂钩,因此生命周期比较长。

程序运行过程中生成的临时变量:
比如:String 对象,由于其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次即可回收。

几乎所有的 GC 都采用分代收集算法执行垃圾回收的,在 HotSpot 中,基于分代的概念,GC 所使用的内存回收算法必须结合年轻代和老年代各自的特点.


年轻代(Young Gen)
区域相对老年代较小,对象生命周期短、存活率低,回收频繁
采用复制算法, 复制算法的效率只和当前存活对象大小有关.

老年代(Tenured Gen)
区域较大,对象生命周期长、存活率高,回收不及年轻代频繁。
一般是由标记- 清除或者是标记-清除与标记-整理的混合实现。

  • Mark 阶段的开销与存活对象的数量成正比。
  • Sweep 阶段的开销与所管理区域的大小成正相关。
  • Compact 阶段的开销与存活对象的数据成正比。

2.System.gc()

默认情况下可以通过 System.gc()或者 Runtime.getRuntime().gc()的调用,会显式触发 Full GC整堆收集,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。

System.gc()调用附带免责声明,无法保证对垃圾收集器的调用(不能确保立即生效)


3.内存溢出,内存泄漏

内存溢出
由于 GC 一直在发展,所有一般情况下,除非应用程序占用的内存增长速度非常快,造成垃圾回收已经跟不上内存消耗的速度,否则不太容易出现 OOM 的情况。

大多数情况下,GC 会进行各种年龄段的垃圾回收,实在不行了就放大招,来一次独占式的 Full GC 操作,这时候会回收大量的内存,供应用程序继续使用。Javadoc 中对 OutofMemoryError 的解释是,没有空闲内存,并且垃圾收集器也无法提供更多内存。

内存泄漏

“存储渗漏”。严格来说,只有对象不会再被程序用到了,但是 GC 又不能回收他们的情况,才叫内存泄漏。

尽管内存泄漏并不会立刻引起程序崩溃,但是一旦发生内存泄漏,程序中的可用内存就会被逐步蚕食,直至耗尽所有内存,最终出现 OutofMemory 异常,导致程序崩溃。

注意,这里的存储空间并不是指物理内存,而是指虚拟内存大小,这个虚拟内存大小取决于磁盘交换区设定的大小。


4. Stop the World

简称为STW , GC 事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为 STW。

可达性分析算法中枚举根节点(GC Roots)会导致所有 Java 执行线程停顿

在标记垃圾对象时,需要以某个时间点上的内存情况进行分析,采用(拍照/快照),由于不进行停顿的话,内存中的对象不停的变化,导致分析结果不准确,注意:停顿是不可避免的,采用优秀的垃圾回收期尽可能地减少时间.

  • 分析工作必须在一个能确保一致性的快照中进行
  • 一致性指整个分析期间整个执行系统看起来像被冻结在某个时间点上
  • 如果出现分析过程中对象引用关系还在不断变化,则分析结果的准确性无法保证
  • 被 STW 中断的应用程序线程会在完成 GC 之后恢复,频繁中断会让用户感觉
  • 像是网速不快造成电影卡带一样,所以我们需要减少 STW 的发生。
  • STW 事件和采用哪款 GC 无关,所有的 GC 都有这个事件。
  • 越优秀,回收效率越来越高,尽可能地缩短了暂停时间。
  • STW 是 JVM 在后台自动发起和自动完成的。在用户不可见的情况下,把用户正常的工作线程全部停掉。

5.对象引用

当内存空间还足够时,则能保留在内存中;如果内存空间在进行垃圾收集后还是很紧张,则可以抛弃这些对象。

在 JDK1.2 版之后,Java 对引用的概念进行了扩充,这4种引用强度依次逐渐减弱:

  • 强引用(Strong Reference)
  • 软引用(Soft Reference)
  • 弱引用(Weak Reference)
  • 虚引用(Phantom Reference)

除强引用外,其他3种引用均可以在java.lang.ref包中找到。
Reference 子类中只有终结器引用是包内可见的,其他 3 种引用类型均为public,可以在应用程序中直接使用.

强引用–>有引用指向的对象,
软引用,弱引用,虚引用,都是垃圾.

在这里插入图片描述

强引用(Strong Reference)

在程序代码之中普遍存在的引用赋值,即类似“Object obj=new Object()”这种引用关系。
无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。宁可报 OOM,也不会 GC 强引用.

  • 在 Java 程序中,最常见的引用类型是强引用(普通系统 99%以上都是强引用),也是默认的引用类型。
  • Java 语言中使用 new 操作符创建一个新的对象,并将其赋值给一个变量的时候,这个变量就成为指向该对象的一个强引用。
  • 只要强引用的对象是可触及的,垃圾收集器就永远不会回收掉被引用的对象。
  • 只要强引用的对象是可达的,jvm 宁可报 OOM,也不会回收强引用。
  • 对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以当做垃圾被收集了,当然具体回收时机还是要看垃圾收集策略。
  • 软引用、弱引用和虚引用的对象是软可触及、弱可触及和虚可触及的,在一定条件下,都是可以被回收的。强引用是造成 Java 内存泄漏的主要原因之一
public class StrongReferenceDemo {
    public static void main(String[] args) {
        StringBuffer str = new StringBuffer("Hello,world");
        StringBuffer str1 = str;
        str = null;
        System.gc();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(str1);
    }
}

软引用(SoftReference)

在系统将要发生内存溢出之前,将会把这些对象列入回收范围之中进行第二次回收。如果这次回收后还没有足够的内存,才会抛出内存溢出异常。

软引用是用来描述一些还有用,但非必需的对象。

  • 只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
  • 注意,这里的第一次回收是不可达的对象软引用通常用来实现内存敏感的缓存。
  • 高速缓存就有用到软引用。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。

弱引用(WeakReference):

被弱引用关联的对象只能生存到下一次垃圾收集之前
当垃圾收集器工作时,无论内存空间是否足够,都会回收掉被弱引用关联的对象

  • 弱引用也是用来描述那些非必需对象,只被弱引用关联的对象只能生存到下一次垃圾收集发生为止。
  • 在系统 GC 时,只要发现弱引用,不管系统堆空间使用是否充足,都会回收掉只被弱引用关联的对象。

虚引用(PhantomReference):

一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个对象的实例。

  • 为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知

  • 所有引用类型中最弱的一个一个对象是否有虚引用的存在,完全不会决定对象的生命周期。

  • 如果一个对象仅持有虚引用,那么它和没有引用几乎是一样的,随时都可能被垃圾回收器回收。

  • 虚引用必须和引用队列一起使用。虚引用在创建时必须提供一个引用队列作为参数。

以下内容为了解部分






6.垃圾回收器(了解即可)

收集算法是内存回收的方法论,那么收集器就是内存回收的实践者

  • 垃圾收集器没有在 java 虚拟机规范中进行过多的规定,可以由不同的厂商、不同版本的 JVM 来实现。

  • 由于 JDK 的版本处于高速迭代过程中,因此 Java 发展至今已经衍生了众多的 GC 版本。

  • 从不同角度分析垃圾收集器,可以将 GC 分为不同的类型。

分类

按线程数分,可以分为串行垃圾回收器和并行垃圾回收器

串行回收指的是在同一时间段内只允许有一个 CPU 用于执行垃圾回收操作,此时工作线程被暂停,直至垃圾收集工作结束

并行回收 可以运用多个 CPU 同时执行垃圾回收,因此提升了应用的吞吐量,不过并行回收仍然与串行回收一样,采用独占式,使用 "stop-the-world"机制

在这里插入图片描述

按照工作模式分,可以分为并发式垃圾回收器和独占式垃圾回收器。
并发式垃圾回收器 与应用程序线程交替工作,以尽可能减少应用程序的停顿时间。

独占式垃圾回收器(stop the world) 运行后就停止应用程序中的所有用户线程,直到垃圾回收过程完全结束。

在这里插入图片描述


GC 性能指标

吞吐量运行用户代码的时间占总运行时间的比例(总运行时间:程序的运行时间+内存回收的时间)

垃圾收集开销:垃圾收集所用时间与总运行时间的比例。

暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间。
收集频率:相对于应用程序的执行,收集操作发生的频率。
内存占用:Java 堆区所占的内存大小。
生命周期:一个对象从诞生到被回收所经历的时间。


HotSpot 垃圾收集器(了解即可)

如果两个收集器之间存在连线,则说明它们可以搭配使用。虚拟机所处的区域则表示它是属于新生代还是老年代收集器。

串行回收器:Serial,Serial old
并行回收器:ParNew,Parallel scavenge,Parallel old
并发回收器:CMS、G1

在这里插入图片描述

在这里插入图片描述

新生代收集器:Serial,ParNew.Parallel scavenge;
老年代收集器:Serial old.Parallel old.cMS;
整堆收集器:G1;

在这里插入图片描述

1.Serial 垃圾收集器(单线程)

Serial 收集器是最基本的、新生代的、发展历史最悠久的收集器。

特点:单线程、简单高效,采用复制算法,对于限定单个 CPU 的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。收集器进行垃圾回收时,必须暂停其他所有的工作线程,直到它结束(Stop The World)。

应用场景:适用于 Client 模式下的虚拟机。

Serial / Serial Old 收集器运行

在这里插入图片描述

2.Serial Old 垃圾收集器(单线程)

Serial Old 是 Serial 收集器的老年代版本。

特点:同样是单线程收集器,采用标记-整理算法。

应用场景:主要也是使用在 Client 模式下的虚拟机中。也可在 Server 模式下使用。

3.ParNew 垃圾收集器(多线程)

ParNew 收集器其实就是 Serial 收集器的多线程版本。

除了使用多线程外其余行为均和 Serial 收集器一模一样(参数控制、收集算法、Stop The World、对象分配规则、回收策略等)。

特点:多线程、ParNew 收集器默认开启的收集线程数与 CPU 的数量相同,在CPU 非常多的环境中,可以使用-XX:ParallelGCThreads 参数来限制垃圾收集的线程数。和 Serial 收集器一样存在 Stop The World 问题

应用场景:ParNew 收集器是许多运行在 Server 模式下的虚拟机中首选的新生代收集器,因为它是除了 Serial 收集器外,唯一一个能与 CMS 收集器配合工作的。

ParNew/Serial Old 组合收集器运行

在这里插入图片描述

4.Parallel Scavenge 垃圾收集器(多线程)

Parallel Scavenge 和 ParNew 一样,都是多线程、新生代垃圾收集器。

Parallel Scavenge:追求 CPU 吞吐量,能够在较短时间内完成指定任务,
因此适合没有交互的后台计算,。

5.Parallel Old 垃圾收集器(多线程)

是 Parallel Scavenge 收集器的老年代版本。

特点:多线程,采用标记-整理算法。

应用场景:注重高吞吐量以及 CPU 资源敏感的场合,都可以优先考虑Parallel Scavenge+Parallel Old 收集器。

在这里插入图片描述

6. CMS 回收器(低延迟)

CMS(Concurrent Mark Sweep,并发标记清除)收集器是以获取最短回收停顿时间为目标的收集器(追求低停顿),它在垃圾收集时使得用户线程和 GC 线程并发执行,因此在垃圾收集过程中用户也不会感到明显的卡顿。

  • 初始标记:Stop The World,仅使用一条初始标记线程对所有与 GC Roots 直接关联的对象进行标记。
  • 并发标记:使用多条标记线程,与用户线程并发执行。此过程进行可达性分析,标记出所有废弃对象。速度很慢。
  • 重新标记:Stop The World,使用多条标记线程并发执行,将刚才并发标记过程中新出现的废弃对象标记出来。
  • 并发清除:只使用一条 GC 线程,与用户线程并发执行,清除刚才标记的对象。这个过程非常耗时。
  • 并发标记与并发清除过程耗时最长,且可以与用户线程一起工作,因此,总体上说,CMS 收集器的内存回收过程是与用户线程一起并发执行的。

在这里插入图片描述

优点: 并发收集;低延迟
缺点

  • 会产生内存碎片,导致并发清除后,用户线程可用的空间不足。在无法分配大对象的青况下,不得不提前触发 Ful1 GC. 2.CMS 收集器对 CPU 资源非常敏感。
  • 在并发阶段,它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
7.G1(Garbage First)回收器(区域划分代式)

应用程序所应对的业务越来越庞大、复杂,用户越来越多,没有GC 就不能保证应用程序正常进行,而经常造成 STW 的 GC 又跟不上实际的需求,所以才会不断地尝试对 GC 进行优化。G1(Garbage-First)垃圾回收器是在 Java7 update 4 之后引入的一个新的垃圾回收器,是当今收集器技术发展的
最前沿成果之一

为了适应现在不断扩大的内存和不断增加的处理器数量,进一步降低暂停时间(pause time),同时兼顾良好的吞吐量。

在这里插入图片描述

G1 是一个并行回收器,它把堆内存分割为很多不相关的区域(Region)(物理上不连续的)。使用不同的 Region 来表示 Eden、幸存者 0 区,幸存者1 区,老年代等

G1 GC 有计划地避免在整个 Java 堆中进行全区域的垃圾收集。G1 跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region

侧重点在于回收垃圾最大量的区间(Region),即垃圾优先(Garbage First)。

  • G1(Garbage-First)是一款面向服务端应用的垃圾收集器,主要针对配备多核 CPU 及大容量内存的机器,以极高概率满足 Gc 停顿时间的同时,还兼具高吞吐量的性能特征。
  • G1 收集器可以 “ 建立可预测的停顿时间模型 ”,它维护了一个列表用于记录每个 Region 回收的价值大小(回收后获得的空间大小以及回收所需时间的经验值),这样可以保证 G1 收集器在有限的时间内可以获得最大的回收效率。

在这里插入图片描述

  • 初始标记:标记出 GC Roots 直接关联的对象,这个阶段速度较快,需要停止用户线程,单线程执行。
  • 并发标记:从 GC Root 开始对堆中的对象进行可达新分析,找出存活对象,这个阶段耗时较长,但可以和用户线程并发执行。
  • 最终标记:修正在并发标记阶段引用户程序执行而产生变动的标记记录。
  • 筛选回收:筛选回收阶段会对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来指定回收计划(用最少的时间来回收包含垃圾最多的区域,这就是 Garbage First 的由来——第一时间清理垃圾最多的区块),这里为了提高回收效率,并没有采用和用户线程并发执行的方式,而
    是停顿用户线程。

适用场景:要求尽可能可控 GC 停顿时间;内存占用较大的应用。可以用 -XX:+UseG1GC使用 G1 收集器,jdk9 默认使用 G1 收集器。


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小智RE0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值