精华内容
下载资源
问答
  • jvm记忆集
    2021-05-07 20:09:18

    前言

    假设年轻代进行Minor GC做垃圾回收处理。此时会采取GC Roots (可达性分析算法),标记非垃圾对象。

    如果此时新生代中的某个对象,被老年代某个对象引用着。

    此时Minor GC操作前,需要判断当前新生代中的这个对象是否为垃圾对象,则需要老年代中进行检索。导致可达性分析算法执行效率低下

    基于上述问题,在JVM中,引入了记忆集卡表的概念。

    记忆集 Remember Set

    JVM在年轻代中开辟了一块内存空间,维护老年代对年轻代中所有的引用的位置(标识)。

    Remember Set

    新生代中引入记忆集(Remember Set)的数据结构,记录从非收集区到收集区的指针集合。避免把整个老年代加入到GC Roots 的扫描范围。

    GC Roots 可达性分析年轻代中的对象,

    更多相关内容
  • JVM记忆集和卡表

    千次阅读 2021-02-21 15:42:07
    那我们进行垃圾回收的代价实在是太大了,因此我们引入了一种叫做记忆集的抽象数据结构来记录这种引用关系。 什么是记忆集? 记忆集是一种用于记录从非收集区域指向收集区域的指针集合的数据结构。 如果我们不考虑效率...

    当我们进行young gc时,我们的gc roots除了常见的栈引用、静态变量、常量、锁对象、class对象这些常见的之外,如果老年代有对象引用了我们的新生代对象,那么老年代的对象也应该加入gc roots的范围中,但是如果每次进行young gc我们都需要扫描一次老年代的话,那我们进行垃圾回收的代价实在是太大了,因此我们引入了一种叫做记忆集的抽象数据结构来记录这种引用关系。

    什么是记忆集?

    记忆集是一种用于记录从非收集区域指向收集区域的指针集合的数据结构。

    如果我们不考虑效率和成本问题,我们可以用一个数组存储所有有指针指向新生代的老年代对象。但是如果这样的话我们维护成本就很好,打个比方,假如所有的老年代对象都有指针指向了新生代,那么我们需要维护整个老年代大小的记忆集,毫无疑问这种方法是不可取的。因此我们引入了卡表的数据结构

    什么是卡表?

    记忆集是我们针对于跨代引用问题提出的思想,而卡表则是针对于该种思想的具体实现。(可以理解为记忆集是结构,卡表是实现类)

    在hotspot虚拟机中,卡表是一个字节数组,数组的每一项对应着内存中的某一块连续地址的区域,如果该区域中有引用指向了待回收区域的对象,卡表数组对应的元素将被置为1,没有则置为0;下图为卡表的一个实现,某块内存的地址向右移动9位(相当于除以512)定位到一个卡表元素,也就是说,内存中每512字节的连续区域会被定位到同一片卡表区域,如果卡表对应元素为1则代表该512个字节所在区域中有指向的指针。

    CARD_TABLE [this address >> 9] = 0; 
    

    写屏障(Write Barrier)

    我们每次对引用进行改变时,我们在程序中并没有手动去维护卡表的信息,那么卡表信息的维护到底是如何进行的呢,这就依赖于我们的写屏障功能。

    写屏障可以理解为对于我们引用类型字段复制的AOP操作。在赋前的部分的写屏障叫作写前屏障(Pre-Write Barrier),在赋值后的部分的写屏障叫作写后屏障(PostWrite Barrier)。

    void oop_field_store(oop* field, oop new_value) {
    // 引用字段赋值奥做
    *field = new_value;
    // 写后屏障,在这里完成卡表状态更新
    post_write_barrier(field, new_value);
    }
    

    G1的记忆集

    上述的卡表机制基本上适用于CMS垃圾回收器,因为CMS垃圾回收器只需要在young gc时维护老年代对新生代的引用即可,但是G1垃圾回收器不一样,因为G1垃圾回收器是基于分区模型的,所以每一个Region需要知道有哪些region的引用指向了它,并且这些region是不是本次垃圾回收区域的一部分。因此G1垃圾回收器不能简单的只维护一个卡表(卡表只能简单的知道某块内存区域有没有引用收集区域的对象,但是不能知道到底是谁引用了自己),所以在G1垃圾回收器的记忆集的实现实际上是基于哈希表的,key代表的是其他region的起始地址,value是一集合,里面存放了对应区域的卡表的索引,因此G1的region能够通过记忆集知道,当前是哪个region有引用指向了它,并且能知道是哪块区域存在指针指向。

    但是大家应该能注意到,每个region都维护一个记忆集,内存占用量肯定很大,这也就是为什么G1垃圾回收器比传统的其他垃圾回收器要有更高的内存占用。据统计G1至少要耗费大约10%-20%的Java堆空间lai’wei’hu收集器的工作。

    展开全文
  • JVM垃圾回收-记忆集和卡表

    多人点赞 2022-02-21 22:13:41
    记忆集是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。卡表是记忆集的一种具体实现。

    🥂在说记忆集和卡表之前,先给大家介绍一下跨代引用的问题。

     

    👩🏻‍🏫假如要现在进行一次只局限于新生代区域内的收集(Minor GC),但新生代的实例对象1在老年代中被引用,为了找出该区域(新生代)中所有的存活对象,不得不在固定的GC Roots之外,再额外遍历整个老年代中所有对象来确保可达性分析结果的正确性,反过来也是一样。遍历整个老年代所有对象的方案虽然理论上可行,但无疑会为内存回收带来很大的性能负担。

    👉🏻事实上并不只是新生代、老年代之间才有跨代引用的问题,所有涉及部分区域收集(Partial GC)行为的垃圾收集器,典型的如G1、ZGCShenandoah收集器,都会面临相同的问题。

    🍬那么如何才能解决跨代引用呢?

    首先,跨代引用相对于同代引用来说仅占极少数。原因是跨代引用的对象应该倾向于同时生存或者同时死亡的(举个🌰:如果某个新生代对象存在跨代引用,由于老年代对象难以消亡,该引用会使得新生代对象在收集时同样得以存活,进而在年龄增长之后晋升到老年代中,这时跨代引用也随即被消除了)。

    🌴🌴依据上面说所,就不应再为了少量的跨代引用去扫描整个老年代,也不必浪费空间专门记录每一个对象是否存在及存在哪些跨代引用,只需在新生代上建立一个全局的数据结构(该结构被称为“记忆集”,Remembered Set),这个结构把老年代划分成若干小块,标识出老年代的哪一块内存会存在跨代引用。此后当发生Minor GC时,只有包含了跨代引用的小块内存里的对象才会被加入到GCRoots进行扫描。虽然这种方法需要在对象改变引用关系(如将自己或者某个属性赋值)时维护记录数据的正确性,会增加一些运行时的开销,但比起收集时扫描整个老年代来说仍然是划算的。

    下面就来介绍一下这个全局的数据结构记忆集。

    记忆集✨✨

    🍺记忆集是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。如果我们不考虑效率和成本的话,最简单的实现可以用非收集区域中所有含跨代引用的对象数组来实现这个数据结构,如下面代码所示:

    //以对象指针来实现记忆集的伪代码
    Class RememberedSet {
    	Object[] set[OBJECT_INTERGENERATIONAL_REFERENCE_SIZE]; 
    }
    

    🎈这种记录全部含跨代引用对象的实现方案,无论是空间占用还是维护成本都相当高昂。而在垃圾收集的场景中,收集器只需要通过记忆集判断出某一块非收集区域是否存在有指向了收集区域的指针就可以了,并不需要了解这些跨代指针的全部细节。那设计者在实现记忆集的时候,便可以选择更为粗犷的记录粒度来节省记忆集的存储和维护成本。下面列举了一些可供选择(当然也可以选择这个范围以外的)的记录精度:

    • 字长精度:每个记录精确到一个机器字长(就是处理器的寻址位数,如常见的32位或64位,这个 精度决定了机器访问物理内存地址的指针长度),该字包含跨代指针。
    • 对象精度:每个记录精确到一个对象,该对象里有字段含有跨代指针。
    • 卡精度:每个记录精确到一块内存区域,该区域内有对象含有跨代指针。

    🍍🍍上面的,第三种“卡精度”所指的是用一种称为“卡表”(Card Table)的方式去实现记忆集,这也是目前最常用的记忆集的实现形式。

    👀卡表和记忆集又有什么关系呢?

    前面介绍记忆集的时候提到 记忆集其实是一种"抽象”的数据结构,抽象的意思是只定义了记忆集的行为意图,并没有定义其行为的具体实现卡表就是记忆集的一种具体实现,它定义了记忆集的记录精度、与堆内存的映射关系等。关于记忆集与卡表的关系,可以按照Java中Map与HashMap的关系来类比理解(即接口和实现类来的关系)。

    🍋下面来详细说一下记忆集的具体实现卡表

    卡表✨✨

    卡表是使用一个字节数组CARD_TABLE[] 实现,每个元素对应其标识的内存区域一块特定大小的内存块,每个内存块称为卡页,hotspot使用的卡页是2^9大小 即512字节。如下图所示

    🍦这样我们就可以把某个区域按照卡页进行划分,假如我们现在要对新生代区域进行垃圾回收,那么就可以把老年代区域看成是一个卡页一个卡页划分好的,如下图所示。

    如图所示🍹,因为cardpage1中存在指向新生代的跨代引用,所以对应卡表的第一个位置为1,表明该page区域存在跨代应用的对象。

    📍卡表角度:因为page1中存在跨代饮用的对象,所以卡表对应的第一个位置记为1,表明page1这个元素变脏。

    📍内存回收角度:因为卡表的第一个位置为1,表明该page区域存在跨代应用的对象,垃圾回收的时候需要扫描该区域。

    😎一个卡页的内存中通常包含不止一个对象,只要卡页内有一个(或更多)对象的字段存在着跨代指针,那就将对应卡表的数组元素的值标识为1,称为这个元素变脏(Dirty),没有则标识为0。在垃圾收集发生时,只要筛选出卡表中变脏的元素,就能轻易得出哪些卡页内存块中包含跨代指针,把它们加入GC Roots中一并扫描。这样就不需要扫描整个老年代大大减少GC Roots的扫描范围。

    thanks 

    展开全文
  • 此篇内容来自《深入理解Java...JVM将堆内存进行了分代,对象间可能存在跨代引用,那么每次进行GC的时候都需要进行全堆扫描判断是否有引用吗?答案并不是,JVM通过卡表的的技术来解决这个问题。 跨代引用假说 跨代引.

    此篇内容来自《深入理解Java虚拟机》

    跨代引用

    对象不是孤立的,对象之间会存在跨代引用,假如现在进行一次只局限于新生代区域内的收集(Minor GC),但新生代中的东西是完全有可能被来年代所引用的,为了找出该区域中的存活对象,不得不在固定的GC Roots之外再额外遍历整个老年代中所有的东西来确保可达性分析结果的正确性。
    JVM将堆内存进行了分代,对象间可能存在跨代引用,那么每次进行GC的时候都需要进行全堆扫描判断是否有引用吗?答案并不是,JVM通过卡表的的技术来解决这个问题。

    跨代引用假说

    跨代引用相对于同代引用来说占极少数。
    存在互相引用关系的两个对象,是应该倾向于同时生存或者同时消亡的。举例,如果某个新生代对象存在跨代引用,由于老年代对象难以消亡,该引用会使得新生对象在收集时同样得以存活,进而年龄增长后晋升到老年代中,这时跨代引用也随机被消除了。
    因此依据这条假说,我们就不应再为了少量的跨代引用去扫描整个老年代,也不必浪费空间专门记录每一个对象是否存在及存在哪些跨代引用,只需在新生代上建立一个全局的数据结构(该结构被称为“记忆集”)。

    记忆集(Remember Set)

    记忆集是一种抽象概念,用于在实现部分垃圾收集(Partial GC)时用于记录从非收集区域指向收集区域的指针集合,卡表是记忆集的一种实现方式。例如在分代式GC中,通常能单独收集的只有Yong gen,那记忆集记录的就是Old gen指向Young gen的跨代指针。

    卡表(Card Table)

    卡表最简单的形式可以只是一个字节数组,而HotSpot虚拟机确实也是这样做的,下边这行代码是HotSpot默认的卡表标记逻辑:

    CARD_TABLE [this address >> 9] = 0;
    

    之所以使用byte数组而不是bit数组主要是速度上的考量,现代计算机硬件都是最小按字节寻址的,没有直接存储一个bit指令,所以要用bit的话就不得不多消耗几条shift+mask指令。

    字节数组CARD_TABLE的每一个元素都对应着其标识的内存区域中的一块特定大小的内存块,这个内存块被称作“卡页”(Card Page)。一般来说,卡页大小都是以2的N次幂的字节数,通过上面的代码卡页看出HotSpot中使用的卡页是2的9次幂,即512字节(地址右移9位,相当于用地址除以512)。那如果卡表标识内存区域的起始地址是0x0000的话,数组CARD_TABLE的第0、1、2号元素,分别对应了地址范围为0.00000x01FF、0x02000x03FF、0x04FF~0x05FF的卡页内存块。备注:十六进制数200、400分别为十进制的512、1024,这3个内存块为从0开始、512字节容量的相邻区域。
    在这里插入图片描述

    在这里插入图片描述

    一个卡页的内存中通常包含不止一个对象,只要卡页内有一个(或更多)对象的字段存在着跨代指针,那就将对应卡表的数组元素的值标识为1,成这个元素变脏(Dirty),没有则标识为0。在垃圾收集发生时,只要筛选出卡表中变脏的元素,就能轻易得出哪些卡页内存块中包含跨代指针,把他们加入GC Roots中一并扫描。

    写屏障

    上边已经解决了GC Roots扫描范围的问题,但是还没有解决卡表元素如何维护的问题,例如他们何时变脏、谁把他们变脏等。


    卡表元素变脏时机是很明确的,在其他分代区域中对象引用了本区域对象时,其对应的卡表元素就应该变脏,变脏的时间点原则上应该发生在引用字段赋值的那一刻,但问题是如何变脏,即如何在对象赋值的那一刻去更新卡表呢?假如是解释执行的字节码,那相对好处理,虚拟机负责每条字节码指令的执行,有充分的介入空间;但在编译执行的场景中呢?经过即时编译后的代码已经是纯粹的机器指令了,这就必须找到一个在机器码层面的手段,把维护卡表的动作放到每一个赋值操作之中。


    在HotSpot虚拟机里是通过写屏障(Write Barrier)技术去维护卡表状态的。这与解决并发乱序执行问题的“内存屏障”有所不同。写屏障可以看作在虚拟机层面对“引用类型字段赋值”这个动手做的AOP切面,在引用对象赋值时会产生一个环形(Around)通知,供程序执行额外的动作,也就是说赋值前后都在写屏障的覆盖范畴之内。在赋值前的部分写屏障叫做写前屏障(Pre-Write-Barrier),在赋值后的则叫作写后屏障(Post-Write-Barrier)。HotSpot虚拟机的许多收集器中都有使用到写屏障,但直到G1收集器出现,其他收集器都只用到了写后屏障。

    更新卡表的简单逻辑

    void oop_field_store(oop* field, oop new_value) {
    	// 引用字段赋值操作
    	*field = new_value;
    	// 写后屏障,在这里完成卡表状态更新
    	post_write_barrier(field, new_value);
    }
    

    应用写屏障后,虚拟机就会对所有的赋值操作生成相应的指令,一旦收集器在写屏障中增加了更新卡表的操作,无论更新的是否为老年代对新生代的引用,每次只要对引用进行更新,就会产生额外的开销,不过这个开销与Minor GC时扫描整个老年代的代价相比还是低的多的。

    伪共享

    除了写屏障的开销外,卡表在高并发的情况下还存在着“伪共享”(False Sharing)的问题,伪共享是处理并发底层细节时一种经常需要考虑的问题,现在中央处理器的缓存系统是以缓存行(Cache Line)为单位存储的,当多线程修改互相独立的变量时,如果这些变量在同一个缓存行,就会导致写回、无效化或者同步从而导致性能降低,这就是伪共享的问题。

    维基百科对于伪共享的解释:CPU的缓存是以缓存行(cache line)为单位进行缓存的,当多个线程修改不同变量,而这些变量又处于同一个缓存行时就会影响彼此的性能。例如:线程1和线程2共享一个缓存行,线程1只读取缓存行中的变量1,线程2修改缓存行中的变量2,虽然线程1和线程2操作的是不同的变量,由于变量1和变量2同处于一个缓存行中,当变量2被修改后,缓存行失效,线程1要重新从主存中读取,因此导致缓存失效,从而产生性能问题。
    总体来说就是线程同时访问缓存行导致了,数据在缓存行里的缓存数据失效。


    假设处理器的缓存行大小为64个字节,由于一个卡表元素占一个字节,64个卡表元素将会共享一个缓存行,这64卡表元素对应的卡页总的内存为32KB(64*512字节),也就是说如果不同线程更新的对象正好处于这32KB的内存区域中,就会导致更新卡表时正好写入同一个缓存行而影响性能,为了避免伪共享,就是不采用无条件的写屏障,而是先检查卡标记,只有当卡标记未被标记过才将其标记为变脏,即将卡表的更新逻辑变为以下代码:

    if (CARD_TABLE [this address >> 9] != 0)
    CARD_TABLE [this address >> 9] = 0;
    

    JDK7之后,HotSpot虚拟机增加了一个新的参数-XX:+UseCondCardMark,用来决定是否开启卡表更新的条件判断。开启会增加一次额外判定的开销,但是能够解决伪共享的问题,两者各有损耗,是否打开要根据实际运行情况来进行测试权衡。


    记录精度

    一般假设:

    • RSet:对象粒度(RSet中存有Old gen的对象指针)
    • Cart Table:块粒度(上边已经讲到,通常是2的幂次方,HotSpot使用了512个字节),可能会包含多个对象

    精度选择:

    • 字粒度:每个记录精确到一个机器字(word)。该字包含有跨代指针
    • 对象粒度:每个记录精确到一个对象。该对象里有跨代指针
    • card粒度:每个精度精确到一大块内存区域。该区域内可能含有大量的跨代指针
    • ====…

    精度的不同,准确性就不同,垃圾收集过程中就可能会产生floating garbage,虽然不影响GC的正确性,但会带来一定的内存开销(已经死了对象没有被回收)
































    展开全文
  • 垃圾收集器在新生代中建立了名为记忆集的数据结构,用来避免把整个老年代加进GC roots扫描范围。事 实上并不只是新生代、老年代之间才有跨代引用的问题,所有涉及部分区域收集(Partial GC)行为的 垃圾收集器,典型...
  • 为解决对象跨代引用所带来的问题,垃圾收集器在新生代中建 立了名为记忆集(Remembered Set)的数据结构,用以避免把整个老年代加进GC Roots扫描范围。 事实上并不只是新生代、老年代之间才有跨代引用的问题,所有...
  • 引言:在JVM中,使用到了记忆集这种数据结构来解决跨代引用的问题,使得仅花费很小的代价就知道了哪些老年代区域上的对象包含了指向新生代的指针,从而避免了扫描整个老年代。但是现在有一个问题,由于在运行的过程...
  • 再谈JVM里的记忆集合

    千次阅读 2019-10-26 19:22:13
    在之前的文章《通过HotSpot源码详解Java堆空间创建过程》中,...我们从JVM的环境中抽离出来,考虑一个独立的两分代内存模型。该模型已经运行了一段时间,经历了多次对象创建与GC的过程,如下图所示。 其中G...
  • 记忆集 记忆集是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构 前面介绍分代回收的文章中提过记忆集(Remebered Set)的数据结构,给老年代进行分块,记忆集存储着哪一块老年代的区域存在跨带引用。...
  • 为此,正常情况下需要扫描整个老年代来确定哪些对象引用了新生代的对象,但这种方式的开销太大,所以JVM引入了记忆集的概念,采用记忆集的方式,能有效减小开销。 G1收集器出现之前的记忆集 首先,我们要了解记忆集...
  • 引言:在前面两篇博文中,主要讲述的是经典垃圾回收器中的记忆集,在经典垃圾回收器中,记忆集主要通过Card Table来实现。而在G1垃圾回收器中,记忆集的结构发生了较大改变。 为什么单纯的Card Table结构不再适用? ...
  • JVM(3)之垃圾回收(GC垃圾收集器+垃圾回收算法+安全点+记忆集与卡表+并发可达性分析......)
  • 记忆集和卡表
  • 本文目录 JVM 之 OopMap 和 RememberSet OopMap RememberSet JVM 之 OopMap 和 RememberSet OopMap OopMap 有什么用? OopMap 用于枚举GCRoots 当垃圾回收发生时,收集线程 会 对栈上的内存进行扫描,看看哪些...
  • 记忆集与卡表

    2021-12-18 22:29:41
    记忆集与卡表 为什么需要记忆集? 跨代引用假说(IntergenerationalReferenceHypothesis):跨代引用相对于同代引用来说仅占极少数。 存在互相引用关系的两个对象,是应该倾向于同时生存或者同时消亡的。举个例子,如果...
  • JVM-内存区域

    2018-12-13 15:35:46
    如上图所示,首先Java源代码们(.java后缀)会被Java编译器编译为字节码文件(.class后缀),然后由JVM中的类加载器加载各个类的字节码文件,加载完毕后,交给JVM执行引擎.在整个程序的执行过程中,JVM会用一段空间来存储程序...
  • 清除算法2.2 标记-复制算法2.3 标记-整理算法三、HotSpot的算法细节实现3.1 根节点枚举3.2 安全点如何在垃圾收集时让所有线程跑到最近的安全点停顿下来3.3 安全区域原理3.4 记忆集与卡表3.4.1 记忆集的实现卡表3.5 ...
  • 记忆集也叫rememberSet,垃圾收集器在新生代中建立了记忆集这样的数据结构,用来避免把整个老年代加入到GC ROOTS的扫描范围中。对于记忆集来说,我们可以理解为他是一个抽象类,那么具体实现它的方法将由子类去完成...
  • 当前,以Hadoop、Spark为...这些大数据处理框架采用分布式架构,使用Java、Scala等面向对象语言编写,在集群节点上以Java虚拟机(JVM)为运行时环境执行计算任务,因此依赖JVM的自动内存管理机制来分配和回收数据对象.
  • 文章目录Java内存模型我们开发人员编写的Java代码是怎么让电脑认识的为什么说java是跨平台语言Jdk和Jre和JVM的区别说一下 JVM由那些部分组成,运行流程是什么?说一下 JVM 运行时数据区详细的介绍下程序计数器?...
  • 记忆集与写屏障⑧. G1回收器垃圾回收过程①. 年轻代GC②. 并发标记过程③. 混合回收 Mixed GC④. Full GC⑨. 优化建议 ①. 什么是G1垃圾收集器 ①. G1(Garbage-First)是一款面向服务端应用的垃圾收集器,主要针对...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,159
精华内容 2,063
关键字:

jvm记忆集