垃圾回收与算法

2.1如何确定垃圾

在接触垃圾回收机制和算术法之前,先了解下引用计数法的概念和可达性分析,是对理解垃圾回收算法的基础。
2.1.1:引用计数法的概念
在java中,引用和对象是关联的,如果要操作对象必须用引用进行,因此,很显然的一个简单办法就是通过引用计数来判断一个对象是否回收。简单说,即一个对象如果没有任何与之关联的引用,即他们的引用计数都为0,则说明这些对象可能不会被用到,那么这个对象就是可回收垃圾。
注意: 为了解决引用计数法的循环引用问题,java使用了可达性分析的方法,通过一系列的"GC roots" 对象作为起点搜索。 如果在"GC roots" 和对象之间没有可达路径,则称该对象是不可达的。
特别要注意的是,不可达对象不等同与可回收对象,不可达对象变为可回收对象至少经过两次标记过程,两次标记后仍然是可回收对象,将面临回收。

2.2 垃圾回收算法

2.2.1:标记清除算法
最基础的垃圾回收算法,分为两个阶段,标记和清除。标记阶段标出所需要回收的对象,清除阶段回收被标记的对象的空间。 如下图所示:

从上图可以看出,该算法最大的问题就是内存碎片化严重,后续可能发生大对象不能找到可利用的空间的问题。
2.2.2:复制算法
为了解决Mark-Sweep(标记 清除)算法内存碎片化的缺陷而被提出的算法,按内存容量将内存划分为等大小的两块,每次只使用其中的一块,当这一块内存满后将尚存活的对象复制到另一块去,把已使用的内存清除。如下图。

这种方法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压到了原来的一半。且存活对象增多的话,Copying 算法的效率会大大降低。

2.2.3:标记整理算法

结合上面两个算法,为了避免缺陷而提出。标记阶段和Mark-Sweep 算法相同,标记后不是清除对象,而是将存活对象移向内存的一端,然后清除端边界外的对象。如图

2.2.4分代收集算法
分代收集算法是目前大部分JVM采用的方法,其核心思想是根据对象存活的不同周期将内存划分为不同的域,一般情况下将GC堆划分为老生代和新生代。老生代的特点是垃圾回收时只有 少量对象被回收。新生代的特点就是垃圾回收是每次都有大量的垃圾需要被回收,因此根据不同区域选择不同的算法。

2…2.5:新生代与复制算法
目前大多数的JVM的GC对于新生代都采用Coyping 算法,因为新生代每次垃圾回收都要回收大量对象,即复制的操作比较少,但通常并不是按照1:1来划分新生代。一般是将新生代化成一块较大的Eden空间和两个较小的Survivor,每次使用Eden空间的其中一块和Survivor空间,当进行回收时,将该两块空间中还存活的对象复制到另外一块Survivor中。

2.2.6:老年代标记复制算法
而老年代因为每次回收的少量对象,因而采用,Mark - Compact
1:JVM虚拟机提到过的处于方法区的永久代,它采用储存class类,常量,方法描述等。对永久代的回收包括废弃常量和无用的类。
2:对象的内存分配主要在新生代的Eden space 和Survivor space 的Form Space (Survivor 目前存放对象的那一块),少数情况会直接分配到老生代。
3:当新生代的Eden Space 和 From Space 空间不足时,会发生一次gc,进行gc后,Eden Space 和From Space 区的存活对象会被挪到To Space,然后将Eden Space 和 From Space 进行清理。
4:如果To Space 无法足够存储某个对象,则将这个对象存储到老生代。
5:在进行GC后,使用的便是Eden Space 和To Space ,如此反复循环。
6:当对象在Survivor区躲过一次gc后,其年龄就会+1。默认情况下年龄到达15 会被移到老年代。

2.3.JAVA 四中引用类型

2.3.1. 强引用
在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引
用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即
使该对象以后永远都不会被用到 JVM 也不会回收。因此强引用是造成 Java 内存泄漏的主要原因之
一。

2.5.2. 软引用
软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它
不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。

2.3.3. 弱引用
弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象
来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存。

2.3.4. 虚引用
虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚
引用的主要作用是跟踪对象被垃圾回收的状态。

2.4.GC 分代收集算法 VS 分区收集算法

2.4.1 分代收集算法
当前主流 VM 垃圾收集都采用”分代收集”(Generational Collection)算法, 这种算法会根据
对象存活周期的不同将内存划分为几块, 如 JVM 中的 新生代、老年代、永久代,这样就可以根据
各年代特点分别采用最适当的 GC 算法

2.4.1.1. 在新生代-复制算法
每次垃圾收集都能发现大批对象已死, 只有少量存活. 因此选用复制算法, 只需要付出少量
存活对象的复制成本就可以完成收集.

2.4.1.2. 在老年代-标记整理算法
因为对象存活率高、没有额外空间对它进行分配担保, 就必须采用“标记—清理”或“标
记—整理”算法来进行回收, 不必进行内存复制, 且直接腾出空闲内存

2.6.2. 分区收集算法
分区算法则将整个堆空间划分为连续的不同小区间, 每个小区间独立使用, 独立回收. 这样做的
好处是可以控制一次回收多少个小区间 , 根据目标停顿时间, 每次合理地回收若干个小区间(而不是
整个堆), 从而减少一次 GC 所产生的停顿。
2.7.GC 垃圾收集器
Java 堆内存被划分为新生代和年老代两部分,新生代主要使用复制和标记-清除垃圾回收算法;
年老代主要使用标记-整理垃圾回收算法,因此 java 虚拟中针对新生代和年老代分别提供了多种不
同的垃圾收集器,JDK1.6 中 Sun HotSpot 虚拟机的垃圾收集器如下:

2.7.1. Serial 垃圾收集器(单线程、复制算法)
Serial(英文连续)是最基本垃圾收集器,使用复制算法,曾经是JDK1.3.1 之前新生代唯一的垃圾
收集器。Serial 是一个单线程的收集器,它不但只会使用一个 CPU 或一条线程去完成垃圾收集工
作,并且在进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。

Serial 垃圾收集器虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限
定单个 CPU 环境来说,没有线程交互的开销,可以获得最高的单线程垃圾收集效率,因此 Serial
垃圾收集器依然是 java 虚拟机运行在 Client 模式下默认的新生代垃圾收集器。

2.7.2. ParNew 垃圾收集器(Serial+多线程)
ParNew 垃圾收集器其实是 Serial 收集器的多线程版本,也使用复制算法,除了使用多线程进行垃
圾收集之外,其余的行为和 Serial 收集器完全一样,ParNew 垃圾收集器在垃圾收集过程中同样也
要暂停所有其他的工作线程。

2.6.2. 分区收集算法
分区算法则将整个堆空间划分为连续的不同小区间, 每个小区间独立使用, 独立回收. 这样做的
好处是可以控制一次回收多少个小区间 , 根据目标停顿时间, 每次合理地回收若干个小区间(而不是
整个堆), 从而减少一次 GC 所产生的停顿。

2.7.GC 垃圾收集器
Java 堆内存被划分为新生代和年老代两部分,新生代主要使用复制和标记-清除垃圾回收算法;
年老代主要使用标记-整理垃圾回收算法,因此 java 虚拟中针对新生代和年老代分别提供了多种不
同的垃圾收集器,JDK1.6 中 Sun HotSpot 虚拟机的垃圾收集器如下:

2.7.1. Serial 垃圾收集器(单线程、复制算法)
Serial(英文连续)是最基本垃圾收集器,使用复制算法,曾经是JDK1.3.1 之前新生代唯一的垃圾
收集器。Serial 是一个单线程的收集器,它不但只会使用一个 CPU 或一条线程去完成垃圾收集工
作,并且在进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。
Serial 垃圾收集器虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限
定单个 CPU 环境来说,没有线程交互的开销,可以获得最高的单线程垃圾收集效率,因此 Serial
垃圾收集器依然是 java 虚拟机运行在 Client 模式下默认的新生代垃圾收集器。

2.7.2. ParNew 垃圾收集器(Serial+多线程)
ParNew 垃圾收集器其实是 Serial 收集器的多线程版本,也使用复制算法,除了使用多线程进行垃
圾收集之外,其余的行为和 Serial 收集器完全一样,ParNew 垃圾收集器在垃圾收集过程中同样也
要暂停所有其他的工作线程。

ParNew 收集器默认开启和 CPU 数目相同的线程数,可以通过-XX:ParallelGCThreads 参数来限
制垃圾收集器的线程数。【Parallel:平行的】
ParNew虽然是除了多线程外和Serial 收集器几乎完全一样,但是ParNew垃圾收集器是很多 java
虚拟机运行在 Server 模式下新生代的默认垃圾收集器。

2.7.3. Parallel Scavenge 收集器(多线程复制算法、高效)
Parallel Scavenge 收集器也是一个新生代垃圾收集器,同样使用复制算法,也是一个多线程的垃
圾收集器,它重点关注的是程序达到一个可控制的吞吐量(Thoughput,CPU 用于运行用户代码
的时间/CPU 总消耗时间,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),
高吞吐量可以最高效率地利用 CPU 时间,尽快地完成程序的运算任务,主要适用于在后台运算而
不需要太多交互的任务。自适应调节策略也是 ParallelScavenge 收集器与 ParNew 收集器的一个
重要区别。
2.7.4. Serial Old 收集器(单线程标记整理算法
Serial Old 是 Serial 垃圾收集器年老代版本,它同样是个单线程的收集器,使用标记-整理算法,
这个收集器也主要是运行在 Client 默认的 java 虚拟机默认的年老代垃圾收集器。 在 Server 模式下,主要有两个用途:

  1. 在 JDK1.5 之前版本中与新生代的 Parallel Scavenge 收集器搭配使用。
  2. 作为年老代中使用 CMS 收集器的后备垃圾收集方案。
    新生代 Serial 与年老代 Serial Old 搭配垃圾收集过程图:

    新生代 Parallel Scavenge 收集器与 ParNew 收集器工作原理类似,都是多线程的收集器,都使
    用的是复制算法,在垃圾收集过程中都需要暂停所有的工作线程。新生代 Parallel
    Scavenge/ParNew 与年老代 Serial Old 搭配垃圾收集过程图:

    2.7.5. Parallel Old 收集器(多线程标记整理算法)
    Parallel Old 收集器是Parallel Scavenge的年老代版本,使用多线程的标记-整理算法,在 JDK1.6
    才开始提供。
    在 JDK1.6 之前,新生代使用 ParallelScavenge 收集器只能搭配年老代的 Serial Old 收集器,只
    能保证新生代的吞吐量优先,无法保证整体的吞吐量,Parallel Old 正是为了在年老代同样提供吞
    吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,可以优先考虑新生代 Parallel Scavenge
    和年老代 Parallel Old 收集器的搭配策略。
    新生代 Parallel Scavenge 和年老代 Parallel Old 收集器搭配运行过程图:

    2.7.6. CMS 收集器(多线程标记清除算法)
    Concurrent mark sweep(CMS)收集器是一种年老代垃圾收集器,其最主要目标是获取最短垃圾
    回收停顿时间,和其他年老代使用标记-整理算法不同,它使用多线程的标记-清除算法。
    最短的垃圾收集停顿时间可以为交互比较高的程序提高用户体验。
    CMS 工作机制相比其他的垃圾收集器来说更复杂,整个过程分为以下 4 个阶段:
    2.7.6.1. 初始标记
    只是标记一下 GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。
    2.7.6.2. 并发标记
    进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。
    2.7.6.3. 重新标记
    为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记
    记录,仍然需要暂停所有的工作线程。
    2.7.6.4. 并发清除
    清除 GC Roots 不可达对象,和用户线程一起工作,不需要暂停工作线程。由于耗时最长的并
    发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作,所以总体上来看
    CMS 收集器的内存回收和用户线程是一起并发地执行。
    CMS 收集器工作过程:

    2.7.7. G1 收集器
    Garbage first 垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与 CMS 收集器,G1 收
    集器两个最突出的改进是:
  3. 基于标记-整理算法,不产生内存碎片。
  4. 可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。
    G1 收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域
    的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾
    最多的区域。区域划分和优先级区域回收机制,确保 G1 收集器可以在有限时间获得最高的垃圾收
    集效率。

2.8. JAVA IO/NIO

2.8.1. 阻塞 IO 模型
最传统的一种 IO 模型,即在读写数据过程中会发生阻塞现象。当用户线程发出 IO 请求之后,内
核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用
户线程交出 CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用
13/04/2018 Page 35 of 283
户线程才解除 block 状态。典型的阻塞 IO 模型的例子为:data = socket.read();如果数据没有就
绪,就会一直阻塞在 read 方法。
2.8.2. 非阻塞 IO 模型
当用户线程发起一个 read 操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个
error 时,它就知道数据还没有准备好,于是它可以再次发送 read 操作。一旦内核中的数据准备
好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。
所以事实上,在非阻塞 IO 模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞 IO
不会交出 CPU,而会一直占用 CPU。典型的非阻塞 IO 模型一般如下:

while(true){data = socket.read();
if(data!= error){处理数据
break;
}}

但是对于非阻塞 IO 就有一个非常严重的问题,在 while 循环中需要不断地去询问内核数据是否就
绪,这样会导致 CPU 占用率非常高,因此一般情况下很少使用 while 循环这种方式来读取数据。
2.8.3. 多路复用 IO 模型
多路复用 IO 模型是目前使用得比较多的模型。Java NIO 实际上就是多路复用 IO。在多路复用 IO
模型中,会有一个线程不断去轮询多个 socket 的状态,只有当 socket 真正有读写事件时,才真
正调用实际的 IO 读写操作。因为在多路复用 IO 模型中,只需要使用一个线程就可以管理多个
socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有
socket 读写事件进行时,才会使用 IO 资源,所以它大大减少了资源占用。在 Java NIO 中,是通
过 selector.select()去查询每个通道是否有到达事件,如果没有事件,则一直阻塞在那里,因此这
种方式会导致用户线程的阻塞。多路复用 IO 模式,通过一个线程就可以管理多个 socket,只有当
socket 真正有读写事件发生才会占用资源来进行实际的读写操作。因此,多路复用 IO 比较适合连
接数比较多的情况。

 另外多路复用 IO 为何比非阻塞 IO 模型的效率高是因为在非阻塞 IO 中,不断地询问 socket 状态

时通过用户线程去进行的,而在多路复用 IO 中,轮询每个 socket 状态是内核在进行的,这个效
率要比用户线程要高的多。
不过要注意的是,多路复用 IO 模型是通过轮询的方式来检测是否有事件到达,并且对到达的事件
逐一进行响应。因此对于多路复用 IO 模型来说,一旦事件响应体很大,那么就会导致后续的事件
迟迟得不到处理,并且会影响新的事件轮询。
13/04/2018 Page 36 of 283
2.8.4. 信号驱动 IO 模型
在信号驱动 IO 模型中,当用户线程发起一个 IO 请求操作,会给对应的 socket 注册一个信号函
数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到
信号之后,便在信号函数中调用 IO 读写操作来进行实际的 IO 请求操作。
2.8.5. 异步 IO 模型
异步 IO 模型才是最理想的 IO 模型,在异步 IO 模型中,当用户线程发起 read 操作之后,立刻就
可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个 asynchronous read 之后,
它会立刻返回,说明 read 请求已经成功发起了,因此不会对用户线程产生任何 block。然后,内
核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程
发送一个信号,告诉它 read 操作完成了。也就说用户线程完全不需要实际的整个 IO 操作是如何
进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示 IO 操作已经完成,可以直接
去使用数据了。
也就说在异步 IO 模型中,IO 操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完
成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用 IO 函数进行具体的
读写。这点是和信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据
已经就绪,然后需要用户线程调用 IO 函数进行实际的读写操作;而在异步 IO 模型中,收到信号
表示 IO 操作已经完成,不需要再在用户线程中调用 IO 函数进行实际的读写操作。

JVM垃圾回收与算法(2)相关推荐

  1. 垃圾回收机制和JVM垃圾回收常见算法

    垃圾回收机制和JVM垃圾回收常见算法 垃圾回收的好处和特点: 好处: 1. 提高编程效率: 2. 垃圾回收机制保护程序的完整性. 特点: 1. 只能回收无用对象的内存空间,对其他物理资源无能为力: 2 ...

  2. JVM—垃圾回收与算法

    目录 一.如何确定垃圾 1.引用计数法 2.可达性分析 二.标记清除算法(Mark-Sweep) 三.复制算法(copying) 四.标记整理算法(Mark-Compact) 五.分代收集算法 1.新 ...

  3. JVM - 垃圾回收相关算法

    目录 总览 垃圾标记阶段:对象存活判断 引用计数算法 可达性分析算法(根搜索算法.追踪性垃圾收集) 对象的 finalization 机制 MAT 与 JProfiler 查看 GC Roots 垃圾 ...

  4. java 虚拟机 Java内存结构 JVM垃圾回收机制算法

    什么是HotSpot VM 提起HotSpot VM,相信所有Java程序员都知道,它是Sun JDK和OpenJDK中所带的虚拟机,也是目前使用范围最广的Java虚拟机. 但不一定所有人都知道的是, ...

  5. JVM—垃圾回收GC算法

    1 GC算法简介 算法 特点 标记-清除 分为"标记"和"清除"两个阶段 复制 可以解决效率问题,将可用的内存按容量划分为大小相等的两块. 标记-整理 先标记. ...

  6. JVM垃圾回收算法 总结及汇总

    先看一眼JVM虚拟机运行时的内存模型: 1.方法区 Perm(永久代.非堆) 2.虚拟机栈 3.本地方法栈 (Native方法) 4.堆 5.程序计数器 1 首先的问题是:jvm如何知道那些对象需要回 ...

  7. JVM中垃圾回收相关算法 - 值得了解一下的,因为早晚得了解

    JVM中垃圾回收相关算法 - 我想是值得你了解一下的,因为早晚得了解.

  8. JVM 垃圾回收算法 -可达性分析算法!!!高频面试!!!

    前言:学习JVM,那么不可避免的要去了解JVM相关的垃圾回收算法,本文只是讲了讲了可达性分析算法,至于标记-清除.标记-复制,标记-整理,分代收集等等算法,会在近两天的文章中陆续更新出来. 很喜欢一句 ...

  9. JVM学习笔记之-拉圾回收概述,垃圾回收相关算法

    拉圾回收概述 什么是垃圾 垃圾收集,不是Java语言的伴生产物.早在1960年,第一门开始使用内存动态分配和垃圾收集技术的Lisp语言诞生. 关于垃圾收集有三个经典问题: 哪些内存需要回收? 什么时候 ...

最新文章

  1. Java Bad version
  2. java如何访问局域网共享文件
  3. Select的OnChange()事件
  4. Python_sqlalchemy之多对多建表
  5. awgn信道中的噪声功率谱密度_从OFC2020看高级算法在光通信中的应用
  6. django中的ajax_post请求
  7. 什么是服务器的SSL PSE
  8. JavaScript对SEO的影响及解决之道
  9. xmpp 常见错误 一
  10. 电力拖动自动控制系统_系主任带你看专业 | 电气工程及其自动化、电子科学与技术、信息工程、自动化,优质就业、超高考研、竞赛获奖都在这里……...
  11. 模板类出现外部符号无法解析错误
  12. 大数据的普及催生医疗信息技术市场蓝海
  13. [译]反射(Reflection)和动态(dynamic)
  14. redis的key与value乱码问题
  15. spring-IOC注解部分笔记整理(观看IT黑马视频自学)
  16. python 实现抖音视频无水印解析
  17. 树莓派书籍全方位推荐
  18. 提示Could not calculate build plan Plugin org.apache.maven.pluginsmaven-resources
  19. 使用smtp协议发送邮件
  20. 基于互联云及多云的云化基础设施算力调度

热门文章

  1. 关爱胆囊,请戒掉这些饮食陋习
  2. UI设计培训费用多少钱?贵不贵?
  3. 服装加盟系统选对了,服装连锁店的管理问题少一半!
  4. 减小VMware虚拟机虚拟磁盘大小
  5. android推送设备id,技术解读:极光推送的设备唯一性标识 RegistrationID
  6. 谁在做中国最赚钱的网站
  7. 【2023校招】厦门极致互动研发岗笔试AK
  8. signature=8b78efd08a83109cc3e573938dab113b,YOUR SIGNATURE DELI.
  9. 7-3 两个有序链表序列的合并 (15 分)
  10. Docker系列之五:Volume 卷的使用——以Redis为例