首页 Java java教程 JVM的内存区域划分以及垃圾回收机制详解

JVM的内存区域划分以及垃圾回收机制详解

Jun 23, 2017 pm 03:11 PM
内存 区域 垃圾

在我们写Java代码时,大部分情况下是不用关心你New的对象是否被释放掉,或者什么时候被释放掉。因为JVM中有垃圾自动回收机制。在之前的博客中我们聊过Objective-C中的MRC(手动引用计数)以及ARC(自动引用计数)的内存管理方式,下方会对其进行回顾。而目前的JVM的内存回收机制则不是使用的引用计数,而是主要使用的“复制式回收”和“自适应回收”。

当然除了上面是这两种算法外,还有其他是算法,下方也将会对其进行介绍。本篇博客,我们先简单聊一下JVM的区域划分,然后在此基础上介绍一下JVM的垃圾回收机制。

 

一、JVM内存区域划分简述

当然本部分简单的聊一下JVM的内存区域的划分,为下方垃圾回收机制内容的展开进行铺垫。当然对JVM内存区域划分的内容网上有好多详细的内容,请自行Google。

根据JVM内存区域的划分,简单的画了下方的这个示意图。区域主要分为两大块,一块是堆区(Heap),我们所New出的对象都会在堆区进行分配,在C语言中的malloc所分配的方法就是从Heap区获取的。而垃圾回收器主要是对堆区的内存进行回收的。

而另一部分则是非堆区,非堆区主要包括用于编译和保存本地代码的“代码缓存区(Code Cache)”、保存JVM自己的静态数据的“永生代(Perm Gen)”、存放方法参数局部变量等引用以及记录方法调用顺序的“Java虚拟机栈(JVM Stack)”和“本地方法栈(Local Method Stack)”。

  

垃圾回收器主要回收的是堆区中未使用的内存区域,并对相应的区域进行整理。在堆区中,又根据对象内存的存活时间或者对象大小,分为“年轻代”和“年老代”。“年轻代”中的对象是不稳定的易产生垃圾,而“年老代”中的对象比较稳定,不易产生垃圾。之所以将其分开,是分而治之,根据不同区域的内存块的特点,采取不同的内存回收算法,从而提高堆区的垃圾回收的效率。下方会给出具体的介绍。

 

 

二、常见的内存回收算法简介

上面我们简单的了解的JVM中内存区域的划分,接下来我们就来看一下几种常见的内存回收算法。当然,下方所介绍的内存回收的算法不仅仅是JVM中所使用到的,我们还会回顾一下OC中的内存回收方式。下方主要包括“引用计数式回收”、“复制式回收”、“标记整理式回收”、“分代式回收”。

 

1、引用计数式内存回收

引用计数(Reference Count)式内存回收机制是Objective-C以及Swift语言中正在使用的内存回收机制,在之前的博客中我们也详细的聊过引用计数式的内存回收。只要有引用,那么引用计数就加1。当引用计数为0时,该块内存就会被回收。当然这中内存清理方式容易形成“引用循环”。

Objective-C的引用计数中循环引用而造成内存泄露的问题,可以将变量声明成weak或者strong类型。也就是说我们可以将引用定义为“强引用”或者“弱引用”。当出现“强引用循环”时,我们将其中的一个引用设置为weak类型即可,然后这种强引用循环就被打破了,也就不会造成“内存泄露”的问题。关于“引用计数式内存回收”的更多以及更详细的内容,请参考之前发布的关于OC内容的相关博客。

为了更清晰的了解引用计数的工作方式,就简单的画了下方这个图。在左边的栈中的a、b、c三个引用分别指向堆中的不同区域块。在堆中的内存区域块中,该区域有一个强引用时,其retainCount就会加1。而在弱引用时,就retainCount就不会加1。

我们先来看看a引用的第1块内存区域,因为该内存块只有a在强引用,所以retainCount=1,当a不在引用该内存区域时,retainCount=0,该内存会理解被回收的。这种情况下是不会造成内存泄露的。

我们再来看看b指向的内存区域2。b和内存块3都强引用了内存块2,所以2的retainCount=2。而内存块2也强引用了内存块3,所以3的retainCount=1。所以b指向的这块内存区域就存在“强引用循环”,因为当b不再指向这块内存区域时,rc=2就会变为rc=1。因为retainCount不为零,所以这2块内存区域是不会被释放的,2不会被释放,那么自然而然的3块内存区域也不会被释放,但是这块内存区域有不会再被使用到了,所以就会造成“内存泄露”的情况。如果这两块内存区域特别大,那么我们可想而知,后果是比较严重的。

像c引用的这块情况,就不会引起“强引用循环”,因为其中的一个引用链是是弱引用的。当c不在引用第4块内存时,rc由1变为零,那么该块区域就会被立即释放。而内存块4被释放后,内存块5的rc由1变为0,内存块5也会被释放掉。这种情况下是不会引起内存泄露的。而在Objective-C中正是采用的这种方式来回收内存的,当然了,在OC中除了“强引用”和“弱引用”外,还有自动释放池。也就是说,Autorealease类型的引用,让retainCount = 0时,不会被立即释放掉,而是在出自动释放池时才会被释放掉,在此就不做过多赘述了。

  

 

2、复制式内存回收

聊完引用计数回收,我们知道引用计数容易引起“循环引用”的问题,为了解决“循环引用”引起的内存泄露问题,OC中引入和“强引用”和“弱引用”的概念。接下来我们在看看复制式内存回收机制,在该机制中是不需要关心“循环引用”的问题的。简单的说,复制式回收其核心就是“复制”,但前提是有条件复制。在垃圾回收时,将“活对象”复制到另一块空白的堆区,然后将之前的区域一并清除。“活对象”就是指沿着对象的引用链可以到“栈”上的对象。当然在将活对象复制到新的“堆区”后,也要将栈区的引用进行修改。

下方就是我们画的复制式回收的简图,主要将堆分为两大部分,在进行垃圾回收时,会将一个堆上的活对象复制到另一个堆上。下方堆1区是目前正在使用的区块,堆2区则是空闲区。而在堆1区中未被标记的那些内存块,也就是2、3是要被回收的垃圾对象。而1、4、5是要被复制的“活对象”。因为沿着栈上的a可到达区块1、沿着c可到达区块4、5。而区块2和3虽然有引用,但是不是来自非堆区,也就是2和3的引用都是来自堆区的引用,所以是要被回收的对象。

  

找到了活对象后,接下来要做的就是将活对象进行复制,将其复制到堆2区。当然,复制到堆2区的对象间的内存地址是连续的,如果要分配新的内存空间的话,直接从堆空闲的一段分配即可。这样在分配内存空间时的效率是比较高的。对象复制后,要修改来自“非堆区”的引用地址。如下所示。

  

复制完毕后,我们直接将堆2区的中的所有内存空间进行回收即可,下方就是复制回收后的最终结果。下方的堆1区清空后,可以接收复制过来的对象了。当对堆2区进行垃圾回收时,会把堆2区的活对象拷贝到堆1区上。

从该实例中我们可以看出当内存垃圾特别多的时候“复制式”垃圾回收的效率还是比较高的,因为复制的对象比较少,清除时直接将旧的堆空间进行清理即可。但是,当垃圾比较少的时候,这种方式会复制大量的活对象,效率还是比较低的。这种方式也会将堆的存储空间进行分半。也就是说,总有一半是空闲的,堆空间的利用率不高。

  

 

3、标记-压缩回收算法

从上述“复制式”垃圾回收过程中,我们知道,垃圾多时其效率比较高,而垃圾少时,其工作方式效率是比较低的。那么,接下来,我们来介绍另一种标记-压缩回收算法,这种算法在垃圾少时的工作效率比较高,而垃圾多的情况下,工作效率反而不高,这就与“复制式”形成了互补。下方我们将会对标记-压缩回收算法进行介绍。

标记-压缩的第一部就是标记,需要将堆区中的“活对象”进行标记。上面的内容我们已经聊了什么是“活对象”,在此就不做过多赘述了。由“活对象”的特征我们可以看出,下方的活对象是内存区域1和3,所以我们将其进行标记。

  

标记完成后,我们就开始进行压缩了,将活对象压缩到“堆区”的一段,然后将剩余的部分进行清除。下方就是将1和3这两个活对象进行了压缩。压缩后,将下方的空间进行Clean。也就是说Clean的部分,就可以分配新的对象了。

  

下方截图是标记-压缩清理后的状态。标记-压缩式垃圾回收可充分利用堆区的空间,当垃圾比较少时,这种处理方式效率还是比较高的,如果垃圾太多碎片化严重时,移动的“活对象”较多,效率比较低。这种方式可以与“复制式”结合使用,根据当前堆区的垃圾状态来选择哪种回收方式。正好与“复制式”形成优势互补。将“复制式”、“标记-压缩式”的回收方式进行整合的算法,就是“分代式”垃圾回收机制,下方会详细介绍到。

  

 

4、分代式垃圾回收

“分代”即根据对象易产生垃圾的状态或者对象的大小将其分为不同的代,可分为“年轻代”、“年老代”和“永久代”。“永久代”不在堆中,再次先不做讨论。根据分代垃圾回收的特点,画了下方的简图。

在堆中,主要把区域分为“年轻代”、“年老代”。位于“年轻代”的对象内存创建的时间不长,更新比较快,易产生“内存垃圾”,所以“年轻代”的垃圾回收使用“复制式”回收方式效率比较高。“年轻代”又可分为两个区,一个是Eden Space(伊甸园)和Survivor Sprace(幸存者区)。Eden Space去主要存放那些初次被创建的对象,而Survivor Sprace存放的是从Eden Space幸存下来的“活对象”。在Survivor Sprace(幸存者区)中又分为form和to两块,用于相互复制对象来进行垃圾清理。

而“年老代”中存放的是一些“大对象”以及从Survivor Sprace中存活下来的“对象”,一般到“年老代”的对象比较稳定,产生垃圾较少,针对这种情况,使用“标记-压缩”式回收效率比较高。“分代垃圾回收”主要是分而治之,根据不同对象的特点将其分类,根据分类的特点来具体选择合适的垃圾回收方案。 

  

 

三、分代式垃圾回收的具体工作原理

当然在JVM具体的垃圾回收时,根据线程分可分为使用单个线程回收的“串行垃圾回收”,使用多个线程回收的“并行垃圾回收”。根据程序的挂起状态,又可分为“独占式回收”和“并发式回收”。当然之前也多次聊过“并行”与“并发”绝对不是一个概念,切不可将其混淆。本篇博客就不对上述这些方式进行详述了,感兴趣的,请自行Google。

下面我们来看一下“分代式垃圾回收”的具体工作原理的完整步骤,来直观的感受一下“分代式”的垃圾回收的执行方式。

 

1、垃圾回收前

下图是等待“分代垃圾回收”的简图,从下图中,我们可以看出在堆中有些已分配的对象内存并没有被栈上引用,这些就是要被回收的对象。我们可以看出,下方的堆,整体上分为“年轻代”和“年老代”,而年轻代,有可细分为Eden Space, From以及To三个区域。关于每个区域的作用,在上面介绍“分代垃圾回收”时,我们已经介绍过了,所以在此部分我们不做详细介绍了。

  

 

2、分代垃圾回收

下图是对上述堆控件的垃圾回收过程。因为我们有上图可以看出,To区域是空白区,可以接受被复制的对象。由于“年轻代”易产生内存垃圾,所以采用“复制式”内存回收的方式。我们将Eden Space和From两个堆区块中的“活对象”拷贝到To区。拷贝的同时,我们也要修改被拷贝内存的栈引用地址。而对From或者Eden区域的“大对象”存储空间直接将其复制到“年老代”。因为“大对象”在From与To区多次复制的效率比较低,直接将其加入到“年老代”中以提高回收效率。

对于“年老代”的垃圾回收,就采用“标记-压缩”式垃圾回收。首先,先将活对象进行“标记”。

  

 

3、垃圾回收后的结果

下方就是“分代”垃圾回收后的具体结果。从下方简图中,我们可以看出,Eden Space和From中的活对象都被复制到了To区,而“年老代”的堆区的存储空间也变化不少。而且在“年老代”中多出了从From区复制过来的大对象。具体如下所示。

  

 

 

四、Eclipse的GC日志配置与分析

上面聊这么多,接下来我们来直观的感受一下在Eclipse如何查看垃圾回收的过程以及分析垃圾回收的日志信息。默认情况下,是不显示垃圾回收的过程以及打印日志的,需要在运行配置中添加相关的配置项来将垃圾回收的日志进行打印。本部分我们来看一下Eclipse中的垃圾回收日志记录的配置,然后我们来分析一下这些日志记录。当然我们本篇博客中使用的是Java8,如果你用其他版本的Java打印出来的日志信息会略有不同,好开始本部分的内容。

1、配置Eclipse的运行设置

在Eclipse中的运行设置中添加相应的配置项,垃圾回收时才会打印相应的日志信息。选择我们的工程,然后找到Run Configurations…选项,进行运行时的配置。

  

 

下方就是上述选项打开的对话框,然后找到(x)=Arguments这个标签栏,在VM arguments中添加相应的虚拟机参数,这些参数都会作为工程在运行时的参数。下方我们添加了-XX:+PrintGCTimeStamps-XX:+PrintGCDetails两个参数。由这两个参数名我们不难看出相应参数所对应的功能,一个是打印垃圾回收时的时间戳,另一个是打印垃圾回收时的细节。当然还有好多其他的参数,比如选择“垃圾回收”时的具体算法的参数,以及选择是“串行”还是“并行”的参数,还有一些选择是“独占式”还是“并发式”垃圾回收的参数。在此就不做过多赘述了,请自行Google。

  

 

2、回收日志的打印与解析

配置完上述参数后,当我们使用System.gc(); 来进行强制垃圾回收时,会打印出相应的参数信息。首先我们得创建测试用的代码,下方就是我们所创建的测试类,当然测试类中的代码比较简单。主要就是new了以字符串,然后将引用置为null, 最后调用System.gc()进行回收。具体代码如下所示:

package com.zeluli.gclog;public class GCLogTest {public static void main(String[] args) {
        String s = new String("Value");
        s = null;
        System.gc();
    }
}
登录后复制

 

下方就是上述代码所运行的效果,接下来我们将对下方日志信息的主要内容进行介绍。

  • [PSYoungGen: 1997K->416K(38400K)] 1997K->424K(125952K), 0.0010277 secs]

    • PSYoungGen表示,并行对“年轻代”进行回收,1997K->416K表示年轻代相应区域中“回收前->回收后”的大小,而(38400K)表示“年轻代”堆的总大小。而后方的1997K->424K(125952K)数据是以整个堆的角度来看待的问题。1997K(堆回收前使用的内存) -> 424K(堆回收后使用的内存)(125952K-堆的总内存空间)。

  • [ParOldGen: 8K->328K(87552K)]

    • ParOldGen并行回收“年老代”,后边的参数与上述并行回收年轻代的参数类似,就不多说了。

  • [Metaspace: 2669K->2669K(1056768K)]

    • 则表示“元数据区”的回收情况,Metaspace及“永久代”区,用于存放静态数据或者系统方法的区域。  

  

 

上述就是简单的垃圾回收的日志,本篇博客的内容就先到这儿吧,关于JVM中的垃圾回收的内容还有好多,以后结合着具体情况,再陆陆续续的进行介绍。今天博客就先到这儿。

 

以上是JVM的内存区域划分以及垃圾回收机制详解的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
威尔R.E.P.O.有交叉游戏吗?
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

打印机内存不足,无法打印页面Excel或PowerPoint错误 打印机内存不足,无法打印页面Excel或PowerPoint错误 Feb 19, 2024 pm 05:45 PM

如果在打印Excel工作表或PowerPoint演示文稿时遇到打印机内存不足的问题,这篇文章可能对您有所帮助。您可能收到类似的错误消息,指出打印机内存不足,无法打印页面。不过,您可以遵循一些建议来解决这个问题。为什么打印时打印机内存不可用?打印机内存不足可能导致内存不可用错误。有时是由于打印机驱动程序设置过低,也可能是其他原因。大文件大小打印机驱动程序过时或损坏来自已安装的加载项的中断打印机设置配置错误发生此问题也可能是因为MicrosoftWindows打印机驱动程序上的内存设置较低。修复打印

大内存优化,电脑升级16g/32g内存速度没什么变化怎么办? 大内存优化,电脑升级16g/32g内存速度没什么变化怎么办? Jun 18, 2024 pm 06:51 PM

对于机械硬盘、或SATA固态硬盘,软件运行速度的提升会有感觉,如果是NVME硬盘,可能感觉不到。一,注册表导入桌面新建一个文本文档,复制粘贴如下内容,另存为1.reg,然后右键合并,并重启电脑。WindowsRegistryEditorVersion5.00[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SessionManager\MemoryManagement]"DisablePagingExecutive"=d

小米14Pro如何查看内存占用? 小米14Pro如何查看内存占用? Mar 18, 2024 pm 02:19 PM

最近,小米发布了一款功能强大的高端智能手机小米14Pro,它不仅外观设计时尚,而且拥有内在和外在的黑科技。该手机拥有顶级的性能和出色的多任务处理能力,让用户能够畅享快速而流畅的手机使用体验。但是性能也是会收到内存的影响的,很多用户们想要知道小米14Pro如何查看内存占用,赶快来看看吧。小米14Pro如何查看内存占用?小米14Pro查看内存占用方法介绍打开小米14Pro手机【设置】中的【应用管理】按钮。查看已安装的所有应用列表,浏览列表并找到你想查看的应用,点击它进入应用详细页面。在应用详细页面中

Windows输入遇到挂起或内存使用率高的问题[修复] Windows输入遇到挂起或内存使用率高的问题[修复] Feb 19, 2024 pm 10:48 PM

Windows的输入体验是一个关键的系统服务,负责处理来自各种人机接口设备的用户输入。它在系统启动时自动启动,在后台运行。然而,有时候这个服务可能会出现自动挂起或占用过多内存的情况,导致系统性能下降。因此,及时监控和管理这个进程是至关重要的,以确保系统的效率和稳定性。在这篇文章中,我们将分享如何解决Windows输入体验被挂起或导致内存使用率高的问题。Windows输入体验服务没有用户界面,但它与处理与输入设备相关的基本系统任务和功能有密切关联。它的作用是帮助Windows系统理解用户输入的每一

电脑8g和16g内存区别大吗?(电脑内存选8g还是16g) 电脑8g和16g内存区别大吗?(电脑内存选8g还是16g) Mar 13, 2024 pm 06:10 PM

  新手用户在购买电脑时,会好奇电脑内存8g和16g有什么区别?应该选择8g还是16g呢?针对这个问题,今天小编就来跟大家详细说明一下。  电脑内存8g和16g的区别大吗?  1、在一般家庭或者是普通工作使用,8G运行内存可以满足,因此在使用过程中8g和16g区别不大。  2、游戏爱好者使用时候,目前大型游戏基本上是6g起步,8g是最低标准。目前在屏幕是2k的情况下,更高分辨率并不会带来更高的帧数表现,因此对8g和16g也无较大差异。  3、对于音频、视频剪辑使用者来说,8g和16g会出现明显区

消息称三星电子、SK 海力士堆叠式移动内存 2026 年后商业化 消息称三星电子、SK 海力士堆叠式移动内存 2026 年后商业化 Sep 03, 2024 pm 02:15 PM

本站9月3日消息,韩媒etnews当地时间昨日报道称,三星电子和SK海力士的“类HBM式”堆叠结构移动内存产品将在2026年后实现商业化。消息人士表示这两大韩国内存巨头将堆叠式移动内存视为未来重要收入来源,计划将“类HBM内存”扩展到智能手机、平板电脑和笔记本电脑中,为端侧AI提供动力。综合本站此前报道,三星电子的此类产品叫做LPWideI/O内存,SK海力士则将这方面技术称为VFO。两家企业使用了大致相同的技术路线,即将扇出封装和垂直通道结合在一起。三星电子的LPWideI/O内存位宽达512

三星宣布完成 16 层混合键合堆叠工艺技术验证,有望在 HBM4 内存大面积应用 三星宣布完成 16 层混合键合堆叠工艺技术验证,有望在 HBM4 内存大面积应用 Apr 07, 2024 pm 09:19 PM

报道称,三星电子的高管DaeWooKim表示,在2024年韩国微电子和封装学会年会上,三星电子将完成采用16层混合键合HBM内存技术的验证。据悉,这项技术已通过技术验证。报道还称,此次技术验证将为未来若干年内的内存市场发展奠定基础。DaeWooKim表示,三星电子成功制造了基于混合键合技术的16层堆叠HBM3内存,该内存样品工作正常,未来16层堆叠混合键合技术将用于HBM4内存量产。▲图源TheElec,下同相较现有键合工艺,混合键合无需在DRAM内存层间添加凸块,而是将上下两层直接铜对铜连接,

美光:HBM 内存消耗 3 倍晶圆量,明年产能基本预定完毕 美光:HBM 内存消耗 3 倍晶圆量,明年产能基本预定完毕 Mar 22, 2024 pm 08:16 PM

本站3月21日消息,美光在发布季度财报后举行了电话会议。在该会议上美光CEO桑杰・梅赫罗特拉(SanjayMehrotra)表示,相对于传统内存,HBM对晶圆量的消耗明显更高。美光表示,在同一节点生产同等容量的情况下,目前最先进的HBM3E内存对晶圆量的消耗是标准DDR5的三倍,并且预计随着性能的提升和封装复杂度的加剧,在未来的HBM4上这一比值将进一步提升。参考本站以往报道,这一高比值有相当一部分原因在HBM的低良率上。HBM内存采用多层DRAM内存TSV连接堆叠而成,一层出现问题就意味着整个

See all articles