首页 >> 大全

JAVA的垃圾收集器与内存分配策略【一篇文章直接看懂】

2023-09-19 大全 24 作者:考证青年

内存动态分配和垃圾收集技术是JAVA和C++之间最大的区别之一

垃圾收集( ,GC)只办三件事:

对于对象回收的方法 引用计数法:

每处引用时+1,引用失效时-1,但是主流的Java虚拟机里面都没有选用引用计数算法来管理内存。

比如很难解决对象之间相互循环引用的问题

objA.=objB ;objB.=objA

objA = null; objB = null

后,objA和objB未被回收

可达性分析算法

主流的商用语言(Java\C#\Lisp)通过可达性分析( )进行判断。

基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”( Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。

可以作为GC Roots的对象:

引用概念的修改

判定对象是否存活都和“引用”离不开关系,但是过去的只有被引用和未被引用放在当今过于狭隘了。

譬如我们希望能描述一类对象:当内存空间还足够时,能保留在内存之中,如果内存空

间在进行垃圾收集后仍然非常紧张,那就可以抛弃这些对象——很多系统的缓存功能都符合这样的应

用场景。

JDK1.2后队引用进行了补充

对于对象的消亡判断

在可达性判断中,若判断为不可达的对象时,那么是处于缓刑阶段。宣告一个对象死亡需要两次标记过程

在可达性分析后,发现没有与GC ROOT相连的引用链,则会被第一次标记对象是否有必要执行()方法。若没有覆盖该方法或该方法已经被虚拟机调用过,则视为不需要执行。若判断为需要执行,则将对象放置到名为F-Queue的队列中,然后由优先级低的线程去执行。如果在()中重新与引用链上的任何一个对象建立关联则不会被回收,否则被回收。 回收方法区

主要回收两部分的内容:废弃的常量和不再使用的类型

判断废弃的常量:假如一个字符串“java”曾经进入常量池中,但是当前系统又没有任何一个字符串对象的值是“java”

判断一个类型是否属于“不再被使用的类“:

·该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。

·加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如OSGi、JSP的重加载等,否则通常是很难达成的。

·该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

Java虚拟机被允许对满足上述三个条件的无用类进行回收,这里说的仅仅是“被允许”,而并不是和对象一样,没有引用了就必然会回收。

垃圾收集算法

大致可分为 引用计数式垃圾收集( GC)和 追踪式垃圾收集( GC)

以下介绍的均为主流Java虚拟机中使用的追踪式垃圾收集的范畴

分代收集理论

由上诉的前两条理论产生的设计原则::收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。

若一个区域的大多数对象都是朝生夕灭的,则只关注如何保留少量存货。若剩下的都是难以消亡的,则集中放在一起,使用较低的频率来回收这个区域,兼顾了时间开销和内存的空间。

如今,设计者将JAVA堆划分为新生代和老年代的两个区域。但是,对象不是孤立的,对象之间会存在跨代引用,由此,引出了跨代引用假说,即存在相引用关系的两个对象,是应该倾向于同时生存或者同时消亡的。

由此,只需在新生代上建立一个全局的数据结构(记忆集, Set),该结构把老年代划分为若干小块,标识出哪块存在跨代引用。因此发生Minor GC时,只将这些小块放入GC Root中进行扫描。

不同分代的名词:

·部分收集( GC):指目标不是完整收集整个Java堆的垃圾收集,其中又分为:

■新生代收集(Minor GC/Young GC):指目标只是新生代的垃圾收集。

■老年代收集(Major GC/Old GC):指目标只是老年代的垃圾收集。目前只有CMS收集器会有单独收集老年代的行为。另外请注意“Major GC”这个说法现在有点混淆,在不同资料上常有不同所指,需按上下文区分到底是指老年代的收集还是整堆收集。

■混合收集(Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集。目前只有G1收集器会有这种行为。

·整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集。

标记-清除算法(存活率低时较好)

首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象

也可以反过来,标记存活的对象,统一回收所有未被标记的对象。

主要缺点有两个:

执行效率不稳定。若被回收的过多,则需要进行大量的标记和清除工作,导致执行效率随数量变化内存空间碎片化。在标记清楚后,出现不连续的内存空间,在分配大对象时候,需提前出发GC操作

标记-复制算法(存活率低时较好)

将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。当下,多数采用此方法回收新生代

主要缺点:

将可用的内存缩小了一半,浪费空间

改进后的Appel式回收:

把新生代分为一块较大的Eden空间和两块较小的空间,每次分配内存只使用Eden和其中一块。发生垃圾搜集时,将Eden和中仍然存活的对象一次性复制到另外一块空间上,然后直接清理掉Eden和已用过的那块空间。

标记-整理算法(存活率高时较好)

标记过程与“标记-清除”一直,但后续让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存,是移动式的,而清除是非移动式的

移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作,而清除会产生碎片化空间。

因此。是否移动对象都存在弊端,移动则内存回收时会更复杂,不移动则内存分配时会更复杂,但是因内存分配和访问相比垃圾收集频率要高得多,这部分的耗时增加,总吞吐量仍然是下降的,因此移动相对划算。

也有一种“和稀泥的方法”:平时多数时间都采用标记-清除算法,的碎片化程度已经大到影响对象分配时,再采用标记-整理算法收集一次,以获得规整的内存空间

算法细节实现 根节点枚举

在帮助下,可以快速准确的完成GC Roots枚举

在根节点枚举时,必须暂停用户线程,但枚举必须在一个保障一致性的快照中进行,即不会在分析过程中,引用关系还在变化。即使在号称停顿时间可控/几乎不停顿的CMS\G1\ZGC中,根节点枚举也是必须要停顿的。

但,目前JAVA虚拟机中采用的都是准确式垃圾收集``,故可以在停顿下来后,检查所有的上下文/全局的引用位置,可以直接得到对象引用(使用一组为的数据结构存放)。

在类加载完成后,会把对象中的偏移量对应的类型数据计算出来,在即时编译过程中,在特定位置会记录下栈里的寄存器哪些位置是引用。

故收集器在扫描时,可以直接查到引用信息,不需要从方法区/GC ROOT查找

安全点

解决如何停顿用户线程,让虚拟机进入垃圾回收状态的问题,安全点机制保证了程序执行时,在不太长的时间内就会遇到可进入垃圾收集过程的安全点

并没有为每条指令生成,只是在“特定位置”记录这些信息,成为安全点()。

GC强制要求必须执行到安全点后才能执行

抢先式(没人用了):在GC发生时中断所有用户线程,若线程不在安全点上,则恢复执行,重新中断知道跑到安全点上

主动式:GC需要中断线程时,不直接对线程操作,设置一个标志位,线程执行时主动地轮询该标志,为真时,到附近的安全点挂起。标志位置与安全点是重合的。

安全区域

对于处于Sleep/状态的线程,解决无法响应虚拟机的中断请求。由此引入安全区域(Safe )来解决。

安全区域是指能够确保在某一段代码片段之中,引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。我们也可以把安全区域看作被扩展拉伸了的安全点。

执行到安全区域的代码时

标识自己已经进入了安全区域,故GC发生时,不再管理这些线程离开安全区域时,检查是否虚拟机完成了根节点枚举,若完成则继续执行,若没完成,则等待直到接收到信号 记忆集与卡表

记忆集( Set):解决对象跨代引用的问题,记录从非收集区域指向收集区域的指针集合的抽象数据结构

最简单的实现可以用非收集区域中所有含跨代引用的对象数组来实现

但是对此方案,维护与空间成本很高,但收集器只需要通过记忆集判断是否存在某指针,所以可以使用更粗的粒度进行记录。

第三种的卡精度也成为“卡表”,是最常用的记忆集实现形式。卡表定义了记忆集的记录精度、与堆内存的映射关系等。其中的每个元素对应着一块特定大小内存块,成为卡页(Card Page),(大小通常为 2 N 2^N 2N的字节数)

在卡页中不只一个对象,只要有一个存在跨代指针,则将对应的元素值标记为1,称为元素变脏(Dirty),没有则表示为0。

GC发生->筛选脏元素->得出存在跨代指针的内存块->放入GC Root中一并扫描

写屏障

为了解决卡表元素如何维护的问题,如:何时变脏、谁把他们变脏

有其他分代区域中对象引用了本区域的对象时变脏在机器码层面中,使用写屏障,把维护卡表的动作放到每个赋值操作之中

解释执行的字节码,VM执行每条字节码指令,而在编译执行中,代码是纯粹的机器指令流了

写屏障可以看作在虚拟机层面对“引用类型字段赋值”这个动作的AOP切面,在引用对象赋值时会产生一个环形()通知,供程序执行额外的动作,也就是说赋值的前后都在写屏障的覆盖范畴内。

这边我认为AOP切面,就是与中的权限验证功能类似,在执行时判断是否可以执行。将我们原本一条线执行的程序在中间切开加入了一些其他操作一样。

在赋值前的部分为:前屏障(Pre-Write ),后的为:后屏障(Post-Write )

引用写屏障后->为所有赋值操作生成指令->写屏障增加更新卡表操作

存在伪共享(False )问题:

因为CPU的缓存系统是以缓存行(Cache Line)为单位的,当多线程修改互相独立的变量,且变量共享同一行,会彼此影响(写回、无效化、同步)->降低了性能

解决方法:1.先检查未被标记过才标记为脏 2.JDK7新增了参数,但是增加了一次判断的开销

并发的可达性分析

包含“标记”阶段是所有追踪式垃圾收集算法的共同特征,如果这个阶段会随着堆变大而等比例增加停顿时间,其影响就会波及几乎所有的垃圾收集器,因此并行收益是极大的

三色标记法:

–>由此总结出了产生对象消失问题的两条结论:

赋值器插入了一条或多条从黑色对象到白色对象的新引用;赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。

解决方案:

增量更新( ,破坏第一条):黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象了。【新增白色引用时,记录该引用,扫描结束后,以该引用的黑色为根扫描】原始快照(Snap At The ,SATB,破坏第二条):无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来进行搜索。【删除白色引用时,记录该引用,扫描结束后,以该引用的灰色为根重新扫描】

以上记录操作都是通过写屏障来实现的

经典垃圾收集器

链接的线指代两个收集器可以搭配使用。

== 不存在“万能”的收集器,只有对具体应用场景更合适的收集器==

收集器(标记-复制算法)

该收集器是一个单线程工作的收集器,在进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。

迄今为止,它依然是虚拟机运行在客户端模式下的默认新生代收集器,有着优于其他收集器的地方,那就是简单而高效(与其他收集器的单线程相比)

1.是所有收集器里额外内存消耗( )最小的

2. 对于单核/处理器较少的环境,因为没有线程交互的开销,所以可以获得最高的单线程效率

在部分微服务中,内存一般不会特别大,所以垃圾收集的停顿时间也很短

收集器(标记-复制算法)

是收集器的并行版本,除了能并行其他与收集器完全一致。

/ Old收集过程:

目前的用处:在JDK7之前遗留的系统中,只有他能与CMS(过去可以实现GC线程与用户线程同时工作的收集器)搭配使用,但是CMS作为老年代的无法与新生代的 搭配使用了,只有能搭配使用。后续也被G1收集器所代替。

·并行():并行描述的是多条垃圾收集器线程之间的关系,说明同一时间有多条这样的线程在协同工作,通常默认此时用户线程是处于等待状态。

·并发():并发描述的是垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾收集器线程与用户线程都在运行,但不一定是并行的,可能会交替运行。由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。

收集器(标记-复制算法)

达到一个可控制的吞吐量()

吞吐量 = 运行用户代码时间 运行用户代码时间 + 运行垃圾收集时间 吞吐量 = \frac { 运行用户代码时间 } { 运行用户代码时间+运行垃圾收集时间 } 吞吐量=运行用户代码时间+运行垃圾收集时间运行用户代码时间​

停顿时间越短,越适合交互频繁的程序,高吞吐量可以最高效率地利用处理器资源,适合在后台运算而不需要太多交互的分析任务

用于控制吞吐量的参数:

-XX::最大垃圾收集停顿时间-XX::直接设置吞吐量大小-XX:y:激活后不需要人工指定细节参数,通过运行情况动态调整,称为自适应的调节策略(GC )

自适应调节策略也是 收集器区别于收集器的一个重要特性。

以上皆为新生代收集器

Old收集器(标记-整理算法)

单线程收集器,主要提供给客户端模式下的虚拟机使用,在服务端模式下,作为CMS的后备方案

Old收集器(标记-整理算法)

的老年代版本,在注重吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑 加 Old收集器这个组合

CMS收集器(标记-清除算法)

CMS( Mark Sweep)一种以获取最短回收停顿时间为目标的收集器,集中在B/S系统的服务端上,较为关注服务的响应速度,带来良好的交互体验。

整个过程分为四个步骤:

初始标记(CMS mark)并发标记(CMS mark)重新标记(CMS )并发清除(CMS sweep)

初始标记只是标记GC Roots直接关联的对象,速度很快

并发标记从直接关联的对象遍历对象图,不需要停顿用户线程,并发运行。

重新标记为了修正并发标记期间,用户线程导致标记有变动的对象标记记录,时间停顿比初试标记稍长,远比并发标记短

清除阶段为了删除在标记阶段判断的已死亡的对象,不需要移动存活对象,所以与用户线程并发

因此耗时最长的并发标记和并发清除都是可以与用户线程一起工作的

并发收集、停顿,但也有如下的缺点:

对处理器的资源非常敏感:【面向并发设计的程序都对处理器资源比较敏感】,并发时,占用了一部分线程、导致程序变慢、CMS中,在4核的情况下,GC线程只占用不超过25%,随着核心变多而下降。但是不足4个时,分出一半了。由此,产生了 "增量式并发收集器( Mark Sweep/i-CMS)"的变种,模仿OS的抢占式多任务。在``并发标记、清除`时,让GC与用户讲题运行->时间变慢,下降幅度不明显无法处理“浮动垃圾”( ),因为并发,所以在GC时需要给用户线程留足够内存空间,所以不能等老年代快被填满时收集,JDK 5默认下是68%(偏保守了)

浮动垃圾:在CMS中,有可能出现“Con- ”失败进而导致另一次完全“Stop The World”的Full GC的产生。在运行自然就还会伴随有新的垃圾对象不断产生,但是出现在标记过程结束以后。CMS无法在当次收集中处理掉它们,只好留待下一次垃圾收集时再清理掉。

因为采用的标记-清除,因此有大量空间碎片产生。所以需要内存碎片合并过程。但是在移动存活对象,是无法并行的,所以在默认情况,每次进入Full GC先进行整理【-XX:-负责,要求CMS收集器在执行过若干次(数量由参数值决定)不整理空间的Full GC之后,下一次进入Full GC前会先进行碎片整理】 First收集器(整体标记-整理,局部标记-复制)

简称G1,里程碑式的成果,号称全功能的垃圾收集器(Fully- ),开创了收集器面向局部收集的设计思路,和基于的内存布局形式,它是主要面向服务端应用的垃圾收集器。

核心思想:回收的衡量不再是属于哪个分代,而是取决于哪块的内存中的垃圾数量最多,回收收益最大,称为G1的Mixed GC模式

关键点:基于的堆内存布局【G1将连续的JAVA堆划分为多个大小相同的独立区域(),每个可以根据需要扮演新生代的Eden、、老年代空间】。中的区域专门存放大对象【大小超过的一半】

垃圾收集器与内存分配策略_垃圾收集器原理_

分区示意图:

在应用G1收集器所解决的问题:

步骤:

初始标记( ):标记GC Root直接关联的对象,并修改TAMS指针的值,与Minor GC同步完成并发标记( ):从GC Root开始对堆进行可达性分析,耗时长可与用户并发进行,扫描后进行SATB处理最终标记(Final ):对用户进行短暂的暂停,处理并发后遗留的SATB记录筛选回收(Live Data And ):更新的统计数据,对其回收价值/成本进行排序,根据用户的需求制定回收计划。在这使用标记-复制算法后清除整个旧,设计对象移动,暂停用户,多条GC线程并行完成。

达到延迟可控的情况下,尽可能提高吞吐量的目的。需求从一次把整个JAVA堆清干净,变为能够应付应用的内存分配速率( Rate)即可。与CMS的“标记-清除”算法不同,G1从整体来看是基于“标记-整理”,但从局部(两个之间)基于“标记-复制”,因此G1运作期间不会产生内存空间碎片。但G1在GC时的内存占用()和程序运行的负载()都比CMS高

内存占用角度:G1的卡表更复杂,每个都要有卡表,而CMS只有一份只处理老年代对新生代的引用执行负载:CMS使用写后屏障,G1使用写后维护卡表的同时,因使用SATB,所以使用写前屏障跟踪并发时的并发情况。G1把写前和写后要做的事放入类似消息队列的结构中,进行异步处理。

因此小内存选CMS,大内存选G1,平衡点在6-8GB之间

低延迟垃圾收集器

GC的衡量标准:内存占用()、吞吐量()、延迟(),随着计算机硬件的发展,延迟的重要性日益凸显,越发备受关注。

各收集器的并发情况:

浅色为必须挂起用户线程,深色表为GC与用户并发工作。

在CMS/G1之前都要“Stop The World”停顿,在CMS/G1分别使用增量更新/原始快照,实现了标记阶段的并发。但是CMS中整理碎片空间也要“Stop The World”、G1可以按来回收,但是也是需要在筛选回收时暂停的。

目前/ZGC都是实验阶段的GC

收集器

在商用被ban了

像是G1的下一代继承整,有着类似的堆内存布局。在管理内存的领域改进如下:

支持并发的整理算法默认不适用分代 收集摒弃了记忆集,改用链接矩阵( ):N有对M的引用->标记[N][M]。

步骤:

初始标记( ):标记GC Root直接相关联的对象,短停并发标记( ):标记对象图中可达对象,与用户并发最终标记(Final ):处理剩余的SATB扫描,统计出回收价值最高的,组成回收集,小暂停

---------以上与G1相同----------

并发清理( ):清除没有存活对象的【称为 】并发回收( ):核心差异,先复制回收集中存活的对象到空中,使用读屏障和“ 转发指针解决,时间取决于回收集大小初始引用更新( ):并未操作,只是为了确保GC完成了对象移动工作,有短暂停顿并发引用更新( ):开始更新引用,但是是按照内存物理地址的顺序搜索引用类型后修改最终引用更新(Final ):修改堆中的引用,要修改GC Root中的引用,最后一次停顿,与GC Root有关并发清除( ):回收集中的所有都没有存活对象了,直接全回收掉

黄色:被选入回收集的

绿色:还存活的对象

蓝色:用户可以用来分配对象的

支持并行整理的核心概念:

在原有对象结构前添加一个在不处于并发移动时,引用指向对象自己的字段。【像句柄定位】

存在的问题:

执行效率的问题:保证并发时的访问一致性,需要设置读、写屏障拦截,与其他GC模型相比,加入了额外的转发处理,故而读代价很大,由此改进为基于引用访问屏障(Load ),只拦截引用类型的读写操作性能表现:

未实现最大停顿在10毫秒内,高运行负担导致吞吐量下降,但是低延迟 ZGC收集器

基于内存布局,不设分代,使用读屏障、染色指针和内存多重映射等技术的标记-整理算法

ZGC的具有动态性:动态创建、销毁、容量大小

并发整理算法的实现:

标志性的设计染色指针技术( ),直接把标记信息记在对象的指针上,遍历引用图来标记引用。

64位的linux举例:

染色指针的三大优势:

对象被移走后,可以立即被释放和重用,不需要等待更新引用可以大幅减少内存屏障的使用数量。只需要读屏障【写屏障通常是为了记录引用的变动情况】,一部分是因为染色指针,一部分是因为没有分代收集【没有跨代引用】存储结构可扩展,来记录更多的对象标记、重定位过程相关的数据。若开发出linux前64中未使用的18位【这些不能用来寻址】

虚拟内存映射技术:

JVM重新定义指针中某几位的技术;

在x86系统中,进程共用内存,不隔离。使用分页管理机制,实现线性地址到物理地址空间的映射。故而,linux/x86-64的ZGC使用了多重映射(Multi-)实现多个虚拟地址映射到同一个内存地址上【n-1】===》ZGC在虚拟地址识别的空间大于物理上的。

染色指针中的标志位看作分段符,将这些不同的地址段映射到同一个内存空间,就可以正常寻址了。【原本是一个整体,现在切开了】

运行过程(四大阶段皆可并发)

并发标记( Mark):遍历对象图做可达性分析,但是在指针上标记,更新染色指针的 0、 1并发预备分配( for ):根据查询得出清理哪些。每次GC扫描所有,扫描成本换记忆集维护成本。故重分配集只是决定存活的对象复制到别的中。并发重分配( ):把重分配集存活的对象复制到新的中,并为每个维护一个转发表( Table),记录旧->新的引用。并且可以只从染色指针的引用上明确得知一个对象是否处于重分配集中,若用户线程访问当前对象,可以被预置的内存屏障所拦截,根据转发表转发到新的对象上,并修正引用值,称为指针的“自愈”(Self-)能力。好处:1. 只有第一次会转发,比之前的每次的开销低。2. 中的存活对象都复制后可以立即用于新对象的分配【转发表要留着】并发重映射( Remap):修正整个堆中指向重分配集中就对象的所有引用。但不是迫切任务。因为引用是可以自愈的,故而合并到了下一次GC的并发标记中完成【因为都要遍历所有对象】

ZGC是迄今为止最前沿的成果,几乎所有收集过程可并发,短暂停留只与GC Roots大小相关,在任何堆上都小于10ms

但是:

因为没有分代,所以能承受的对象分配率不会太高。【对一个大堆并发收集时,因为新对象的分配率高,所以有大量的新对象,ZGC只能全都当作存活对象,但是其中大多数是很快就死的===》产生了大量的浮动垃圾】,解决这个问题只能引入分代收集。

性能方面:处于实验阶段

下图:左:吞吐量测试,右:ZGC停顿时间测试

PS:他也支持"NUMA-Aware"内存分配【专为多CPU/多核处理器】

选择合适的垃圾收集器 收集器

一款不能够进行垃圾收集为卖点的垃圾收集器。但是还是有”自动内存管理子系统“的功能,这是GC收集器除了GC之外的工作。

如果应用只要运行数分钟甚至数秒,只要Java虚拟机能正确分配内存,在堆耗尽之前就会退出,那显然运行负载极小、没有任何回收行为的便是很恰当的选择。

收集器的权衡

应用程序:

B/S系统==》延迟时间

钱多==》商用的Vega、Zing VM

钱不够要延迟低能用新版本==》ZGC

要稳定并在系统==》

遗留系统==》CMS,大内存G1

虚拟机及垃圾收集器日记

-Xlog参数:

-Xlog[:[][:[][:[][:-]]]]

其中最关键的是由tag【某个功能块的名字,如gc】与level【日记级别】共同组成。

日志级别:Trace,Debug,Info,,Error,Off,决定了详细程度

的日志规则与Log4j、SLF4j框架一致。如果不置顶,默认值是\level\tags:

[3.080s][info][gc,cpu] GC(5) User=0.03s Sys=0.00s Real=0.01s

内存分配与回收策略

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了