美文网首页
Java程序进阶课程学习(四)

Java程序进阶课程学习(四)

作者: MikeShine | 来源:发表于2020-02-06 11:06 被阅读0次

写在前面

在上一部分的学习中,我们对 JVM 的基础概念、JVM运行时内存、类加载机制有了基本的了解。
下面我们开始对于 JVM 的重点----GC,来进行学习。


6.5 GC中,如何判断对象存活、对象引用

什么是垃圾

很好理解,当一个对象没有引用指向他的时候,他就是垃圾。

判断对象是否存活的算法

为了判断一个对象是否存活,有多种算法。

引用计数算法

顾名思义,对一个对象,我们统计其引用的个数,进行计数。当引用次数为0的时候,即可以判断他变成垃圾了。
引用计数器算法很直观,也很简单。但是并没有在 Java 中使用,因为其不能解决 对象间循环引用 的问题。

//  如果使用引用计数算法,GC无法回收循环引用的情况
ObjA.instance = ObjB
ObjB.instance = ObjA
可达性分析算法(根搜索算法)

既然引用搜索算法无法满足要求,那么 java 就采用了另外一种算法----可达性分析算法。
可达性分析算法中,从根节点GC ROOT 出发,向下搜索引用链,对象如果不可被搜索到(不可达),就是垃圾。
那么什么样的对象可以看做是 GC ROOT 节点呢?

  • 栈中引用的对象(局部对象)
  • 方法区中类静态属性引用的对象
  • 方法区中的常量引用的对象
  • 本地方法栈引用的对象

总结一下,栈和方法区引用的对象。
两个栈(JVM栈和本地方法栈)引用的对象,方法区中 类静态属性、常量 引用的对象

再谈对象引用

这里判断对象是否存活,都是跟 “引用” 息息相关的。而传统意义上的引用,就只有被引用,和没有被引用两种状态。
而很多时候我们需要描述一些,内存空间足够则保留,内存不够则抛弃的对象,则需要多种的引用形式。
Java中同样给出了多种引用形式,下面列出来的引用依次变弱。

  • 强引用
    就是传统意义上理解的引用。 Object obj = new Object() 这种引用。只要强引用存在,就一定不会回收
  • 软引用
    软引用用来描述 有用,非必须的元素
    GC第一次收集之后,软引用对象存在。但是如果内存还是不够(要内存溢出),则会触发GC第二次回收,此时,软引用对象就会被回收。

这里给一个应用场景来理解一下:
我们要实现一个缓存的功能,当内存足够的时候,通过一个软引用对象来取值,而不是直接从繁忙的真实数据来源来取值。内存是在不够了,这个软引用对象被回收,再从数据来源来直接取值。

  • 弱引用
    弱引用描述 非必须的对象
    弱引用的对象,在下一次垃圾GC时候被回收。弱引用主要用来监控对象是否已经被GC标记为即将回收(下一次GC)的垃圾。弱引用的 isEnQueued() 方法就可以返回该对象是否要被下次回收。
  • 虚引用
    唯一目的,就是在对象被GC回收时候,收到一个系统通知。

后面三者都有自己对应的实现类。 SoftReference/ WeakReference/ PhantomReference 类。


6.6 分代垃圾回收

上面一小节我们了解了对于一个对象,JVM是如何判断其是否已经变成垃圾了。那么变成垃圾之后呢?当然是要 GC 了。
目前的垃圾回收,核心都是分代垃圾回收机制。要理解分代回收机制。首先就要明白分代对于内存的划分。
这一部分在下面一小节对于 分代收集算法中,有详细的解释。


6.7 典型的垃圾收集算法

在上面一小节“分代垃圾回收”的基本思想指导下,我们来学习一下目前典型的垃圾收集算法。

mark-sweep标记清除算法

mark-sweep算法是最简单直接的垃圾收集算法,分为两个阶段:标记 + 清除。即标记出需要被回收的对象,再清除对象所占用的空间。

  • 优点:实现简单。
  • 缺点:容易产生内存碎片。
    可能会导致大对象分配内存空间时,触发新一轮的GC。
Copying复制算法

将内存一分为二,只用一半。当这一半用完之后,将存活的对象放到另外一半,把这一半清除掉。

这么理解吧,有点类似于缓存的思想。

  • 优点:没有内存碎片
  • 缺点:内存只有一半。如果存活对象多的话,效率很低
Mark-Compact 标记整理算法

跟 mark-sweep 很类似。先标记,再把存活对象移动到一端,清理掉端边界以外的内存。

Generational Collection 分代收集算法

分代收集算法,比上面的三种GC算法复杂一点。
核心思想是:根据对象存活的生命周期,把内存进行划分。将heap堆分为老年代(Tenured Generation)和新生代(Young Generation)。老年代的特点是,每次GC时候,只有少量的对象需要被回收,新生代则是大量的对象。

新生代采用 Copying 算法,因为要回收的对象很多,存活的对象不多。新生代 = 1较大Eden + 2较小Survivor。 一般只使用一个Eden和Survivor。回收时候,将 Eden 和 Survivor存活的对象,复制到另外一个 Survivor,再清空前者。
那么基于对内存的划分,就可以在不同的内存区域,采用合适的GC算法。

对于老年代,采用 Mark-Compact 算法。
这里需要注意以下几个点:
MinorGC: 清理年轻代。 Major GC:清理老年代。FullGC:清理整个堆空间。

  1. 对象优先在 Eden 分配:
    对象优先在 Eden 区域分配。当 Eden 没有足够空间,虚拟机进行一次 Minor GC
  2. 大对象直接进入老年代
    为了避免 Eden 区和两个 Survivor 区的频繁内存复制,大对象直接进入老年代。
  3. 长期存活的对象进入老年代
    对象再 Eden 中出生,经过一次 Minor GC 后仍存活,被 Survivor接纳,其年龄被设为1,在 Survivor区每经历一个 Minor GC,年龄++,年龄增长到一定值,会被放到老年代中。
  4. 动态对象年龄判定
    并不是一定要满足3中年龄达到阈值的情况,对象才可以进入老年代。
    同年龄对象 总和 超过 survivor 空间内存的一半,这些对象就会进入老年代。
  5. 空间分配担保
    首先,这里是有一个设置HandlePromotionFailure,来设定是否允许老年代来进行担保。
    其次,这里的担保,是指 在 MinorGC 时候,可能遇到一个 Survivor 空间不够用(年轻代中存活对象很多,极端就是都存活)。这时候就需要老年代借用空间给年轻代,这就是担保。
    而担保的前提是,老年代的空间也要够,但是实际上老年代并不知道这一次 MinorGC会存活多少对象,需要多少内存,所以就取一个之前每次均值作为比较对象,如果老年代现存对象比这个均值还要小,那么就需要执行 MajorGC 为年轻代腾出空间,做担保。

跟内存空间联系起来:堆空间 heap 被分为年轻代和老年代。年轻代又分为 eden + 2 survivor

上面说了新生代和老年代,都是在 heap 中的。实际上,在heap 之外还有一个永久代 Permanent Generation(就是方法区non-heap,在 HotSpot虚拟机中叫做永久代),存储 class 类、常量、方法描述等。对永久代的回收主要是回收 废弃常量、无用的类

可以看到,永久代回收的东西已经不是对象了,而是类和常量。那自然也就不在 heap 中了。


6.8 垃圾收集器

前面一个小节我们介绍了GC算法,那么这一个小节会聚焦于GC的具体实现----垃圾收集器。
首先是一张整体的图


垃圾收集器全家桶
Serial/
  • 单线程。
  • 新生代,Copying
  • 简单高效;但是会给用户带来停顿

因为没有线程间交互的开销,所以可以简单高效的进行垃圾收集,对于 Client 模式下的虚拟机是首选。一般是使用 Serial(新生代垃圾收集) + Serial Old(老年代垃圾收集)

ParNew
  • Serial 的多线程版本。(二者的实现绝大部分都相同)

对于 Server 模式下的虚拟机是首选。因为除了 Serial之外,只有 ParNew 可以配合 CMS 收集器,实现并发收集(一边收集一边产生垃圾)。

Parallel Scavenge (平行捡破烂)
  • 新生代,Copying。
  • 多线程(并行收集)

目的:达到可控的吞吐量 ,高效利用CPU时间,尽快完成程序运算任务。
吞吐量: \frac {cpu运行用户代码时间} {用户代码时间+GC时间}

其他的收集器,基本都是关注于,缩短垃圾回收带来的停顿时间,而 Parallel Scavenge 则关注于吞吐量。
停顿时间给交互带来了阻碍,而吞吐量则直接影响计算性能(CPU利用率)。所以 Parallel Scavenge 适合于用在后台运行,交互不多的任务

Serial Old 收集器

Serial 的老年代版本

  • 单线程
  • 老年代,Mark-Compact 标记整理算法。

也是主要用于 Client 模式下的虚拟机。跟前面说的一样。

Parallel Old
  • Parallel Scavenge 老年代版本,Mark-Compact 标记整理算法

在注意吞吐量和CPU利用率时,Parallel Scavenge + Parallel Old。

CMS(Concurrent Mark Sweep)

目的:最短的垃圾收集停顿时间。所以一般用在网站服务器上,因为一般用户要求服务器获得最快的响应。

  • 并发
  • 老年代,标记清除(所有老年代中,唯一一个使用标记清除的)

下面主要了解一下,其垃圾回收的过程。分为4步:初始标记、并发标记、重新标记、并发清除

  1. 初始标记(initial mark)
    标记GC ROOT能直接关联到的对象,速度很快。会停顿
  2. 并发标记(concurrent mark)
    GC ROOT Tracing 的过程,是最耗时的。(是并发执行的,用户线程不停止,所以叫并发标记)
  3. 重新标记(remark)
    标记步骤2期间,程序继续执行产生变动的对象的记录。会停顿较长时间
  4. 并发清除(concurrent sweep)

并发标记、并发清除的 并发 是指垃圾处理进程和用户进程并发进行。初始标记、重新标记这里的并发,是指多个垃圾回收进程同时进行,而此时用户进程是暂停的。

优点:并发收集、低停顿
缺点:

  • CMS收集器 对 CPU资源非常敏感。其实只要涉及并发设计的程序,都会对CPU资源敏感。在CPU数量较少的时候,整体性能被拖慢很多。
  • CMS收集器 无法处理浮动垃圾。在并发清除时候,用户线程还是在运行,这时候产生的垃圾不会在本次垃圾回收被处理,称为浮动垃圾。
  • CMS 空间碎片。由于采用 标记清除算法,会产生内存碎片,引发 新的垃圾收集。
  • Concurrent Mode Failure 采用 SerialOld 作为老年代备用。
G1(Garbage First)

面向 服务端 应用的。标记整理算法。

G1 收集器将堆内存划分为一个个Region。同时维护了各个 Region垃圾堆积价值列表,优先回收价值大,代价小的 Region。
同时,为了避免全堆扫描,每一个 Region 都有一个对应的 Remembered Set。这个 Remembered Set 记录引用对象的信息,从而只要通过 Remembered Set 就可以找到相关对象,不用全堆扫描也不会遗漏。

  • 并行与并发:利用多cpu缩短 stop-the-world停顿时间
  • 分代收集:G1收集器同时运行于 新生代和老年代
  • 空间整合:没有内存碎片
  • 可预测的停顿:由于优先回收机制的存在,可以预测停顿时间。

垃圾回收的过程总结为:

  1. 初始标记
    跟 CMS一样,先标记 GC ROOT 直接相关的对象。
  2. 并发标记
    GC ROOT Tracing过程,并发执行。
  3. 最终标记
    跟 CMS 一样
  4. 筛选回收
    根据维护的回收价值列表,来优先回收。

可以看出,跟CMS来比,除了最后一步,都一样。

总结一下:

  1. 串行、并行、并发:
  • 串行:Serial & Serial Old
  • 并行:ParNew & Parallel Scavenge & Para Old
  • 并发:CMS & G1
  1. 吞吐量优先和停顿时间优先
  • 吞吐量优先:Para Scavenge & Para Old
  • 停顿时间优先:CMS

client 模式: Serial + SerialOld
server 模式:ParNew + CMS
低交互模式:Para Scavenge + ParaOld

更加详细的垃圾回收器的整理

相关文章

  • Java程序进阶课程学习(四)

    写在前面 在上一部分的学习中,我们对 JVM 的基础概念、JVM运行时内存、类加载机制有了基本的了解。下面我们开始...

  • Java程序进阶课程学习(二)

    写在前面 上一份笔记中,我们记录了本课程的前三章,关于并发编程的部分。下面我们接着看,关于 网络编程 的相关部分。...

  • Java程序进阶课程学习(五)

    写在前面 之前我们大概学习了集合类的相关知识。了解了一下基本的结合类和其中的一些方法。但是,对于集合类的性能,并没...

  • Java程序进阶课程学习(三)

    写在前面 前面两节,我们学习了关于,并发编程 & 网络编程的 一些基础知识,有了基本的了解。这一部分我们学习一下关...

  • Java程序进阶课程学习(一)

    写在前面 继之前的 Java 的基础课程之上,来看下 Java 的进阶课程主要包括了 多线程的概念和一些虚拟机的知...

  • Java程序进阶课程学习(六)

    写在前面 上一部分中,我们对 Java 中的 集合框架和具体的集合类进行了一定的了解(作为 java 中常用的一些...

  • java学习day04-方法和数组

    java学习第四天内容总结: 学习内容: 关注公众号:java进阶架构师,获取的学习视频 总结: 1、java...

  • Java进阶

    注:采转归档,自己学习查询使用 Java进阶01 String类Java进阶02 异常处理Java进阶03 IO基...

  • Java进阶之路(转)

    Java进阶之路——从初级程序员到架构师,从小工到专家来源:极客头条原文 怎样学习才能从一名Java初级程序员成长...

  • Java编程语言基础知识进阶学习路线及目标

    Java编程语言基础知识进阶学习内容及学习目标,此阶段学习具备JavaSE基本开发技巧,可胜任简单单机应用程序。对...

网友评论

      本文标题:Java程序进阶课程学习(四)

      本文链接:https://www.haomeiwen.com/subject/dppyxhtx.html