文章目录

  • 前言
  • 一、如何判断对象已死/是垃圾?
    • 1. 引用计数算法
    • 2. 可达性分析算法
  • 二、垃圾回收算法
    • 1. 标记-清除算法
    • 2. 复制算法
    • 3. 标记-整理算法
    • 4. 分代收集算法
  • 三、垃圾收集器
    • 1.CMS:老年代收集器
    • 2.G1收集器(唯一一款全区域的垃圾回收器)
  • 四、JMM
    • 1.如何划分
    • 2.回顾volatile作用

前言

上面写了Java运行时内存的各个区域。对于程序计数器、虚拟机栈、本地方法栈这三部分区域而言,其生命周期与相关线程有关,随线程而生,随线程而灭。并且这三个区域的内存分配与回收具有确定性(当方法结束或者线程结束时,内存自然随着线程回收了)

垃圾回收出现在堆和方法区
垃圾: 没有任何引用能指向的对象
垃圾回收: java虚拟机启动,就会创建一些垃圾回收线程,来并发的执行垃圾回收工作

一、如何判断对象已死/是垃圾?

1. 引用计数算法

给对象增加一个引用计数器,多一个地方引用,计数器+1;少一个就-1;任何时刻计数器为0的对象就是不能再被使用的,即对象已“死”

缺陷: 无法解决循环引用问题(java中没有采用)

循环引用:

2. 可达性分析算法

上面提到Java并不采用引用计数法来判断对象是否已“死”,而采用“可达性分析”来判断对象是否存活(同样采用此法的还有C#,Lisp-最早的一门采用动态内存分配的语言)

核心思想: 通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到GC Roots没有任何的引用链相连时(从GC Roots到这个对象不可达)时,证明此对象是不可用的。以下图为例:

GC Roots: 根结点(程序运行起始的位置引用到的对象)
引用链: 从GC Roots往下搜索,走过的路径
如果从GC Roots,无法到达的对象,就是垃圾

对象Object5-Object7之间虽然彼此还有关联,但是他们到GC Roots是不可达的,因此他们会被判定为可回收对象

在Java语言中,可作为GC Roots的对象包含下面几种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
  2. 方法区中类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中JNI(Native方法)引用的对象

从上面可以看出“引用”的功能,除了最早我们使用它(引用)来查找对象,现在我们还可以使用“引用”来判断死亡对象了。所以在JDK1.2时,Java对引用的概念做了扩充,将引用分为强引用、软引用、弱引用和虚引用四种,这四种引用的强度依次递减

四种引用类型(了解)

1.强引用: new对象的都是强引用,只要强引用还存在,垃圾回收器永远不会回收被引用的对象实例
2.软引用: 用来描述一些还有用但是不是必须的对象。对于软引用关联着的对象,在系统要发生内存溢出之前,会把这些对象列入回收范围之中进行第二次回收。如果这次回收还是没有足够的内存,才会抛出内存溢出异常
3.弱引用: 也是用来描述非必需对象的。但是它的强度要弱于软引用。被弱引用关联的对象只能生存到下一次垃圾回收发生之前。也就是说:当垃圾回收器开始进行工作时,无论当前内容是否够用,都会回收掉只被弱引用关联的对象
4.虚引用: 它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的唯一目的就是能在这个对象被收集器回收时收到系统一个通知

整体看垃圾回收:
jvm启动,就会创建一些垃圾回收线程(清理工),执行垃圾回收工作:
使用工具(垃圾回收器)按照一定的方式(垃圾回收算法)来清理垃圾

二、垃圾回收算法

上面我们已经将死亡对象标记出来了,标记出来之后我们就可以进行垃圾回收操作了

1. 标记-清除算法

“标记-清除”算法是最基础的收集算法。

分为“标记”和“清除”两个阶段:
首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象

存在两个缺陷:
(1)效率问题: 标记和清除这两个过程的效率都不高
(2)空间问题: 标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行中需要分配较大对象时,无法找到足够连续内存而不得不提前触发另一次垃圾收集

2. 复制算法

“复制”算法是为了解决“标记-清理”的效率问题。 它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后再把已经使用过的内存区域一次清理掉。

好处: 每次都是对整个半区进行内存回收,内存分配时就不需要考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配即可,实现简单,运行高效
缺点: 内存利用率比较低,只有50%

新生代中98%的对象都是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间,而是将内存(新生代内存)分为一块较大的Eden(伊甸园)空间和两块较小的Survivor(幸存者)空间,每次使用Eden和其中一块Survivor(两个Survivor区域一个称为From区,另一个称为To区域)。当回收时,将Eden和Survivor还存活的对象一次性复制到另一块Survivor空间上,最后清理掉Eden和刚才用到的Survivor空间
当Survivor空间不够用时,需要依赖其他内存(老年代)进行分配担保

HotSpot默认Eden与Survivor的大小比例是8:1,也就是说Eden:Survivor From:Survivor To=8:1:1。所以每次新生代可用内存空间为整个新生代容量的90%,而剩下的10%用来存放回收后存活的对象

HotSpot实现的复制算法流程如下:

  1. 当Eden区满的时候,会触发一次Minor gc,把还活着的对象拷贝到Survivor From区;当Eden区再次触发Minor gc时,会扫描Eden区和From区域,对两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域,并将Eden和From区域清空
  2. 当后续Eden又发生Minor gc的时候,会对Eden和To区域进行垃圾回收,存活的对象复制到From区域,并将Eden和To区域清空
  3. 部分对象会在From和To区域中复制来复制去,如此交换15次,最终如果还是存活,就存入到老年代

3. 标记-整理算法

复制收集算法在对象存活率较高时会进行比较多的复制操作,效率会变低。因此在老年代一般不能使用复制算法
针对老年代的特点,提出了一种“标记-整理算法”。标记过程中仍与“标记-清除”过程一致,但后续步骤不是直接对可回收对象进行清理,而是将存活对象移动到连续的空间,再清除剩余空间

优点:不会出现内存碎片的问题

4. 分代收集算法

分代算法和上面讲的3种算法不同,分代算法是通过区域划分,实现不同区域和不同的垃圾回收策略,从而实现更好的垃圾回收
当前JVM垃圾收集都采用的是“分代收集”算法,这个算法并没有新思想,只是根据对象存货周期的不同将内存划分为几块

在堆中,根据对象创建,及回收的特点,分为了两块区域:
(1)新生代:(对象朝生夕死:很快的创建,又很快变为不可用的垃圾)

方法调用:创建栈帧、入栈、
方法返回:出栈,里边局部变量指向的对象在方法返回就不可用
采取的算法:复制算法(复制算法(存活对象大多数情况下不多))
默认划分为E:S:S=8:1:1,空间利用率就是90%
默认每次使用E区和一块S区来保存对象,另一块S区留空
gc的时候,把存活对象复制到另一块留空的S区
可能存活对象需要的空间,S区还不够放:由老年代进行担保,如果放不下,就放到老年代

Eden(E区),2个Survivor(S区)

(2)老年代: 对象可能长期存活,对象存活率高,没有额外空间对它进行分配担保
采取的算法:标记清除算法,标记整理算法

对象什么时候进入老年代/新生代?

  1. 新创建的小对象默认放在新生代
  2. 大对象进入老年代
  3. 新生代gc,存活对象在S区放不下的,也会进入老年代
  4. 新生代的对象,每经历一次gc,年纪+1,如果年纪>15,就会进入老年代

新生代gc(Minor GC) vs 老年代GC(Major GC)

Minor GC: 对我们自己写的程序影响比较小,回收速度快
对程序有何影响?垃圾回收是在垃圾回收线程中并发执行,可能导致用户线程暂停(Stop the world,STW)
Major GC: 对程序影响大,回收速度慢

三、垃圾收集器

如果说上面我们讲的收集算法时内存回收的方法论,那么垃圾收集器就是内存回收的具体体现

垃圾收集器的作用:
垃圾收集器是为了保证内存能够正常,持久运行的一种技术,它是将程序中不用的死亡对象也就是垃圾对象进行清除,从而保证了新对象能够正常申请到内存对象

下面图中这些收集器是HotSpot虚拟机随着不同版本推出的重要的垃圾收集器:

上图展示了7种作用域不同分代的收集器,如果两个收集器之间存在连线,就说明他们之间可以搭配使用。所处的区域,表示它是属于新生代收集器还是老年代收集器。
在讲具体的收集器之前我们先来明确三个概念:

  • 并行: 指多条垃圾收集线程并行工作,用户线程处于等待状态
  • 并发: 指用户线程与垃圾收集线程同时执行(不一定并行,可能会交替执行),用户程序继续运行,而垃圾收集程序在另外一个CPU上
  • 吞吐量: 就是CPU用于运行用户代码的时间与CPU总消耗时间的比值

Java应用程序来说:大略上,可以分为2种类型:

  1. 用户体验优先(实时性要求比较高): 用户要使用的程序、系统

ParNew(新生代)+CMS(老年代)
G1
用户体验优先:每次的STW时间最少;总的STW时间可能变多

  1. 吞吐量优先(批处理任务): 用户不使用,只执行任务的

Parallel Scavenge+Parallel Old
吞吐量优先:总的STW(用户线程暂停)时间最少;单词的STW时间可能变多

ParNew和Parallel Scavenge都是新生代收集器,使用复制算法
Parallel Old是老年代收集器:标记-整理算法

1.CMS:老年代收集器

特性:

  1. CMS收集器是一种以获取最短回收停顿时间为目标的收集器。用户体验优先(并发收集,低停顿
  2. 采取“标记-清除”算法(存在这个算法的两个缺陷:效率不高,内存碎片)

针对效率不高:JVM实际对这个算法有进一步的优化

分为四个阶段:(名称,大概作用,特点(是否STW))

  • 初始标记:初始标记仅仅只是标记一下GC Roots,速度很快,需要“Stop The World”
  • 并发标记:并发标记阶段就是进行引用链搜索的过程
  • 重新标记:重新标记阶段是为了修正并发阶段期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短,仍然需要“Stop The World”
  • 并发清除:并发清除阶段会清除对象

第一个和第三个阶段需要停止用户线程
第二个和第四个阶段可以并发执行

由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的

CMS收集器优点: CMS是一款优秀的收集器,并发收集、低停顿
缺陷:

  1. 对cpu资源非常敏感:gc线程需要cpu资源,相对就需要更多cpu资源
  2. 无法处理浮动垃圾:当次gc时,处于第4个阶段,用户线程并发的创建的对象中,也包括垃圾对象(浮动垃圾);需要为浮动垃圾预留一部分空间;如果浮动垃圾产生太多,老年代剩余内存无法满足,会导致Concurrent Mode Falure,会触发另一次的老年代GC(使用Serial Old进行回收)
  3. 内存碎片问题:基于“标记-清除”算法实现的收集器,收集结束时会有大量空间碎片产生

2.G1收集器(唯一一款全区域的垃圾回收器)

G1垃圾回收器是用在heap memory很大的情况下,把heap划分为很多很多的region块,然后并行的对其进行垃圾回收
G1垃圾回收器在清除实例所占用的内存空间后,还会做内存压缩
G1垃圾回收器回收region的时候基本不会STW,而是基于most garbage优先回收
算法:整体看基于“标记-整理”算法,局部看是基于“复制”算法(将多个E区,多个S区,存活对象,复制到另一块空白的S区)

全堆收集器:用户体验优先
内存划分: 把堆划分为相等的很多个区域,每个区域根据需要设置为E(Eden)、S(Survivor)、T(Tenured老年区)。结果如下图:

一个region有可能属于Eden,Survivor或者Tenured内存区域。
图中的E表示该region属于Eden内存区域
S表示属于Survivor内存区域
T表示属于Tenured内存区域
图中空白的表示未使用的内存空间
G1垃圾收集器还增加了一种新的内存区域,叫做Humongous内存区域,如图中的H块。这种内存区域主要用于存储大对象,即大小超过一个region大小的50%的对象

年轻代垃圾收集:
在G1垃圾收集器中,年轻代的垃圾回收过程使用复制算法,把Eden区和Survivor区的对象复制到新的Survivor区域

老年代垃圾收集:
对于老年代上的垃圾收集,G1垃圾收集器也分为4个阶段,基本跟CMS垃圾收集器一样,但略有不同,也分为四个阶段:

  • 初始标记阶段:同CMS垃圾收集器的初始标记阶段一样,G1也需要暂停应用程序的执行,它会标记从根对象出发,在根对象的第一层孩子节点中标记所有可达的对象。但是G1的垃圾收集器的初始标记阶段是跟minor gc一同发生的。也就是说,在G1中,你不用像在CMS那样,单独暂停应用程序的执行来运行初始标记阶段,而是在G1触发minor gc的时候一并将老年代上的初始标记阶段给做了
  • 并发标记阶段:在这个阶段G1做的事情跟CMS一样。但G1同时还多做了一件事情,就是如果在并发标记阶段中,发现哪些老年代中对象的存活率很小或者基本没有对象存活,那么G1就会在这个阶段将其回收掉,而不用等到后面的clean up阶段。这也是Garbage First名字的由来。同时,在该阶段,G1会计算每个region的对象存活率,方便后面的clean up阶段使用

Garbage First:优先回收。老年代存活率低或没有存活的,就直接回收

  • 最终标记(CMS中的Remark阶段):在这个阶段G1做的事情跟CMS一样,但是采用的算法不同,G1采用一种叫做SATB的算法能够在Remark阶段更快的标记可达对象
  • 筛选回收阶段:在G1中,没有CMS中对应的Sweep阶段。相反,他有一个筛选回收阶段,在这个阶段中,G1会挑选出那些对象存活率低的region进行回收,这个阶段也是和minor gc一同发生的

G1是一款面向服务端应用的垃圾收集器。HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。如果应用追求低停顿,G1可以作为选择;如果应用追求吞吐量,G1并不带来特别明显的好处

四、JMM

JVM定义了一种Java内存模型(JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。 在此之前,C/C++直接使用物理硬件和操作系统的内存模型,因此,会由于不同平台下的内存模型的差异,有可能导致在一套平台上并发完全正常,而在另一套平台上并发访问经常出错

作用:不同硬件及操作系统,对内存的访问操作也不同,Java采取统一的内存模型来屏蔽以上的差异

1.如何划分

(1)工作内存: 线程私有的,cpu执行线程的指令时,使用寄存器来保存上下文
(2)主存: 线程共享的,Java进程的内存

Java内存模型的主要目标是定义程序中各个变量的访问规则,即在JVM中将变量存储到内存和从内存中取出变量这样的底层细节。此处的变量包含实例字段、静态字段和构成数组对象的元素,但不包括局部变量和方法参数,因为后者是线程私有的,不会被线程共享

Java内存模型规定了所有的变量都存储在主内存中。每条线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存进行,而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。线程、主内存、工作内存三者的交互关系如下所示:

Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为happens-before原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么他们就不能保证他们的有序性,虚拟机可以随意的对他们进行重排序
happens-before: JVM规定了多组线程,执行操作的时候,哪些操作必须先于哪些操作的规则

2.回顾volatile作用

关键字volatile可以说是JVM提供的最轻量级的同步机制,但是它并不容易完全被正确理解和使用。JVM内存模型对volatile专门定义了一些特殊的访问规则

不保证原子性,保证有序性和可见性: 隐藏含义:(操作本身是原子性,使用volatile就可以保证线程安全)

happens-before提供了volatile修饰的变量的规则
>volatile变量规则: 对一个变量的写操作先行发生于后面对这个变量的读操作
volatile变量的代码行,建立了一个内存屏障(禁止指令重排序)

垃圾回收相关知识(垃圾回收算法及垃圾收集器)相关推荐

  1. 深入理解java虚拟机之——垃圾回收(垃圾判断,垃圾收集算法,垃圾收集器)

    java与C++之间有一道动态内存分配和垃圾收集的"墙",墙里面的人想出来,墙外边的人想进去,或许只有骑在墙上才能清楚的了解,墙内墙外都是牢笼. 上面一句出自<深入理解jav ...

  2. Java常见GC算法_垃圾收集器及内存分配_G1垃圾收集器

    常见GC算法 引用计数法: 每个对象都有一个计数器, 对象被引用一次, 计数器+1, 当对象引用失败一次. 计数器-1, 当对象计数器等于0, 说明对象没有被应用, 就可GC 优: 运行过程中, 可随 ...

  3. Hotspot虚拟机- 垃圾收集算法和垃圾收集器

    引言 声明:由于CSDN不支持Hexo支持的Markdown语法,大家看到{% asset_img 1st.png Mark and Sweep %}这样的标签时,就是一张图片.由于图片较多,我就不一 ...

  4. JVM结构、内存分配、垃圾回收算法、垃圾收集器。

    2019独角兽企业重金招聘Python工程师标准>>> 一.JVM结构 根据<java虚拟机规范>规定,JVM的基本结构一般如下图所示: 从左图可知,JVM主要包括四个部 ...

  5. JVM 调优实战--常见的垃圾回收算法及垃圾收集器组合

    什么是垃圾 C语言申请内存:malloc free C++: new delete c/C++ 手动回收内存 Java: new ? 自动内存回收,编程上简单,系统不容易出错,手动释放内存,容易出两种 ...

  6. 谈谈GC垃圾回收—什么是垃圾,垃圾回收算法,垃圾收集器

    我们都知道,在JAVA虚拟机中进行垃圾回收的场所有2个:堆,方法区.那么,首先要弄清什么是垃圾呢?--没有任何引用指向的一个对象或者多个对象的叫做垃圾.其次,如何定位到垃圾? 主要使用两个方法: 1. ...

  7. 判别两棵树是否相等 设计算法_垃圾回收算法和垃圾收集器

    1.什么是垃圾回收 对于内存当中无用的对象进行回收,如何去判断一个对象是不是无用的对象. 引用计数法: 每个对象中都会存储一个引用计数,每增加一个引用就+1,消失一个引用就-1.当引用计数器为0时就会 ...

  8. JVM垃圾回收机制总结(3) :按代垃圾收集器

    全文转载:http://pengjiaheng.iteye.com/blog/524024 作者:和你在一起 [from JavaEye] 为什么要分代 分代的垃圾回收策略,是基于这样一个事实:不同的 ...

  9. jvm相关,垃圾收集算法,垃圾收集器,jvm调优--学习笔记

    对内存合理分配,优化jvm 参数,就是为了尽可能减少新生代(Minor GC),或者是整个老年代(Major GC) ,或者是整个 Java 堆 (Full GC) ,尽量减少 GC 带来的系统停顿, ...

最新文章

  1. Visual Studio 2017新版发布,极大提高开发效率丨附下载
  2. MOOON-server新消息处理接口
  3. R语言:再谈REmap包
  4. 【Git】pull遇到错误:error: Your local changes to the following files would be overwritten by merge:
  5. [BZOJ2725/Violet 6]故乡的梦
  6. VSS2005 上传pdf 空白
  7. mysql max嵌套select_使用嵌套select子式 解决mysql不能叠加使用如max(sum())的问题
  8. 值得使用的CSS库添加图像悬停效果!
  9. HDU1272 小希的迷宫【并查集】
  10. 关于流媒体压缩的问题xvidcore的问题
  11. phpwind9.0 read.php 修改,phpwind 9.0后台执行任意php代码
  12. 将图片从AutoCAD导出到Word
  13. 润迈德医疗上市首日跌15%:年亏6.3亿 平安资本是股东
  14. Win10任务栏图标变成空白方块解决办法
  15. printf打印二进制数据
  16. CCNA 初学(第一课)
  17. phpstudy打不开localhost
  18. 爬虫实战——起点中文网小说的爬取
  19. leetcode.1402做菜顺序
  20. 手淘搜索流量下降时如何提升?

热门文章

  1. 1. 我的自学编程之路
  2. Hi3861鸿蒙物联网项目实战:智慧农业
  3. 墙裂推荐|7款运营工具,能帮你提升90%的效率,高效完成KPI任务
  4. InkSpace制作简易logo
  5. [嘭嘭养成记]2. 在运行时将完整动作分割成局部动作
  6. LOL火爆原因分析~心理方面
  7. 容联云通讯的学习笔记二
  8. NC65 sql server 查询人员薪资发放 数据
  9. 要做中国的GE,富士康工业互联网做得怎么样?
  10. 七牛云免费对象存储,并绑定到cloudreve中