答应我,下次再被问到垃圾回收器,别再支支吾吾了!

x33g5p2x  于2021-11-22 转载在 其他  
字(3.5k)|赞(0)|评价(0)|浏览(294)

首先,JVM全称:Java Virtual Machine,也就是我们常说的Java虚拟机,通过在实际的计算机上仿真模拟各种计算机功能来实现的,简单来说,JVM就是用来解析和运行Java程序的。

简单地介绍完JVM以后,接下来我们来看看JVM的内存结构模型:

以上就是JVM的内存结构模型了,那么图中的区块又是什么意思呢?

方法区: 用于存储虚拟机加载的类信息,常量,静态变量等数据。

堆: 存放对象的实例,所有的对象和数组都要在堆上分配,是JVM锁管理的内存中最大的一块区域!

栈: Java方法锁执行的内存模型,存储局部变量表,操作数栈,动态链接,方法出口等信息。生命周期和线程相同。

本地方法栈: 作用与虚拟机类似,不同点在于本地方法栈为native方法执行服务,虚拟机栈为虚拟机执行的Java方法服务。

程序计数器: 为当前线程所执行的行号指示器。是JVM内存区域最小的一块区域。执行字节码工作时就是利用程序计数器来选取下一个需要执行的字节码命令。

其次,当我们在编写程序的时候,对内存的处理极其重要,忘记或错误的内存回收会导致程序或系统的不稳定甚至崩溃掉,而JAVA提供的GC(垃圾回收机制,GabageCollection)则可以自动检测对象是否超过作用域从而达到自动回收内存的目的。那么接下来,我们就来说说Java的垃圾回收机制。

Java垃圾回收机制:

虽然Java中,程序员是不需要显示地去释放一个对象地内存,而是由虚拟机去执行,但是其中的原理你必须得知道!

在JVM中,有一个垃圾回收的线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前内存不足的时候,才会去触发这个垃圾回收线程执行,扫描那些没有被任何地方引用的对象,并将他们添加到要回收的集合中,进行回收。

垃圾回收的优点和原理:

由于Java中有垃圾回收机制,所以Java中的对象将不在有作用域这一说法,只有对象的引用才有作用域,垃圾回收可以有效地防止内存泄漏,有效地使用内存。垃圾回收器通常是作为一个单独地低级别地线程运行,不可预知地情况下对内存堆中已经死亡地或者长时间没有使用的对象进行清除和回收。

而对于垃圾回收器来说,我们每创建一个对象的时候,GC就开始监控这个对象的地址、大小以及它的使用情况。通常,GC采用有向图的方式来记录和管理堆中的所有对象。通过这种方式来确定哪些对象是可达的,哪些对象是不可达的,当GC确定某些对象为不可达的时候,GC就回收这些内存对象了。

那么JVM是怎么判断一个对象是否存活的呢?

①引用计数法:
所谓引用计数法,则是给每个对象设置一个引用记数器,每当有一个地方引用这个对象的时候,就将计数器加一,引用失效的时候,就将计数器减一。当一个对象的引用技术器为0时,说明此对象没有被引用,也就是死对象,将会被垃圾回收。
但是这个方法有一个缺陷就是无法解决循环引用的问题,也就是说当对象A引用对象B,对象B又引用对象A的情况下,此时AB的引用计数器都不为0,也就是无法完成垃圾回收,所以主流的虚拟机都没有采用这种算法。
②可达性算法(引用链法)
从一个被称之为GC Roots 的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。
而在Java中可以作为GC Roots的对象有以下几种:
一、虚拟机引用的对象
二、方法区类静态属性的引用对象
三、方法区常量池引用的对象
四、本地方法栈JNI引用的对象
虽然这些方法都不能判定一个对象能否被回收,但是当满足上述条件的时候,一个对象还不一定会被回收。当一个对象不可达GC Root时,这个对象并不会立马被回收,而是处于一个死缓的阶段,若要真正地被回收需要经历两次标记。

当然,我们可以手动执行System.gc(),来通知GC运行,但是Java语言规范并不会保证GC是一定会执行的。

铺垫了这么多,接下来就说说今天的主角,JVM中的垃圾回收器

新生代垃圾收集器:

Serial收集器: Serial收集器只能使用一条线程进行垃圾收集的工作,并且在进行垃圾收集的时候,所有的线程都要停止工作,等待垃圾收集线程完成以后,其他的线程才可以继续工作。它所使用的算法是复制算法。

ParNew收集器: parNew收集器是Serial收集器的多线程版本,为了利用CPU多线程的优势,ParNew收集器可以运行多个线程来进行垃圾收集工作,这样来提高垃圾收集过程的效率。同样也是使用的复制算法。

Parallel Scavenge收集器 是一款多线程的垃圾收集器,但是它有何ParNew有很大的不同点。Parallel Scavenge收集器和其他收集器的关注点不同,像ParNew收集器关注的是如何缩短垃圾收集的时间,而Parallel Scavenge收集器关注的则是如何控制系统运行的吞吐量。这里的吞吐量指的就是CPU用于运行应用程序总时间的占比,即吞吐量=代码运行时间/(代码运行时间+垃圾回收时间)。如果虚拟机运行的总的CPU的时间是100分钟,而用于垃圾回收的时间为1分钟,那么吞吐量就是99%。同样也是使用的复制算法。

老年代垃圾收集器:

Serial Old收集器: Serial Old收集器是Serial收集器的老年代版本。这款收集器主要用于客户端应用程序中作为老年代的垃圾收集器,也可以作为服务端应用程序的垃圾收集器。使用算法是标记-整理。

Parallel Old收集器: Parallel Old收集器是Parallel Scavenge收集器的老年代版本。这个收集器实在JDK1.6版本中出现的,所以在JDK1.6之前,新生代的Parallel Scavenge只能和Serial Old这款单线程的老年代收集器配合使用。它和Parallel Scavenge收集器一样,也是一款专注吞吐量的垃圾收集器,和Parallel Scavenge配合使用,可以实现对Java堆内存的吞吐量优先的垃圾收集策略。使用的算法是标记-整理。

CMS收集器: CMS是目前老年代收集器中比较出色的垃圾收收集器,CMS全称:Concurrent Mark Sweep,是一款使用 标记-清除 算法的并发收集器。CMS垃圾收集器是一款以获取最短停顿时间为目标的垃圾收集器,CMS工作的时候,可以分为4个阶段:①初始标记阶段(initial mark);②并发标记阶段(concurrrnt mark);③重新标记阶段(remark);④并发清除阶段(concurrent sweep)。

还有一个收集器叫G1的垃圾收集器,主要是通过初始标记-并发标记-重新标记-复制清除的步骤工作,所使用的算法是复制+标记整理。

接下来就要说说Java中垃圾收集的算法了:

标记 - 清除: 这个是垃圾收集算法中最最基础的,根据名字就可以知道,就是根据标记哪些是要被回收的对象,然后同一回收。这种方法很简单,但是它的效率不高,标记和清除的效率也都很低;其次就是会产生大量不连续的内存碎片,导致了以后程序在分配较大的对象时,由于设有充足的连续内存而提前触发一次GC动作。

复制算法: 上面所提到的新生代JVM垃圾回收器都是用的这个算法。简单来说就是为了解决效率问题,复制算法将可用的内存按照容量进行划分,划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上去,然后一次性清除完第一块内存,再将第二块的对象复制到第一块内存上去。
但是这种方法的代价太高,基本每次都会浪费掉一半的内存,于是将这个算法进行了改进,内存大小不在是按照1:1进行分配,而是按照8:1:1三部分进行分配,较大的内存交给Eden区,其余的内存区叫Survior区。每次都会优先使用Eden区,若Eden区满了,就将对象复制到第二块内存区域,然后清除Eden区,如果此时存活的对象太多,以至于Survior不够时,会将这些对象通过分配担保的机制复制到老年代中。(堆分为新生代和老年代)

标记 - 整理: 这个算法主要是为了解决标记-清除所产生的大量内存碎片的问题;当内存的存活率较高的时候,也解决了复制算法效率上的问题。它的不同之处就是在清除对象的时候将可回收对象移动到一段,然后清除掉便捷意外的对象,这样就不会产生内存碎片了。

分代收集: 现在的虚拟机垃圾收集大多都是采用这种方式,它根据对象的生存周期,将堆分为新生代和来年代。在新生代中,由于对象生存期短暂,每次回收都会有大量的对象死去,那么这是就采用复制算法。老年代中对象的存活率是比较高的,没有额外的空间进行分配担保。

这些是博主学习过程中整理出来的皮毛,若要更深层次地了解可进一步访问度娘!

今天地分享就到这里,欢迎大家一起交流学习!

相关文章