搜索
首页Javajava教程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
1t内存等于多少GB1t内存等于多少GBFeb 22, 2023 pm 04:55 PM

1t内存等于1024GB。1t内存是指内存的存储容量为“1TB”,而1TB等于1024GB。但这只是计算机原理中理论上的数值,一般在系统显示可用存储空间中会偏少;因为硬盘制造商对硬盘的定义与计算机对硬盘容量的算法不同,导致硬盘标识容量和操作系统中显示的实际容量存在误差。

gear1和gear2内存模式是什么gear1和gear2内存模式是什么Sep 14, 2022 am 11:15 AM

gear1和gear2内存模式指的是CPU的内存控制器与内存频率的比例关系;gear1表示内存控制器频率和内存工作频率之比是“1:1”,而gear2表示内存控制器频率和内存工作频率之比是“1:2”,可减轻内存控制器压力,让内存更容易得到更高的频率。

电脑c盘一般留多大内存电脑c盘一般留多大内存Jun 27, 2023 pm 03:15 PM

电脑C盘一般留50-80G,由于系统在日后使用当中会产生垃圾文件和缓存文件等,因此建议至少预留50GB-80GB的空间给C盘,如果不习惯在安装软件时选择路径,日常也不经常清理电脑,那么至少需要100GB。

板载内存是什么意思板载内存是什么意思Jan 30, 2023 pm 03:21 PM

板载内存是指主板上本身集成的内存,是直接焊接在了电脑的主板上无法更换的。板载有“集成”的意思,是指整合于主板芯片中的功能或硬件,主要有板载显卡、声卡、网卡、RAID等。一般板载硬件功能都较简单,不能完全取代独立硬件;但是购买可以控制购买成本。

1tb是多少g内存1tb是多少g内存Nov 30, 2022 am 10:23 AM

1tb理论上是等于1024g;其中T是TB的缩写,G是GB的缩写,但是一般内存不会有1TB的,TB级别的是硬盘;TB表示太字节,是一种信息计量单位,现今通常在标示硬盘总容量、或具有大容量的储存介质之储存容量时使用。

内存或磁盘不足,word无法显示请求字体怎么办内存或磁盘不足,word无法显示请求字体怎么办Nov 06, 2022 am 10:47 AM

内存或磁盘不足,word无法显示请求字体的解决办法:1、打开Word,点击【剪切板】,然后点击【全部清空】;2、在【高级系统设置】中取消勾选“自动管理所有驱动器的分页文件大小”的选项,然后选中需要设置的磁盘盘符,输入合适的大小即可。

硬盘是外存还是内存硬盘是外存还是内存Feb 23, 2023 pm 04:14 PM

硬盘是外存。外存全称“外存储器”,是指除计算机内存及CPU缓存以外的储存器,一般断电后仍然能保存数据;外存通常是磁性介质或光盘,像硬盘,软盘,磁带,CD等,能长期保存信息,并且不依赖于电来保存信息,但是由机械部件带动,速度与CPU相比就显得慢的多。

4g内存win10够用吗4g内存win10够用吗Mar 15, 2023 pm 02:28 PM

不够用。虽然4G内存足够满足Windows 10系统的安装需求,但问题是不可能只在电脑里安装一个系统,还要安装其他应用,而这些应用也会占用一定的内存空间;先不考虑CPU、硬盘和显卡等配件是否能够满足需求,只单独说内存,假设用户安装的Windows 10系统占用了2G内存,但其他的辅助插件和应用可能还会占去2G内存,一旦内存被占满,必然会导致电脑出现卡顿、运行慢的情况。

See all articles

热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.能量晶体解释及其做什么(黄色晶体)
2 周前By尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
4 周前By尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
4 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

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