专业的JAVA编程教程与资源

网站首页 > java教程 正文

java垃圾回收机制(java垃圾回收机制的原理是什么)

temp10 2024-11-08 13:52:56 java教程 8 ℃ 0 评论

一、垃圾回收机制介绍

程序的运行需要依赖内存用来保存代码本身、程序运行过程中申明的变量或创建的对象等。俗话说“有借有还再借不难”,程序运行过程中找内存申请了内存,用完之后就得归还,如果不还的话,那随着程序的运行,内存将会耗尽,该程序将无法正常使用。C、C++语言需要在程序里显示的回收内存,如果C或C++代码某处未进行内存回收,那随着该处代码运行次数越来越多,内存会耗尽,出现内存溢出的错误。而java语言不需要再在程序里显示的去回收,JVM会定时去检查触发垃圾回收的条件是否满足,以及程序运行过程中满足某些条件,就会触发垃圾回收,JVM对垃圾自动标记和回收。

java垃圾回收机制(java垃圾回收机制的原理是什么)

二、垃圾在哪里

JVM内存结构图如下:

栈、本地方法栈:存放基本类型的变量数据和对象的引用,区别在于执行的方法是否为Native方法服务,如果执行的方法为本地方法,比如:System.currentTimeMilis(),那执行过程中申明的基本类型数据和对象的引用则存放到本地方法栈,栈中保存的变量或引用的生命周期随着方法执行完成而销毁。

程序计数器:用来存放执行指令的偏移量和行号指示器等。

元空间:存放类元信息、字段、静态属性、方法、常量等,当加载的类越来越多,元空间内存不足时会发生GC。

:存放java实例对象,是GC作用的主要区域。

三、什么是垃圾

不再使用的对象即垃圾,使用可达性分析算法来确定垃圾对象。可达性分析算法的基本思路,通过一些被称为引用链(GC Root)的对象作为起点,从这些节点依次向下搜索,搜索走过的路径被称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链时(即从GC Root节点到该节点不可达),则证明该对象是需要回收的垃圾对象。

在java语言中,可以作为GC Root的对象包括:

1、元空间中类静态属性引用的对象

2、元空间中常量引用的对象

2、栈中引用的对象

3、本地方法栈中引用的对象

四、垃圾怎么回收

由于Java虚拟机规范并没有对如何实现垃圾收集器做出明确的规定,因此各个厂商的虚拟机可以采用不同的方式来实现垃圾收集器,这里我们讨论几种常见的垃圾收集算法的核心思想。

1、标记-清除算法

标记清除算法(Mark-Sweep)是最基础的一种垃圾回收算法,它分为2部分,先把内存中的这些对象进行标记,哪些属于可回收的标记出来,然后把这些垃圾清理掉。如图所示,清理掉的垃圾就变成内存未使用的区域,等待被再次使用。此种算法存在一个很大问题,那就是内存碎片。假设上图中稍小的方块内存大小为1M,较大的为4M,中等的为2M。垃圾回收完之后内存就被划分为很多段,我们知道开辟内存空间时,需要一段连续的内存,假设现在需要一个3M的内存空间,那一个1M和一个2M的内存区域就无法使用。这样就导致了,明明内存还有很多,但却用不了。

2、复制算法

复制算法(Copying)是在标记清除算法上演化而来,用来解决标记清除算法内存碎片化问题。它将内存分成两个相等大小的块,每次只激活其中的一块,当垃圾回收时只需把存活的对象复制到另一块未激活空间上,将未激活空间标记为已激活,将已激活空间标记为未激活,然后清除原空间中的原对象。该算法解决了内存碎片的问题,但是内存利用率只有实际可用内存的一半。

3、标记-整理算法

标记整理算法(Mark-Compact)标记过程仍然和标记清除算法一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象向一端移动,再清理掉端边界以外的内存区域。标记整理算法一方面在标记-清除算法上做了升级,解决了内存碎片问题,也规避了复制算法只能利用一半内存区域的弊端。看起来很美好,但是从上图可以看到,它对内存变动更频繁,需要整理所有存活对象的引用地址,在效率上比复制要差很多。

目前JVM采用的都是分代收集算法,分代收集算法不是一种真正的算法,一般是把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记-清理或者标记 --- 整理算法来进行回收。所以,分代收集算法指的是对堆内存划分的不同区域使用不同的回收算法,具体是哪几种的算法组合,根据JDK版本的不同而不同,也可以手动指定垃圾回收器的组合,垃圾收集器确定了具体算法。

五、何时触发

1、Eden区空间不足时会触发YGC

2、Old区空间不足时会触发FGC

3、元空间不足时会触发FGC

4、定时任务检查到满足触发GC的条件成立

六、常用垃圾回收器

1、图中展示了7种不同分代的收集器:

Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1;

2、而它们所处区域,则表明其是属于新生代收集器还是老年代收集器:

新生代收集器:Serial、ParNew、Parallel Scavenge;

老年代收集器:Serial Old、Parallel Old、CMS;

整堆收集器:G1;

3、两个收集器间有连线,表明它们可以搭配使用:

Serial/Serial Old、Serial/CMS、ParNew/Serial Old、ParNew/CMS、Parallel Scavenge/Serial Old、Parallel Scavenge/Parallel Old、G1;

结论:一个JVM运行实例可能存在两个垃圾收集器,除非指定垃圾收集器为G1。

在这里还总结一下CMS和G1的区别:

1、CMS收集器是基于“标记-清除”算法实现的,G1收集器是基于“标记-整理”算法实现的,所以,CMS收集器会有内存碎片问题。

2、G1收集器将整个java堆分成很多大小相同的region,而CMS堆内存Y区和O区是连续的,G1是“选择一些内存块”,而不是整代内存来回收,这是G1跟其它GC非常不同的一点,其它GC每次回收都会回收整个Generation的内存(Eden, Old), 而回收内存所需的时间就取决于内存的大小,以及实际垃圾的多少,所以垃圾回收时间是不可控的;而G1每次并不会回收整代内存,到底回收多少内存就看用户配置的暂停时间,配置的时间短就少回收点,配置的时间长就多回收点,伸缩自如。

3、如果应用的内存非常吃紧,对内存进行部分回收根本不够,始终要进行整个Heap的回收,那么G1要做的工作量就一点也不会比其它垃圾回收器少,而且因为本身算法复杂了一点,可能比其它回收器还要差。因此G1比较适合内存稍大一点的应用(一般来说至少4G以上),小内存的应用还是用传统的垃圾回收器比如CMS比较合适。

4、G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。CMS收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表