当前位置: 首页 > news >正文

G1原理—3.G1是如何提升垃圾回收效率

大纲

1.G1为了提升GC的效率设计了哪些核心机制

2.G1中的记忆集是什么

3.G1中的位图和卡表

4.记忆集和卡表有什么关系

5.RSet记忆集是怎么更新的

6.DCQ机制的底层原理是怎样的

7.DCQS机制及GC线程对DCQ的处理

提升G1垃圾回收器GC效率的黑科技

G1设计了一套TLAB机制 + 快速分配机制用来提升分配对象的效率

G1设计了一套记忆集 + 位图 + 卡表 + DCQ + DCQS机制用来提升垃圾回收的效率

1.G1为了提升GC的效率设计了哪些核心机制

(1)记忆集RSet(Remember Set)

(2)位图BitMap

(3)卡表CardTable

(4)这些数据结构是怎么提升GC效率的

(1)记忆集RSet(Remember Set)

一.可达性分析算法在分代模型下的问题

JVM中判断对象是否存活的算法是GC Roots可达性分析算法,即从GC Roots出发标记所有存活对象。然后遍历对象的每个字段继续标记,直到所有对象标记完毕。标记完毕后,一个对象要么是存活对象,要么是垃圾对象。

在分代模型的JVM GC中,老年代和新生代的回收,一般都是不同的。新生代会被先触发,实在无法腾出足够空间,才会进行老年代GC或FGC。

G1中的垃圾回收会分成三种:新生代回收、混合回收和FGC。新生代回收总是收集所有新生代分区,混合回收会收集所有新生代分区和部分老年代分区,FGC则是会对所有分区进行处理。

G1也采用分代模型:首先进行新生代回收,然后不够再进行Mixed GC,最后才可能会FGC。

问题:回收新生代时为什么需要遍历老年代?

因为新生代对象不一定只有新生代对象在引用,老年代对象也可能会引用新生代对象。在触发新生代GC时,老年代里的一些对象也会在新生代的GC Roots中。所以回收新生代时,根据可达性算法,遍历老年代就是为了完善GC Roots。

而这又会面临新的问题:遍历跨代的所有对象浪费时间。由于新生代和老年代GC的阶段是不同的,当只需要回收新生代时,如果还是按照可达性分析算法,那么就会把老年代也都全标记一遍,而此时并不回收老年代。同样如果只需要回收老年代,也要把新生代的所有对象遍历一遍,这样就非常浪费时间。

如何才能拿到老年代中引用新生代的那些对象呢?

二.记忆集是针对分代模型垃圾回收设计的用于解决跨代引用的数据结构

如何找出引用了新生代对象的所有老年代对象?难道只能遍历老年代?RSet记忆集,就是为了解决这个问题而设计的。RSet记忆集,会使用一组key-value结构来记录跨代引用的引用关系。这样在GC时就可以借助GC Roots + 记忆集,快速解决同代引用及跨代引用的可达性分析问题。如下图示:

图片

(2)位图BitMap

一.JVM应该如何标记内存的使用状态

由于JVM管理的是内存,而内存的使用状态,其实是不太好标记的。为了标记JVM中的内存使用状态,最笨的方法就是:直接遍历整个内存块,看看它到底有没有内容。没有内容就认为它是空闲的,有内容就认为它是在使用中的。

但这种简单粗暴、直接遍历内存查看内存是否被使用的方式不适合JVM,如果JVM想查看哪些内存被使用,还要去遍历内存,这样效率就太低了。

二.JVM使用位图来标记内存的使用状态

为了描述内存的使用状态,JVM使用了位图,也就是JVM会在一个位图里记录所有内存的使用状态。如果JVM要要知道某一块内存是否被使用,就直接访问位图中这块内存对应的坐标内容来进行判断,看是否已使用。如下图示:

图片

三.什么是位图

位图就是通过"位"来描述某一块内存的状态的图。计算机存储数据的最小单位是字节,一个字节占用了8个二进制位,每个二进制位的值是0或1。位图就是一组二进制位数据,位图的每个二进制位的0和1都标识其描述内容的状态,如内存是否使用。

G1便使用了位图来描述内存的使用状态,G1会在混合回收的并发标记阶段使用位图,以此来快速判断内存是否已被使用。

(3)卡表CardTable

一.卡表与位图的区别

卡表类似于位图,都是通过使用一段数据去描述一块内存的情况。

卡表和位图不一样的地方是:位图只能使用一个二进制位来描述内存的状态,也就是只能记录使用或未使用。卡表则可以描述更多的信息,比如内存是否使用、内存的引用关系等。卡表使用8个二进制位也就是一个字节来描述一块内存的使用情况。比如内存是否使用、使用了多少内存、内存的引用关系等。

所以,本质上卡表在数据结构层面和位图其实并没有什么太大区别,只不过卡表的描述符比位图长、描述的内容比位图多而已。

二.G1的卡表

G1的卡表会用1个字节(8位)来描述512字节的内存使用情况以及对象的引用关系,并且G1的卡表是一个全局卡表,也就是整个堆内存会共用一个卡表。G1会使用一个全局卡表来描述整个堆内存的使用情况以及对象的引用关系。

由于512字节的内存可能会被引用多次,即同一个对象被多个对象引用。所以卡表的描述,可以理解为一个大概的引用关系描述,卡表并不记录每个对象的具体引用关系。

G1使用了记忆集 + 卡表,来解决分代模型中,跨代引用关系的不好判定、不好追踪问题。

图片

(4)这些数据结构是怎么提升GC效率的

一.标记过程很影响GC效率 + 记忆集可避免跨代遍历提升标记过程效率

JVM的垃圾回收效率主要体现在以下两方面:

第一.标记垃圾对象

第二.清理垃圾 + 整理内存

其中标记过程会分成多个步骤:初始标记、并发标记、重新标记、并发回收。可见标记过程是非常耗时的,不然没有必要分成这么多个阶段。

那么标记过程的耗时具体体现在哪里呢?其实标记过程耗时就是体现在引用关系不好判断、内存是否使用不好判断上面。

首先,初始标记要从GC Roots出发,标记所有被直接引用的对象。然后,并发标记要追踪所有被间接引用的对象。如果出现新生代对象被老年代引用了,通过RSet就可以避免对老年代的遍历。

二.为节约记忆集的内存会使用卡表

引用关系会通过记忆集RSet来进行查找,但是记忆集Rset里不能直接记录哪个对象引用了哪个对象。否则如果一个系统1000w个对象,引用关系还特别复杂,那么一个Rset可能占用的内存就会特别大。

所以这个时候卡表CardTable就出现了,卡表CardTable里可以用1个字节来描述512字节内存的引用关系。这样RSet里就可以只记录CardTable相关内容,这样就能节省很多内存。比如发现老年代有一块512字节的空间里的对象引用了新生代的一个对象,那么记忆集Rset只需要直接记录这个512字节的空间在卡表里的位置即可。

三.并发标记阶段会使用位图

位图和并发标记阶段是息息相关的。在并发标记阶段,可以借助位图描述内存使用情况,避免内存使用冲突,也避免GC线程无效遍历一些未使用的内存。

(5)总结

G1为了提升GC标记效率做了哪些设计:

一.记忆集RSet—避免跨代遍历对象

二.位图BitMap—描述内存使用(避免并发冲突 + 避免无效遍历未使用内存)

三.卡表CardTable—节约记忆集的内存(8个位1个字节描述512字节的引用关系)

问题:Rset到底是什么?它具体是怎么实现的?它里面会记录什么内容?

2.G1中的记忆集是什么

(1)怎么标记一个对象

(2)跨代引用下存在存活对象找不全的问题

(3)怎么记录跨代的引用关系—RSet记忆集

(4)在G1中有多少种引用关系 + 哪些需要记录

(1)怎么标记一个对象

如何判断一个对象需要被标记为垃圾对象,或者需要被标记为存活对象?其实就是通过可达性分析算法来判断对象是否存活,也就是通过追踪GC Roots引用关系来标记。如果一个对象被GC Roots引用或者被GC Roots引用的对象引用了,那么就标记为存活对象,否则就标记为垃圾对象。

那么如何知道引用关系呢?最简单最准确无误的方式就是,从GC Roots 出发,一个一个对象去遍历。把每个对象的每个字段引用的内容都进行遍历,找出所有引用关系。

图片

但如果出现了跨代引用则会有问题,老年代的一个对象引用了新生代,那么用新生代的GC Roots不能找全。这时老年代的一些对象,也要加入到新生代的GC Roots里面,才能找全。

(2)跨代引用下存在存活对象找不全的问题

由于G1本身是有分代的,在某种特殊情况下,有一个老年代的对象引用了新生代的对象。那么此时如果要触发G1的YGC,怎么才能找到这个老年代的对象呢?如果找不到这个老年代的对象,就没办法找到它引用了哪些新生代对象。

由于YGC针对的是整个新生代的空间,也就是会选择所有新生代Region,拿到GC Roots,然后遍历整个新生代。所以如果找不到老年代对新生代的引用关系,垃圾回收时就可能误操作。要么多清理要么少清理,不管是多清理或者少清理,其实都比较麻烦。如果多清理了,系统就会直接报错。如果少清理了,垃圾对象占用新生代,可能会更加频繁GC。所以这个跨代引用关系是必须知道的。

应该怎么获取这个跨代引用关系呢?想要获取这些引用关系,那么就要找到哪些老年代的对象引用了新生代的对象,也就是要找到老年代里引用了新生代对象的那些对象。

最简单的方式就是直接把老年代也遍历一遍来看看引用关系。但是此时做的是新生代的回收,却要把老年代也遍历一遍,就不合适了。不仅标记时间长,且遍历老年代,从分代隔离回收的思路来看也不合适。那么应该怎么记录跨代的引用关系?

图片

(3)怎么记录跨代的引用关系—RSet记忆集

一.记忆集会通过记录跨代的引用关系来避免遍历整个分代如老年代

举个例子:在G1中,如果老年代的对象引用了新生代的对象,那么直接针对被引用对象开辟一块内存,用来存储到底是谁引用了该对象。

图片

当准备遍历新生代所有对象时,直接把这块内存里的老年代对象,也加入到GC Roots中,然后进行遍历。这样就能避免遍历整个老年代,而且从效率和分代隔离角度都非常合理。

图片

二.记忆集的维度应该是什么

是针对新生代和老年代各创建一个?还是针对Region,每个Region都创建一个?

G1会为每个Region都创建一个RSet记忆集,G1的每个RSet记忆集都会用来存储对应Region里所有对象的引用关系。

G1针对Region来创建记忆集的原因如下:

原因一.老年代、新生代、大对象的Region在GC后可能会发生变化。

原因二.如果针对每个分代创建记忆集,因为Region不断变化可能有并发问题。

原因三.如果针对每个分代创建记忆集,老年代回收时会有额外遍历降低效率。

除了新生代的回收是需要选择所有新生代的Region之外,老年代回收需要寻找性价比高的Region来回收,即选择一部分去回收。这时如果还要遍历整个老年代的记忆集来筛选引用关系,效率就会很低。

图片

总结:

G1选择RSet记忆集去记录跨代的引用关系,大大减少了不必要的遍历操作。

G1会针对Region去创建RSet记忆集,这也继续减少了不必要的遍历操作。

(4)在G1中有多少种引用关系 + 哪些需要记录

一.Region分区内部的引用关系

二.新生代Region到新生代Region有引用关系

三.新生代Region到老年代Region有引用关系

四.老年代Region到新生代Region有引用关系

五.老年代Region到老年代Region有引用关系

此时需要考虑如下问题:

G1的引用关系一共就这么几种,那么到底哪些需要记录?如果不记录,会不会造成漏标记或漏回收的情况?如果会出现这个现象,那么就必须要记录下来了。所以针对上述的几种情况,进行如下分析:

一.Region内部的引用关系

因为是内部的,不管是做新生代回收、老年代回收、还是FGC回收,在GC Roots标记的过程中,是一定可以追踪到这种引用关系的。因为被选中的Region分区,它里面的所有对象一定都会被遍历到。所以Region内部的引用关系,不需要记录;

二.新生代和新生代间的引用关系

这个和Region内部的原理是一样的。新生代GC时肯定可以全部遍历一遍,所以也不需要记录,老年代GC又和这种新生代之间的引用关系没什么相关联的地方。

三.新生代到老年代的引用关系

假如要进行新生代的回收,因为是新生代引用了老年代对象,所以即便遍历到了,新生代GC也不会去处理老年代的对象,并且这种引用关系在新生代回收过程中,也一定会被遍历到。

假如要进行老年代的回收,老年代Region需要知道谁引用了自己,不然可能会出错。但是我们知道GC回收过程是没有老年代的单独回收的,所以如果要回收老年代,肯定会带着一次新生代回收或者直接FGC的,所以也不需要记录。

四.老年代到新生代的引用关系

这个很明显是需要记录的。

五.老年代到老年代的引用关系

假如进入到Mixed回收阶段,那么此时只会选择老年代的部分Region来回收。由于只需选择部分Region进行回收,所以其他Region尽量就不要去遍历。因此需要记录老年代到老年代的引用,避免Mixed回收时遍历老年代。

(5)总结

G1中的记忆集:

一.记忆集是什么

二.标记对象怎么标记

三.跨代引用带来的问题

四.怎么解决跨代引用的问题

五.记忆集RSet里面记录了什么 + 哪些引用关系需要记录 + 哪些不需要记录

问题:

RSet里面到底是怎么存的?

G1是怎么设计引用关系存储结构的?

3.G1中的位图和卡表

(1)引入位图可以做什么 

(2)位图在对象引用方面的应用会有什么问题(描述内容很少 + 位图过大)

(3)怎么优化位图

(4)什么是卡表

(5)卡表和位图到底是什么关系

(1)引入位图可以做什么

位图就是通过"位"来描述某一块内存的状态的图,位图就是一组二进制位数据,位图的每一个二进制位的0和1都标识了其描述内容的状态。

假设有两个分区,分区1有一个对象student,分区2有一个对象score。并且student.score = score,此时这两个分区就有了引用关系了。那么问题来了,如果要找到分区1是怎么引用到分区2,应该怎么做?

一.对分区1的内存按照一个字一个字地遍历

注意:一个字节8个位,在64位的电脑上,一个字是8个字节即64位。为什么用一个字一个字地进行遍历?虽然计算机底层存储单位是字节,但由于JVM中的对象会做对齐操作,所以不需要按照字节来移动。一个字其实就是一个机器字长,即处理器的寻址位数,如32位或64位。

所以为了找到分区1是怎么引用分区2,可以按一个字一个字地遍历分区1。在遍历时再看看每个字里的值是不是指向分区2,显然这种方式效率很低。这种方式其实就是按照一定的步长去遍历整个内存,然后步长就是一个字(即8个字节=64位)。

二.优化后的遍历

还是按照一定的步长去遍历整个内存,但是步长优化成了对象的长度。所以每次遍历时,都会按照当前遍历对象的长度来进行遍历。例如student长度为512K,那么在遍历student后的一个对象时,就从512K之后开始遍历,这样效率就会高出很多。

上面这种方式虽然经过了优化,但内存越大或者Region越大时遍历操作的效率就会越低。所以也不合适,因为找一个引用关系就要遍历一次,时间复杂度是O(n)。所以就有了第三种方式,借助位图来找到分区1是怎么引用到分区2。

三.用位图记录A和B的内存块之间是否发生引用

可以考虑在位图里用一个位,描述一个字的是否被引用的状态。比如在分区A中维护一个BitMap,BitMap的每一个位记录一下分区B对A中的一个字,是否存在引用关系。如果有引用关系,BitMap的位就记为1,如果没有就记位0。

这样查分区A是否被分区B引用时的时间复杂度就是O(1),因为直接通过位图就可以找到是否引用了,而不用遍历整个分区B才能确定是否存在引用分区A。

图片

问题:这种位图的方式,会造成什么问题?

(2)位图在对象引用方面的应用会有什么问题(描述内容很少 + 位图过大)

一.描述内容太少

因为位图只有0和1这样的标记,只能证明是否有引用关系。但是JVM在垃圾回收时不单单只需要是否引用这个信息,它还需要一些其它的描述信息:比如使用量、内存起始位置等。否则GC可能因为信息缺乏,导致无法正常回收。

图片

二.增加描述位又会存在位图过大问题

对于描述信息太少的问题,只能通过增加描述位来增加描述信息。比如不用一个位来描述一个字,而是使用一个字节来描述一个字。从使用1个二进制位描述64位内存的信息,到使用8个二进制位来描述64位内存的信息,这样就可以解决描述信息太少的问题。

但是新的问题来了:此时一个位图占用的内存过大了,例如一个Region的大小是1M。如果用一个字节描述一个字,就意味着需要128K来描述一个Region(1M)。这就相当于占用12.5%的Region空间去描述另外一个Region,这个内存占比就太大了,如果是在32位的机器上,甚至需要25%的空间。

如果要描述一个Region的内存情况:内存为1M就需要用掉128K额外内存来描述。内存为1G就需要用掉128M额外内存来描述,内存为10G就需要用掉1G多的描述数据。所以使用位图的最大问题,就是额外占用的内存空间太大了。

图片

从图中可以看出来,一个字节描述一个字,还是很耗费内存的。相当于把描述能力降低了8倍,把内存空间上升了8倍。

(3)怎么优化位图

既然发现了内存占用空间太大的问题,那么就可以考虑把它优化掉。一般情况下,对象肯定不可能只占用一个字这么小的空间。所以可以直接把一个字节描述一个字的内存使用情况,优化成一个字节描述一个对象的内存使用情况。

但是对象本身又是不规则的,不一定多大,所以不能直接用对象的大小来作为描述的粒度。

最终JVM确定的是:用一个字节来描述512字节的内存,也就是用8位来描述512*8位的内存信息。这样就大大缩减在内存上的占用,使用2K的位图就可描述1M的内存空间。同时能兼顾引用关系查找的速度,不用采取遍历的方式来找到引用关系。

在查询分区A是否引用分区B时,就可以通过访问位图里快速确认是否有对应的引用关系。如果分区的大小是1M,那么只需要遍历2K的位图即可以确认,这时不用再遍历1M的Region内存块了。

图片

(4)什么是卡表

G1中的卡表其实就是前面说的位图优化,就是把整个堆内存,按照512字节的粒度,拆分成一个个card,然后一个个card组合起来就形成了一个全局的卡表。卡表中每8个二进制位描述512字节内存的引用关系、使用情况等。

图片

(5)卡表和位图到底是什么关系

卡表其实就是位图思想的一种实现方式,只是粒度不同罢了。位图使用每一个位来描述对应数据的状态,卡表使用一个字节来描述512字节内存的状态、引用等相关数据。总结来说就是,卡表是位图的增强版。

(6)总结

G1中的位图和卡表:

一.引入位图可以干什么

二.位图在对象引用方面的应用及存在的问题

三.怎么优化位图

四.什么是卡表

五.卡表和位图到底是什么关系

问题:卡表和Rset记忆集是怎么结合起来使用的?其实Rset中存储的就是卡表地址。

图片

4.记忆集和卡表有什么关系

(1)卡表和记忆集到底存储了什么

(2)记忆集和卡表是怎么关联的

(3)RSet在空间和时间上做的平衡

(1)卡表和记忆集到底存储了什么

记忆集RSet里面存储的是"谁引用了当前Region"的引用信息,G1可以通过这些引用信息去找到引用当前Region的对象。

G1通过当前Region的记忆集可以找到:引用了当前Region对象的所有对象所在的Region以及这些对象的位置。

一.记忆集的数据结构和存储结构

那记忆集里面的数据结构到底是什么?存储的数据又是什么呢?

RSet全称叫Remember Set,它是一个Set结构,RSet可以理解成一个类似于Hash表的结构。一个Region会对应一个RSet,一个RSet记录了引用其Region的其他Region的对象引用关系。

一个RSet是由一个个key-value对组成的:key是引用了当前Region(被引用方)的其他Region(引用方)的地址。value是一个数组,数组元素是引用方的对象所在的卡页在卡表中的坐标,卡页就是512字节的内存页。如下图示:

图片

也就是说,在遍历对象时,可以直接通过对象所在Region的记忆集,就能拿到所有引用了当前对象的对象所在Region的卡表数据,即卡页对应的512B内存块地址在卡表中的坐标。

所以记忆集存储的不是哪一些对象引用了当前Region,记忆集存储的是对当前Region有引用关系的对象的大概位置信息。也就是对象所在的Region地址 + 对象所在的卡页在卡表中的坐标,粒度相比对象来说会稍微大一些。

二.记忆集的存储结构和作用

一旦发现老年代的对象引用了一个新生代的Region中的对象,那么G1就会在这个新生代的Region的记忆集中维护一个key-value对。其中key是引用方对象对应的Region的地址,也就是那个老年代的对象所在Region的地址。其中value是一个数组,里面存储的数组元素是老年代对象所在卡页(512字节)在全局卡表中的坐标。

通过记忆集的key,可以快速定位Region,从而避免遍历老年代。通过记忆集的value,可以快速定位对象,从而避免遍历Region。

G1在遍历一个新生代的Region时,就能根据这个Region的记忆集,快速定位到引用该Region的对象所在的Region及这些对象所在的卡页,从而避免对老年代进行全局扫描。

三.卡表中存储了什么

G1中的卡表存储的不是引用关系信息,而是卡页的内存使用情况,比如是否使用、使用多少、GC中的状态信息、以及对应哪一块内存。

四.记忆集中存储了什么

G1中的记忆集存储的是内存之间的引用关系,也就是Region和卡页之间的映射关系。

之前介绍的卡表图:

图片

这里介绍的卡表图:

图片

因为记忆集已存储了引用关系,所以卡表的功能就是描述内存的使用。卡表会记录内存是否使用,对象位于哪个卡页,和GC过程中的状态信息。

通过记忆集可以描述出引用关系,通过卡表可以描述GC的状态信息,对象所处的内存区域,如第几个卡页。借助这些信息能快速知道内存使用情况,如是否使用、使用多少卡页等。结合记忆集和卡表,就能轻易获取引用关系和内存使用情况的详细信息。

(2)记忆集和卡表是怎么关联的

简单来说,RSet记忆集其实就是存储了一些卡表的信息。具体就是对当前Region有引用关系的对象的大概位置信息,也就是对象所在的Region地址 + 对象所在的卡页在卡表中的坐标。

下面举个例子:

一.新生代Region2中有一个对象Obj2。

二.老年代Region1和老年代Region5里有Obj1、Obj3、Obj4、Obj5,其中Obj1、Obj3、Obj4、Obj5均引用了Obj2这个对象。

三.其中Obj3、Obj4位于老年代Region1的同一个卡页CardPage里,该卡页CardPage在全局卡表的坐标是666。

四.Obj1和Obj5分别位于老年代Region5的两个卡页CardPage里面,这两个卡页CardPage在全局卡表的坐标为:1788和1756。

对象的引用关系以及对象所在的卡表坐标在卡表中的信息如下所示,通过这个卡表,我们就能准确的找到对象所在的CardPage页。如下图示:

图片

Region2的RSet存储的信息,如下图示:

图片

当G1在进行新生代垃圾回收时,就可以通过新生代Region2的记忆集RSet,结合新生代的GC Roots,来找到新生代的所有引用关系,以及老年代跨代引用的所有引用关系。这样就不会漏找,同时不需要在进行新生代回收时去遍历整个老年代。

问题:按照RSet的这个存储逻辑,也不能在回收时直接定位到老年代引用新生代的对象到底有哪些。

(3)RSet在空间和时间上做的平衡

一.如果没有RSet + 卡表

那么回收新生代时是需要遍历整个老年代的对象的,非常耗时。为了解决这个问题才引入了RSet + 卡表这两个额外的存储结构。

二.如果把RSet + 卡表的存储粒度,按每一个对象来存储,也不太合适

因为RSet + 卡表会占用很大的额外内存。

三.所以RSet + 卡表的机制是对空间和时间做了平衡后设计出来的结果

按照该设计,每次找老年代的引用对象时只需遍历一下对应的卡页即可。而且还可以结合对象的长度去做遍历,也就是按照一个对象长度的内存空间为步长去遍历来提升性能。

(4)总结

G1中的记忆集和卡表关系:

一.卡表和记忆集到底存储了什么

二.卡表和记忆集之间是怎么关联的

三.RSet在空间和时间上做的平衡

大量的引用关系变更时,更新RSet有可能会产生并发安全问题,以及并发性能问题。所以RSet什么时候维护、以怎样的方式维护,非常重要。

问题:Remember Set的数据到底是怎么维护的?什么时候维护?如果出现了大量的引用关系变更,怎么处理?大量引用关系变更的时候会不会出现性能问题,性能问题应该怎么优化?

RSet是一个垃圾回收的时候非常重要的数据,所以引用关系变更时,为了保证准确性,是否需要更改这个RSet?

5.RSet记忆集是怎么更新的

(1)引用关系发生改变时RSet是否需要更新

(2)每次引用关系变更都更新

(3)应该什么时候处理Rset的更新操作

(4)G1的脏数据队列—异步消费更新RSet

(1)引用关系发生改变时RSet是否需要更新

如果有对象引用变更,比如新增对象的引用,或失去对象的引用。此时RSet肯定是要更新的,否则就会出现回收时引用关系错误。回收时可能就会出现把正常对象当垃圾对象,或把垃圾对象当正常对象。所以在引用关系变更时,一定需要更新Rset。

例子如下:

一.新生代Region2中有一个对象Obj2。

二.老年代Region1和老年代Region5里有Obj1、Obj3、Obj4、Obj5,其中Obj1、Obj3、Obj4、Obj5均引用了Obj2这个对象。

三.其中Obj3、Obj4位于老年代Region1的同一个CardPage里面,这个CardPage在全局卡表的坐标位是666。

四.Obj1和Obj5分别位于老年代Region5的两个CardPage里面,这两个CardPage在全局卡表的坐标是1788和1756。如果Obj5这个对象,执行了Obj5.Obj2 = null,那么此时Obj5对Obj2的引用关系就会消失。此时引用关系发生变更,肯定是需要更新Rset的,那么到底应该怎么更新?更新的策略又是什么?如下图示:

图片

所以接下来有两个问题:

一.每次更新引用关系,RSet一定要立即更新吗?如果不立即更新会有什么影响?

二.如果立即更新会产生什么问题?如果不立即更新的话,又有什么合理的方案?

(2)每次引用关系变更都更新

由于一个Region会有一个RSet,一旦发生同一个对象或同一个Region内对象的引用关系变更,那么是会出现并发访问同一个RSet的情况的。

所以第一个需要解决的问题就是并发问题,而解决并发问题的最好方式要么是串行化处理,要么是分而治之,时间不敏感可用异步队列。

问题一:假如使用串行化处理,那么每次引用关系变更,是否可以立即更新RSet?

如果每次更新引用关系后立即去加一把锁,然后修改RSet,则肯定是能保证RSet的准确性的。如下图示,此时Obj5这个对象所在的CardPage已经从新生代Region2的RSet中移除了。

图片

但是这种方式最大的问题是:虽然保证了准确性,但对象的创建和更新等一系列操作是非常频繁的。如果用这种加锁的方式更新RSet,那么肯定会造成性能问题,毕竟一个Region共享一个RSet。每一次对象的创建和更新等操作,肯定都可能去访问这个RSet,这样对RSet加锁进行串行化处理,必然会造成整体性能的下降。

所以不能采取串行化处理的方式,那么应该采取什么方式来处理呢?

问题二:假如使用分治思想来处理,那么是否能保证系统运行的效率?

使用分治思想,那么就需要把一个RSet分割开来,交给多个线程去处理,同时在最后进行汇总。

但是在这个场景下,肯定不能这么做。

原因一:首先分治操作需要把RSet分开,那么按照什么维度分开呢?

原因二:分治思想因为要分开处理,最后汇总,所以肯定要通过几个线程专门来处理,并且这几个线程最好是固定的,那么这些线程是不是都是系统的工作线程?系统的工作线程专门用来做分治是否合适?如果专门新开几个线程,但本来就有上千个Rset,现在还要分治,那要增加多少线程?

原因三:分治处理后的结果如何汇总、何时汇总?

所以也不能采取分治思想来处理,那么还有没有什么其他的方案比较合适呢?

(3)应该什么时候处理Rset的更新操作

问题:RSet的数据什么时候才会用到的?

JVM最核心的两大操作就是对象分配和垃圾回收,下面分析JVM的这两个核心过程。

一.分配对象时是否需要用到RSet的数据

分配对象时是并不需要用到RSet的数据的。因为G1在分配一个对象时,只需要看内存够不够,剩余空间是否能够分配一个对象。分配完成后,直接更新一下内存的使用情况即可,并不需要借助RSet。

二. 垃圾回收时是否需要用到RSet的数据

RSet本身就是为了垃圾回收时更加方便、不需要遍历空间而设计的,所以在垃圾回收时肯定需要用到RSet。

那么可以得出一个结论:在大多数时间里,RSet即使需要更新而没有把它更新,其实也无所谓,因为不影响程序运行。

如下图示,当新创建一个对象Obj6时,完全和RSet没有任何关系,直接创建即可。

图片

只有在垃圾回收时,才需要使用最新的RSet,否则就会出错,如下图示:

图片

如果此时需要垃圾回收,而Rset没有更新最新的引用关系,肯定会出错。上图RSet里的值,很明显1756这个值是需要剔除的。因为Obj5已经执行过Obj5.obj2 = null操作了,它已经不引用Obj2了。所以为了保证RSet最新,这时GC的遍历过程可能会有额外的遍历。

因此这个更新RSet的操作应该在垃圾回收前完成,既然是在垃圾回收之前完成,那在程序运行到需要垃圾回收的长时间里,就可以通过后台线程把这个更新的事情处理好即可。

(4)G1的脏数据队列—异步消费更新RSet

基于前面的分析,可知只要在垃圾回收前把所有引用的更新处理好即可。而在垃圾回收前,G1是有大量的时间慢慢更新这个引用关系的。

所以G1就设计了一个叫做DCQ(Dirty Card Queue)的队列。在每次有引用关系变更时,就把这个变更操作,发送一个消息到DCQ里,然后有一个专门的线程去异步消费。

这样G1的回收线程在读RSet时就能读到正确数据,同时不影响系统工作,如下图示:

图片

(5)总结

RSet记忆集的更新问题:

一.如果引用关系发生了改变,RSet是否需要更新,应该怎么更新

二.每次引用关系变更都需要更新RSet

三.Rset的更新操作应该在垃圾回收前完成

四.G1的脏数据队列—异步消费更新RSet

问题:异步消费是谁在消费?应该给多少个线程才不会损耗CPU大量的资源?如果到了最后垃圾回收时还是没有消费完毕,应该怎么办?

6.DCQ机制的底层原理是怎样的

(1)异步消费是谁在消费

(2)多个线程可能会消费同一个DCQ里的数据 

(3)DCQ的长度和DCQ的并发消费问题

(4)DCQ里的数据到底是怎么写进去的(写屏障)

概述:

DCQ机制:

一.DCQ有多少个

二.谁来消费DCQ

三.消费线程的数量是怎么确定的

四.DCQ到底是怎么写进去的

RSet的更新并不是同步完成的。G1会把所有的引用关系都先放入一个队列中,称为DCQ,然后使用线程来消费这个DCQ队列以完成更新。正常会有G1ConcRefinementThreads个线程处理,除了Refine线程会更新RSet,GC线程或Mutator(Java的应用线程Mutator)也可能会更新RSet。DCQ会通过DCQS来管理,为了能并发地处理,每个Refine线程只负责DCQS中的某几个DCQ。

(1)异步消费是谁在消费

G1有个Refine并发线程池,线程数默认是G1ConcRefinementThreads+1。Refine线程的主要工作,就是去消费DCQ里的消息,然后去更新RSet。

当然Refine线程还有其他的作用。Refine线程还会进行新生代分区的抽样工作,动态扩展新生代。Refine线程会在满足响应时间的前提下,根据抽样数据(GC时间、垃圾数量等),去调整新生代的Region个数。

总结:Refine线程的主要工作是管理RSet,即消费DCQ里的消息然后更新RSet里的引用关系。Refine线程中会有一个线程一直进行新生代分区的抽样。

图片

问题:如果Refine线程忙不过来了怎么办?DCQ中的变更消息很多,Refine线程超负荷工作无法全部消费变更消息会怎样?

(2)多个线程可能会消费同一个DCQ里的数据 

平常只会有少量的Refine线程去消费DCQ里的数据。当系统并发非常高的时候,DCQ里面的变更消息可能会非常非常多,那么此时Refine线程肯定是忙不过来的。这时就需要有多个Refine线程一起处理引用变更数据,甚至还有可能会有其他线程参与进来一起处理。

我们可以指定Refine线程的数量,Refine线程的默认数量是G1ConcRefinementThreads + 1。

图片

如果实在忙不过来,那么就只能在指定参数时多设置几个Refine线程了。因为G1一般是大内存、多核机器,所以设置4-5个线程其实也无所谓。

图片

如果Refine线程已经够多了,此时还是忙不过来,那么G1就会借助系统的工作线程。即创建完对象后,这个线程很可能没有去等着接收其他请求,而是帮忙去消费DCQ了。

图片

(3)DCQ的长度和DCQ的并发消费问题

如果一个队列无限长无限增加,单独一个DCQ,很多线程都往里面写。由于DCQ的消费必须保证顺序性,而且只有一个DCQ。那么不管是写入还是消费,都会有并发写和并发消费的问题的,所以G1设计了二级缓存来解决并发冲突的问题。

一.第一级缓存是在线程这一层

每一个工作线程都会关联一个DCQ,一个DCQ对应一个Region,多个工作线程可能会对应同一个DCQ。每个线程在执行引用更新操作时,都会往自己那个DCQ里写入变更信息。如果没有第一级缓存,那么所有工作线程都会关联到一个DCQ。DCQ长度默认是256,如果一个DCQ写满,就重新申请一个新的DCQ,并把这个满了的DCQ提交到第二级缓存DCQ Set。

二.第二级缓存是在一个DCQ Set里面

一般叫这个二级缓存为DCQS,在线程持有的DCQ满了以后,会把DCQ提交到DCQS中去。

这样就解决了并发写的问题,因为每个线程只写自己持有的DCQ即可,写满了就提交到DCQS,顶多这时会加一个锁。

此外,由于一个DCQ会对应一个Region,所以按照时间去消费DCQS里的DCQ时,不需要考虑是否会顺序消费的问题。

图片

当Refine线程实在是忙不过来时是怎么处理的?由于Refine线程是直接从DCQS取DCQ去消费的,那么如果Refine线程忙不过来,也就意味着:DCQS已经不能再放下更多满了的DCQ了。

此时工作线程的DCQ满时,就会去判断能否提交到DCQS。如果发现不能提交,工作线程就会自己去处理这个DCQ,然后更新RSet。

图片

(4)DCQ里的数据到底是怎么写进去的(写屏障)

G1会给每个变更操作前都加了一层写屏障,注意这个写屏障和内存屏障可不一样,这个写屏障是类似于增强代码或者AOP切面的一个概念。

写屏障的意思是:我们在修改了内存值时(就是往内存里写东西时),额外执行的一些操作。比如判断是否需要写到DCQ(新生代与新生代的引用就不需要写),比如判断本次的写入操作是否改变了引用,比如发送一条消息到DCQ等。这么一层加强代码,就是所谓的写屏障。

那么这层加强代码到底由什么用呢?作用其实很大,因为不是任何写内存的操作都会改变引用关系什么的。如果没有改变引用关系,就不需要写这么一条数据到DCQ里面了。这样写屏障就可以大大减少后续Refine线程处理DCQ的压力,同时写屏障也避免DCQ快速填满导致Refine线程被快速启动。

所以这个写屏障的作用主要有两点:第一过滤掉不需要写的引用变更操作,比如新生代到新生代的引用、同一分区内的引用等这些引用是不用写的,第二就是把正常的变更数据写入到DCQ里。

(5)总结

DCQ机制:

一.谁来消费DCQ

二.消费线程的数量是怎么确定的

三.DCQ里的数据到底是怎么写进去的

问题:DCQS到什么时候算满?多少个Refine线程去处理DCQS才合适?如果到了GC时,Refine线程还是没有处理完,应该怎么办?

7.DCQS机制及GC线程对DCQ的处理

(1)DCQS是什么

(2)DCQS的白绿黄红四个挡位

(3)如果DCQS直到GC时还没处理完会怎样

概述:

DCQS机制:

一.DCQS是什么

二.DCQS是怎么更新的

三.如果DCQ没有处理完毕就进入GC会怎么样

JVM声明了一个全局的静态变量DCQS,DCQS里面存放的是DCQ。为了性能考虑,所有处理引用关系的线程共享一个DCQS。每个线程(Java的应用线程Mutator)在初始化时都会关联这个DCQS,每个线程都会关联一个DCQ。

DCQ的最大长度默认256,最多存放256个引用关系对象。在本线程中如果产生新的对象引用关系,则把引用者放入DCQ队列中。当本线程的DCQ队列满256个时,就把这个DCQ队列放入到DCQS中。DCQS可以被所有线程共享,所以放入DCQ时需要加锁。而DCQ的处理则是通过Refine线程来处理。

(1)DCQS是什么

G1设计了一个二级缓存来解决DCQ写入引用关系变更数据时的并发冲突。第一层缓存是在线程这一层,每一个工作线程都会关联一个DCQ。每个线程在执行引用更新操作时,都会往自己那个DCQ里写入变更信息。

DCQ的长度默认是256,如果写满了,就重新申请一个新的DCQ,并把这个满了的DCQ提交到第二级缓存,也就是一个DCQ Set里面去。

这个二级缓存叫DCQS,所以所谓的DCQS其实不过就是一个存放DCQ的地方。

(2)DCQS的白绿黄红四个挡位

一.设置Refine线程个数要考虑的问题

DCQS肯定是有上限的。当达到一定的阈值不能再提交时,工作线程就得自己去处理了。这时说明系统负载已经很重了,而且系统的运行速度可能会比较慢,因为工作线程要去处理DCQ更新RSet的引用关系。

负载很重时,G1就要考虑应该启动多少个线程:避免出现负担很重的情况、避免大量Refine线程导致CPU资源占用问题。

那么应该怎么设计Refine线程的个数?

如果启用Refine线程过多,则可能会导致程序整体性能一直都不高。如果启用Refine线程过少,则可能会导致运行一段时间,DCQS就满了。从而导致工作线程要做一些额外的更新RSet操作,这时整体性能也不高。

图片

二.G1对DCQS的数量进行了四个区域划分

那么到底Refine线程的数量怎么设置?什么时候应该用多少个线程?针对这个问题G1对DCQS的数量进行了四个区域划分。

有三个参数来划分这四个区域:

G1ConcRefinementGreenZone

G1ConcRefinementYellowZone

G1ConcRefinementRedZone

这三个值默认都是0,如果没有设置,G1就会自动推断这个三个值。

区域一.白区:[0, green)

如果在这个区域,则Refine线程不会去处理DCQ,只会进行新生代抽样。

区域二.绿区:[green, yellow)

Refine线程开始启动,并根据DCQS的大小来计算启动Refine线程的个数。

区域三.黄区:[yellow, red)

所有Refine线程都会参与到DCQS的处理。

区域四.红区:[red, 正无穷)

所有Refine线程以及系统工作线程都会参与到DCQ的处理。

三.Refine线程个数的推断

Refine线程的个数可以通过参数来设置:

如果没有设置,就和ParallelGCThreads相等。如果ParallelGCThreads没有设置,则根据CPU核数来自动推断有多少个。

推断策略为:

如果核数 小于等于 8,则ParallelGCThreads = CPU核数;如果核数 大于 8,则ParallelGCThreads = 8 + (核数 - 8) * 5 / 8;如果没有设置Refine个数,则Refine总个数 = ParallelGCThreads;如果设置了Refine个数为x,那么Refine的实际值为x + 1;因为会有1个Refine线程在做抽样,另外根据DCQS的size来启动x个线程;

注意:G1会一直有1个Refine线程在处理抽样,下面说的Refine线程特指处理DCQ。

G1会根据DCQS的大小来启动不同的Refine个数,可以理解为让每个Refine线程处理多少个DCQ。比如DCQS大小为9,步长为3,那么就会启动3个Refine线程,当然步长也可以通过参数设置。如果不设置步长,则会自动推断参与处理DCQ的Rrefine线程个数。

DCQS的几个阈值会和ParallelGCThreads有关系:Green = ParallelGCThreads,Yellow = 3 * Green,Red = 6 * Green。

假如总共4个Refine线程处理DCQS(还有一个线程在处理新生代的抽样),那么绿黄红按步长(步长和Refine总线程数量相等)来计算就是[4, 12, 24]:

DCQS在少于4个DCQ时,不启动Refine线程;

在大于等于4个小于9个DCQ时启动1个线程;

在大于等于9个DCQ时,启动第二个线程;

在大于等于11个DCQ时,启动第三个线程;

在达到24个DCQ时工作线程也在处理DCQ;

图片

(3)如果DCQS直到GC时还没处理完会怎样

如果DCQ数量在小于绿区的阈值时,是没有Refine线程在处理的。如果DCQS直到GC时还没有处理完,这时也不需要Refine线程处理了。

G1为了保证运行过程中的效率,会在GC时由GC线程来处理这些DCQ。因为GC线程也是会有多个的,在STW时处理这些DCQ,速度也不会很慢。

总结:启动了Refine线程,然而DCQ在GC开始时还是没有处理完。这时就会由GC线程参与处理没有完成处理的DCQ,直到全部处理完成,然后才会进行下一步GC操作。

相关文章:

G1原理—3.G1是如何提升垃圾回收效率

大纲 1.G1为了提升GC的效率设计了哪些核心机制 2.G1中的记忆集是什么 3.G1中的位图和卡表 4.记忆集和卡表有什么关系 5.RSet记忆集是怎么更新的 6.DCQ机制的底层原理是怎样的 7.DCQS机制及GC线程对DCQ的处理 提升G1垃圾回收器GC效率的黑科技 G1设计了一套TLAB机制 快速…...

IOS界面传值-OC

1、页面跳转 由 ViewController 页面跳转至 NextViewController 页面 &#xff08;1&#xff09;ViewController ViewController.h #import <UIKit/UIKit.h>interface ViewController : UIViewControllerend ViewController.m #import "ViewController.h" …...

C# SQL ASP.NET Web

留学生的课程答疑 按照要求完成程序设计、数据库设计、用户手册等相关技术文档&#xff1b; 要求 1. 计算机相关专业&#xff0c;本科以上学历&#xff0c;至少有1年以上工作经验或实习经历。 2. 熟练掌握WinForm程序开发&#xff0c;或ASP.NET Web编程。 3. 熟悉C#中网络…...

asp.net core webapi 并发请求时 怎么保证实时获取的用户信息是此次请求的?

对于并发请求&#xff0c;每个请求会被分配到一个独立的线程或线程池工作线程上。通过 HttpContext 或 AsyncLocal&#xff0c;每个线程都能独立地获取到它自己的上下文数据。由于这些数据是与当前请求相关的&#xff0c;因此在并发请求时不会互相干扰。 在并发请求时&#xf…...

软件23种设计模式完整版[附Java版示例代码]

一、什么是设计模式 设计模式是在软件设计中反复出现的问题的通用解决方案。它们是经过多次验证和应用的指导原则,旨在帮助软件开发人员解决特定类型的问题,提高代码的可维护性、可扩展性和重用性。 设计模式是一种抽象化的思维方式,可以帮助开发人员更好地组织和设计他们…...

FPGA 20 ,FPGA按键消抖功能解析与实现

目录 前言 一. 具体场景 二. 消抖方法...

基于单片机的无线气象仪系统设计(论文+源码)

1系统方案设计 如图2.1所示为无线气象仪系统设计框架。系统设计采用STM32单片机作为主控制器&#xff0c;结合DHT11温湿度传感器、光敏传感器、BMP180气压传感器、PR-3000-FS-N01风速传感器实现气象环境的温度、湿度、光照、气压、风速等环境数据的检测&#xff0c;并通过OLED1…...

OA系统如何做好DDOS防护

OA系统如何做好DDOS防护&#xff1f;在数字化办公蔚然成风的当下&#xff0c;OA&#xff08;办公自动化&#xff09;系统作为企业内部管理与协作的神经中枢&#xff0c;其安全性和稳定性直接关系到企业的日常运营效率、信息流通效率以及长远发展。OA系统不仅承载着企业内部的日…...

java_单例设计模式

什么是设计模式 什么是单例设计模式 单例设计模式——饿汉式 虽然你没有使用这个对象实例&#xff0c;但是它也帮你创建了&#xff01;容易造成对象的浪费 對象&#xff0c;通常是重量級的對象, 餓漢式可能造成創建了對象&#xff0c;但是沒有使用. package com.hspedu.singl…...

比较分析:Windsurf、Cody、Cline、Roo Cline、Copilot 和 通义灵码

随着人工智能技术的快速发展&#xff0c;开发者工具变得越来越智能化&#xff0c;特别是在代码生成、辅助编程等领域&#xff0c;市面上涌现了多种 AI 驱动的工具。本文将从开源性、集成能力、功能覆盖范围、支持的编程语言、生态兼容性、成本、学习曲线、响应速度、离线支持以…...

vue3后台系统动态路由实现

动态路由的流程&#xff1a;用户登录之后拿到用户信息和token&#xff0c;再去请求后端给的动态路由表&#xff0c;前端处理路由格式为vue路由格式。 1&#xff09;拿到用户信息里面的角色之后再去请求路由表&#xff0c;返回的路由为tree格式 后端返回路由如下&#xff1a; …...

C#版 软件开发6大原则与23种设计模式

开发原则和设计模式一直是软件开发中的圣经, 但是这仅仅适用于中大型的项目开发, 在小型项目的开发中, 这些规则会降低你的开发效率, 使你的工程变得繁杂. 所以只有适合你的才是最好的. 设计模式六大原则1. 单一职责原则&#xff08;Single Responsibility Principle&#xff0…...

后端Java开发:第十三天

第十三天&#xff1a;继承 - 面向对象的核心概念 欢迎来到第十三天的学习&#xff01;今天&#xff0c;我们将深入探讨 Java 中的 继承&#xff08;Inheritance&#xff09;&#xff0c;这是面向对象编程的四大基本特性之一。继承是指一个类&#xff08;子类&#xff09;通过继…...

awr报告无法生成:常见分析手段

awr报告无法生成:常见分析手段 STATISTICS_LEVEL和OPEN_MODEAWR快照是否能自动生成?AWR快照能否手动生成?日志有无ORA-12751或ORA-32701报错?MMON进程是否被挂起?排查数据库隐藏参数分析快照生成错误信息分析AWR Snapshot Tracing分析AWR Table Flush是否超时STATISTICS_L…...

基础算法——差分

原理与特点 先回顾一下前缀和算法。 | arr | 1 | 3 | 7 | 5 | 6 | | ---------- | ------ | ------ | ------ | ------ | ------ | | prefix 值 | 101 | 134 | 13711 | 137516 | 1375622 |前缀和的特点是前面的相加prefix(i)sum(i-1)arr(i)。那么差分数组diff就如下面的形式 |…...

[ LeetCode 75 ] 283 移动零(JavaScript)

283 移动零 题目描述解题思路步骤解析时间和空间复杂度代码实现 题目描述 LeetCode 283 移动零 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操…...

YOLOv10改进,YOLOv10添加HAttention注意机制用于图像修复的混合注意力转换器,CVPR2023,超分辨率重建

摘要 基于Transformer的方法在低层视觉任务中表现出色,例如图像超分辨率。然而,作者通过归因分析发现,这些网络只能利用有限的空间范围的输入信息。这意味着现有网络尚未充分发挥Transformer的潜力。为了激活更多的输入像素以获得更好的重建效果,作者提出了一种新型的混合…...

VS调试MFC进入系统源代码配置

调试MFC代码有时候能进入MFC的源代码,有时候不能.之前一直没有深入研究.后面经过查资料发现每次调试必能进入源代码的配置.很简单,只需要3步. 1.打开工具->选项->调试->符号,勾选Microsoft符号服务器. 2.打开项目->属性->配置属性->常规,MFC的使用修改成&qu…...

C# 告别FirstOrDefault

一、开篇&#xff1a;FirstOrDefault 的 “江湖地位” 在 C# 编程的世界里&#xff0c;FirstOrDefault 可谓是一位 “常客”&#xff0c;被广大开发者频繁地运用在各种项目场景之中。无论是 Windows 窗体应用程序&#xff0c;需要从数据集中检索第一条记录&#xff0c;或是满足…...

图像处理|腐蚀操作

在计算机视觉与图像处理中&#xff0c;腐蚀操作&#xff08;Erosion&#xff09;是形态学操作的一种。形态学操作广泛应用于二值图像中&#xff0c;主要用于分析和提取图像中的结构信息。腐蚀操作是这类操作中最常见的一种&#xff0c;用来对图像进行“收缩”处理&#xff0c;消…...

全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之循环结构(应用)

实战训练1—报数游戏 问题描述&#xff1a; 小明和小鹏玩报数游戏&#xff0c;小明按1∼20 报数&#xff0c;小鹏按1∼30报数。若两人同时开始&#xff0c;并以同样的速度报数&#xff0c;当两人都报了1000个数时&#xff0c;同时报相同数的次数是多少呢&#xff1f; 输入格…...

140.WEB渗透测试-信息收集-小程序、app(11)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;139.WEB渗透测试-信息收集-小程序、app&#xff08;10&#xff09; 3.直接有app 直接拿…...

《新闻大厦抢先版》V0.18.105+Dlcs官方学习版

《新闻大厦抢先版》官方版https://pan.xunlei.com/s/VODaeUn3v-ZWVvvmUMfo5AqWA1?pwdnhpz# 建造并不断优化新闻大楼&#xff0c;保障员工权益并及时赶上周日的印刷交期&#xff01; 招募并管理不同职业以登上成功的阶梯&#xff1a;记者、摄像师、勤杂工&#xff0c;除此以外…...

【Uniapp-Vue3】Prop校验与prop默认值用法及循环遍历数组对象

一、prop校验 如果我们在想要限制prop的类型&#xff0c;就可以在接收prop的时候对接收类型进行限制&#xff1a; defineProps({ 属性名:{ type:类型 } }) 需要注意类型的首字母大写 但是设置了传入参数类型限制并不能严格限制&#xff0c;只会在后台进行提示&#xff1a; 二、…...

Android Studio创建新项目并引入第三方jar、aar库驱动NFC读写器读写IC卡

本示例使用设备&#xff1a;https://item.taobao.com/item.htm?spma21dvs.23580594.0.0.52de2c1bbW3AUC&ftt&id615391857885 一、打开Android Studio,点击 File> New>New project 菜单&#xff0c;选择 要创建的项目模版&#xff0c;点击 Next 二、输入项目名称…...

Spring Boot | 基于MinIO实现文件上传和下载

关注&#xff1a;CodingTechWork 介绍 在现代的 web 应用中&#xff0c;文件上传和下载是常见的需求。MinIO 是一个开源的高性能分布式对象存储服务&#xff0c;可以用来存储和管理大量的非结构化数据&#xff0c;如图片、视频、日志文件等。本文将介绍如何在 Spring Boot 应用…...

【DNS 阿里云,域名解析,解析到IP的指定端口】

- 进入 阿里云域名解析界面 - 点击 解析设置 - 添加记录 1.添加一条 A/AAAA 类型解析你的服务器的IP地址&#xff08;不需要带端口号&#xff0c;这条解析只是起到中转作用&#xff09; 示例&#xff1a;主机记录&#xff1a;aa.bb.com 记录值&#xff1a;xxx.xxx.xxx.xxx (…...

力扣经典二分题:4. 寻找两个正序数组的中位数

题目链接&#xff1a;4. 寻找两个正序数组的中位数 - 力扣&#xff08;LeetCode&#xff09; 一、题目分析 这道题目是让我们在 两个正序的数组中寻找中位数已知两个数组的大小分别是&#xff1a;int m nums1.size(),n nums2.size();中位数性质1&#xff1a;中位数左侧元素 …...

Java Web开发进阶——Spring Boot与Spring Data JPA

Spring Data JPA 是 Spring 提供的一种面向数据访问的持久化框架&#xff0c;它简化了 JPA 的实现&#xff0c;为开发者提供了一种快速操作数据库的方式。在结合 Spring Boot 使用时&#xff0c;开发者能够快速完成数据库访问层的开发。 1. 介绍Spring Data JPA 1.1 什么是Spr…...

PySpark用sort-merge join解决数据倾斜的完整案例

假设有两个大表 table1 和 table2 &#xff0c;并通过 sort-merge join 来解决可能的数据倾斜问题。 from pyspark.sql import SparkSession from pyspark.sql.functions import col# 初始化SparkSession spark SparkSession.builder.appName("SortMergeJoinExample&quo…...

【2025 Rust学习 --- 11 实用工具特型01】

清理特型Drop 当一个值的拥有者消失时&#xff0c;Rust 会丢弃&#xff08;drop&#xff09;该值。丢弃一个值就必须释放 该值拥有的任何其他值、堆存储和系统资源。 丢弃可能发生在多种情况下&#xff1a; 当变量超出作用域时&#xff1b;在表达式语句的末尾&#xff1b;当…...

关于Linux PAM模块下的pam_listfile

讲《Linux下禁止root远程登录访问》故事的时候&#xff0c;说好会另开一篇讲讲pam_listfile。我们先看看pam_listfile的man文档怎么介绍的。 下面这些就好比人物的简介&#xff0c;甚是恼人&#xff1b;让人看得不明就里&#xff0c;反正“他大舅他二舅都是他舅”。可以直接跳…...

根据中文名称首字母进行分组

很多项目中&#xff0c;需要用到中文名称到首字母进行分组&#xff0c;例如&#xff1a;城市、游戏等等。。。 /*** 将集合数据按照汉字首字母分组排序** param list* return*/public Map<String, Object> screenManufacturer(List<Game> list) {Set<String>…...

springboot 集成 etcd

springboot 集成 etcd 往期内容 ETCD 简介docker部署ETCD 前言 好久不见各位小伙伴们&#xff0c;上两期内容中&#xff0c;我们对于分布式kv存储中间件有了简单的认识&#xff0c;完成了docker-compose 部署etcd集群以及可视化工具 etcd Keeper&#xff0c;既然有了认识&a…...

人工智能-数据分析及特征提取思路

1、概况 基于学生行为数据预测是否涉黄、涉黑等。 2.数据分析 数据分析的意义包括得到数据得直觉、发掘潜在的结构、提取重要的变量、删除异常值、检验潜在的假设和建立初步的模型。 2.1数据质量分析 2.1.1数据值分析 查看数据类型&#xff1a; 首先明确各字段的数据类型…...

设计模式 行为型 状态模式(State Pattern)与 常见技术框架应用 解析

状态模式&#xff08;State Pattern&#xff09;是一种行为型设计模式&#xff0c;它允许对象在内部状态改变时改变其行为&#xff0c;使得对象看起来好像修改了它的类。这种设计模式的核心思想是将对象的状态和行为封装成不同的状态类&#xff0c;通过状态对象的行为改变来避免…...

Android 系统签名 keytool-importkeypair

要在 Android 项目中使用系统签名并将 APK 打包时与项目一起打包&#xff0c;可以按照以下步骤操作&#xff1a; 步骤 1&#xff1a;准备系统签名文件 从 Android 系统源码中获取系统签名文件&#xff0c;通常位于 build/target/product/security 目录下&#xff0c;包括 pla…...

ubuntu22.04 gcc,g++从10.5切换到低版本9.5

一、安装gcc-9.5 mkdir gcc cd gcc sudo apt-get download $(apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances --no-pre-depends gcc-9 | grep -v i386 | grep "^\w") sudo dpkg -i *.deb sudo…...

Microsoft 已经弃用了 <experimental/filesystem> 头文件

#define _CRT_SECURE_NO_WARNINGS 1 #define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING 1 //Microsoft 已经弃用了 <experimental / filesystem> 头文件&#xff0c;并计划在将来移除它。取而代之的是 C17 标准引入的 //<filesystem> 头文件&#xf…...

git 提交命令记录

1.已有本地和远程仓库 查看仓库远程地址: git remote -v 大量提交 git add . git commit -m "提交说明" git push 之后输入用户名密码 删除文件 git rm 文件名 替代git add 后面一样 2.全新提交 新建远程仓库 git init touch README.md git add . …...

Unity + Firebase + GoogleSignIn 导入问题

我目前使用 Unity版本&#xff1a;2021.3.33f1 JDK版本为&#xff1a;1.8 Gradle 版本为&#xff1a;6.1.1 Firebase 版本: 9.6.0 Google Sign In 版本为&#xff1a; 1.0.1 问题1 &#xff1a;手机点击登录报错 apk转化成zip&#xff0c;解压&#xff0c;看到/lib/armeabi-v…...

深度学习的加速器:Horovod,让分布式训练更简单高效!

什么是 Horovod&#xff1f; Horovod 是 Uber 开发的一个专注于深度学习分布式训练的开源框架&#xff0c;旨在简化和加速多 GPU、多节点环境下的训练过程。它以轻量级、易用、高性能著称&#xff0c;特别适合需要快速部署分布式训练的场景。Horovod 的名字来源于俄罗斯传统舞…...

AI刷题-异或编码、拼凑单词 chi

目录 一、异或编码 问题描述 测试样例 解题思路&#xff1a; 问题理解 解题思路 数据结构选择 算法步骤 最终代码&#xff1a; 运行结果&#xff1a; 二、拼凑单词 chi 问题描述 测试样例 解题思路&#xff1a; 问题理解 数据结构选择 算法步骤 最终代码&a…...

xml简介

目录 基本语法特点及应用场景一个简单示例 xml&#xff08;全称eXtensible Markup Language&#xff09;是一种用于存储和传输数据的标记语言&#xff0c;跨平台并且跨语言&#xff0c;xml内容较多&#xff0c;这篇文章会介绍一些基础的内容。 基本语法 xml文档通常以xml声明开…...

MYSQL------MySQL 复制MySQL Cluster 架构

MySQL 复制 安装配置 主服务器配置 首先&#xff0c;在主服务器的配置文件&#xff08;my.cnf 或 my.ini&#xff09;中添加以下基本配置&#xff1a; [mysqld] server-id 1 log-bin /var/log/mysql/mysql-bin.logserver-id&#xff1a;为服务器分配唯一的标识&#xff0…...

【人工智能】 用Python构建图像分类模型:从TensorFlow到PyTorch的全面指南

随着深度学习在计算机视觉领域的迅猛发展&#xff0c;图像分类作为其核心任务之一&#xff0c;受到了广泛的关注。本文旨在详细介绍如何使用Python构建图像分类模型&#xff0c;从TensorFlow到PyTorch两个主流深度学习框架进行全面对比与实践。文章首先回顾了图像分类的基本概念…...

计算机网络 笔记 数据链路层 2

1,信道划分&#xff1a; (1)时分复用TDM 将时间等分为“TDM帧”&#xff0c;每个TDM帧内部等分为m个时隙&#xff0c;m个用户对应m个时隙 缺点&#xff1a;每个节点只分到了总带宽的1/m,如果有部分的1节点不发出数据&#xff0c;那么就会在这个时间信道被闲置&#xff0c;利用…...

2024年度漏洞态势分析报告,需要访问自取即可!(PDF版本)

2024年度漏洞态势分析报告&#xff0c;需要访问自取即可!(PDF版本),大家有什么好的也可以发一下看看...

Apache Hop从入门到精通 第一课 揭开Apache Hop神秘面纱

一、Apache Hop是什么&#xff1f; 1、Apache Hop&#xff0c;简称Hop&#xff0c;全称为Hop Orchestration Platform&#xff0c;即Hop 工作编排平台&#xff0c;是一个数据编排和数据工程平台&#xff0c;旨在促进数据和元数据编排的所有方面。Hop让你专注于你想要解决的问题…...

Unity 人体切片三维可视化,可任意裁切切割。查看不同断层的图像。

Unity 人体切片三维可视化&#xff0c;真彩色&#xff0c;可任意裁切切割。查看不同断层的图像。 点击查看效果: 视频效果...