- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
大纲 。
1.G1的对象分配原理是怎样的 。
2.深入分析TLAB机制原理 。
3.借助TLAB分配对象的实现原理是什么 。
4.什么是快速分配 + 什么是慢速分配 。
5.大对象分配的过程 + 与TLAB的关系 。
6.救命的稻草—JVM的最终分配尝试 。
。
G1如何分配对象+TLAB机制+分区协调机制 。
G1设计了一套TLAB机制+快速分配机制用来提升分配对象的效率 。
G1设计了一套记忆集+位图+卡表+DCQ+DCQS机制用来提升垃圾回收的效率 。
。
1.G1的对象分配原理是怎样的 。
(1)停顿预测模型总结 。
(2)G1中是怎么分配一个对象的 。
(3)如何解决对象创建过程的冲突问题 。
(4)无锁化分配——基于TLAB的快速分配 。
(5)分配TLAB时对堆内存加锁——大大减少锁冲突导致串行化执行的问题 。
。
G1除了要考虑垃圾对象回收的效率外,还要考虑对象分配的效率。如果对象分配很慢,那即便对象垃圾回收效率很高,系统性能也不高.
。
(1)停顿预测模型总结 。
G1如何满足用户设定的停顿时间?
一.预测在停顿时间范围内G1能回收多少垃圾 。
二.G1进行预测的依据其实就是历史数据 。
三.拿到历史数据后G1应该怎么样 。
四.线性算法模型、衰减算法模型 。
。
如何设计一个合理的预测算法?
通过衰减标准差算法:
davg(n) = Vn, n = 1
davg(n) = (1 - α) * Vn + α * davg(n - 1), n > 1
//上述公式中的α为历史数据权值,1-α为最近一次数据权值
//衰减因子α越小,最新的数据对结果影响越大,最近一次的数据对结果影响最大
//例如α = 0.6,GC次数为3,三次分别为:
//第一次回收2G,用时200ms
//第二次回收5G,用时300ms
//第三次回收3G,用时500ms
//那么计算结果就如下:
davg(1) = 2G / 200ms
davg(2) = (1 - 0.6) * 5G / 300ms + 0.6 * 2G / 200ms
davg(3) = (1 - 0.6) * 3G / 500ms + 0.6((1 - 0.6) * 5G / 300ms + 0.6 * 2G / 200ms)
(2)G1中是怎么分配一个对象的 。
系统程序在创建一个对象时,会先找新生代的Eden区来存储。在G1中,会从Eden区包含的Region里选择一个Region来进行对象的分配.
但是如果有两个线程,同时要找其中一个Region来分配对象,并且这两线程刚好找到这个Region里的同段内存,那么就会出现并发安全问题.
(3)如何解决对象创建过程的冲突问题 。
一个简单的思路就是加锁。线程1在分配对象时,直接对整个堆内存加锁。分配完成后,再由线程2进行对象分配,此时一定不会出现并发安全问题.
。
为什么要对整个堆内存进行加锁?因为对象分配的过程是非常复杂的,不仅仅是分配一个对象。还要做引用替换、引用关系处理、Region元数据维护、对象头处理等。只锁一个Region,或只锁一段内存是不够的,因此只能锁整个堆内存.
。
但是新的问题出现了,这个分配效率很显然非常低,那么应该如何解决这个分配的效率问题?
(4)无锁化分配——基于TLAB的快速分配 。
想要解决并发安全问题,一般有三种思路:
一.使用锁 。
二.使用CAS这种自旋模式(类似锁的思想) 。
三.使用本地缓冲,自己改自己的 。
。
G1会使用本地缓冲区来解决并发分配对象的安全和效率问题,整体来说G1提供了两种对象分配策略:
一.慢速分配 。
二.基于线程本地分配缓冲(TLAB)的快速分配 。
。
TLAB全称就是Thread Local Allocation Buffer,即线程本地分配缓冲。每个线程都会有一个自己的本地分配缓冲区,专门用于对象的快速分配。所以TLAB产生的目的就是为了进行内存快速分配,G1会通过为每个线程分配一个TLAB缓冲区来避免和减少使用锁.
。
TLAB属于线程的,不同的线程不共享TLAB。线程在分配对象时,会从JVM堆分配一个固定大小的内存区域作为TLAB。然后优先从当前线程的TLAB中分配对象,不需要锁,从而实现无锁化分配即快速分配.
(5)分配TLAB时对堆内存加锁——大大减少锁冲突导致串行化执行的问题 。
一.为什么说TLAB大大减少了锁冲突导致串行化执行的问题 。
分配TLAB时,由于一个线程会有一个TLAB,为避免多个线程对同一块内存分配TLAB产生并发冲突,会采用CAS自旋.
。
自旋次数其实可能也就与线程数量一致,基本执行几十次最多几百次。一个for循环里执行几十次几百次是很快的,连1ms都不到.
。
这相当于不再把锁放在分配对象的环节,因为分配对象可能达上千万次。而TLAB就相当于把上千万次的加锁过程,减少到几十次到几百次,所以就大大减少了锁冲突导致串行化执行的问题.
如图所示,只有在线程需要分配TLAB时才会对堆内存加一个全局锁。如果不需要分配TLAB就直接快速分配一个对象,这样就大大提升了效率.
。
二.其他的一些问题 。
既然要分配TLAB,那何时分配TLAB、分配多大、TLAB占满了怎么办?如果实在没有办法用TLAB的方式分配,有没有什么兜底的策略?
。
TLAB不能无限大,一定会有被占满的时候。并且TLAB被占满了以后,程序肯定要继续运行,这时该怎么办?
。
2.深入分析TLAB机制原理 。
(1)TLAB是什么 + TLAB是怎么分配的 。
(2)如何确定TLAB大小 + TLAB满了如何处理 。
(3)怎么判断TLAB满了 。
(4)TLAB满了怎么办 + 经常满又怎么办 。
。
(1)TLAB是什么 + TLAB是怎么分配的 。
首先需要知道的是,程序创建的对象是由线程创建的。线程在分配时,也是以一个对象的身份分配出来的,比如创建线程是由Thread对象new出来的:
Thread thread = new Thread();
所以创建一个线程时,也会有一个线程对象需要被分配出来。而事实上,分配TLAB就是和分配线程对象同时进行的.
。
创建线程,分配线程对象时,会从堆内存分配一个固定大小的内存区域。并且将该区域作为线程的私有缓冲区,这个私有缓冲区就是TLAB.
。
注意:在分配TLAB给线程时,是需要加锁的,G1会使用CAS来分配TLAB.
问题:TLAB的数量不能无限多,应怎么限制?
因为分配线程对象时,会从JVM堆内存上分配一个TLAB供线程使用,所以理论上有多少个线程就会有多少个TLAB缓冲区。那么由于线程数量肯定不会是无限的,否则CPU会崩溃,所以TLAB的数量会跟随线程的数量:有多少个线程,就有多少个TLAB.
。
问题:如果TLAB过大,会有什么问题?如果TLAB过小,又会有什么问题?
。
(2)如何确定TLAB大小 + TLAB满了如何处理 。
一.TLAB的大小要有一个平衡点 。
情况一:如果TLAB过小 。
会导致TLAB快速被填满,从而导致不断分配新的TLAB,降低分配效率.
。
情况二:如果TLAB过大 。
由于TLAB是线程独享,所以TLAB过大会造成内存碎片,拖慢垃圾回收的效率。因为运行过程中,TLAB可能很多内存都没有被使用,造成内存碎片。同时在垃圾回收时,因为要对TLAB做一些判断,所以会拖慢垃圾回收的效率.
。
二.如何确定TLAB的大小 。
TLAB初始化时有一个公式计算:TLABSize = Eden * 2 * 1% / 线程个数。其中乘以2是因为,JVM的开发者默认TLAB的内存使用服从均匀分布。均匀分布指对象会均匀分布在整个TLAB空间,最终50%的空间会被使用。分配好TLAB后,线程在创建对象时,就会优先通过TLAB来创建对象.
。
三.TLAB满了无法分配对象了会怎么处理 。
TLAB满了的处理思路无非两种:
一.重新申请一个TLAB给线程继续分配对象 。
二.直接通过堆内存分配对象 。
。
G1是使用了两者结合的方式来操作的。如果TLAB满了无法分配对象了,就先去申请一个新的TLAB来分配对象。如果无法申请新的TLAB,才通过对堆内存加锁,直接在堆上分配对象.
(3)怎么判断TLAB满了 。
一.为什么需要判断TLAB满了 。
因为TLAB大小分配好后,其大小就固定了,而对象的大小却是不规则的,所以很有可能会出现对象放不进TLAB的情况。但是TLAB却还有比较大比例的空间没有使用,这时就会造成内存浪费。所以如何判断TLAB满了,是一个比较复杂的事情.
。
二.G1是怎么判断TLAB满了 。
G1设计了一个refill_waste来判断TLAB满了,refill_waste的含义是一个TLAB可以浪费的最大内存大小是refill_waste。也就是说,一个TLAB中最多可以剩余refill_waste这么多的空闲空间。如果TLAB剩余的空闲空间比refill_waste少,那就代表该TLAB已经满了.
。
refill_waste的表示一个TLAB中可以浪费的内存的比例,refill_waste的值可以通过TLABRefillWasteFraction来调整。TLABRefillWasteFraction默认值是64,即可以浪费的内存比例为1/64。如果TLAB为1M,那么refill_waste就是16K.
。
问题:判断一个TLAB满了以后,对象应该怎么分配?如果TLAB经常进入这种满的状态,说明TLAB的空间设置不是很合理,和我们对象大小的规律不匹配了,应该怎么解决这个不合理?
。
(4)TLAB满了怎么办 + 经常满又怎么办 。
G1设计的refill_waste不是简单去判断是否满了,其判断过程会比较复杂,具体逻辑如下:
。
一.线程要分配一个对象,首先会从线程持有的TLAB里面进行分配 。
如果TLAB剩余空间够了,就直接分配。如果TLAB剩余空间不够,这时就去判断refill_waste.
。
二.此时要对比对象所需空间大小是否大于refill_waste这个最大浪费空间 。
如果大于refill_waste,则直接在TLAB外分配,也就是在堆内存里直接分配。如果小于refill_waste,就重新申请一个TLAB,用来存储新创建的对象.
。
三.重新申请新的TLAB时,会根据TLABRefillWasteFraction来动态调整 。
动态调整目的是适应当前系统分配对象的情况,动态调整依据是refill_waste和TLAB大小无法满足当前系统的对象分配。因为对象既大于当前TLAB剩余的可用空间,也大于refill_waste。即剩余空间太小了,分配对象经常没办法分配,只能到堆内存加锁分配。所以很显然还没有达到一个更加合理的refill_waste和TLAB大小。因此系统会动态调整TLAB大小和refill_waste大小,直到一个更合理的值.
。
3.借助TLAB分配对象的实现原理是什么 。
(1)TLAB是怎么实现分配对象的(指针碰撞法) 。
(2)dummy哑元对象的作用是处理TLAB内存碎片 。
(3)如果实在无法在TLAB分配对象,应该怎么处理 。
。
(1)TLAB是怎么实现分配对象的(指针碰撞法) 。
对象分配是一个比较复杂的过程,这里我们不关注对象到底怎么创建的,因为它包含了很多东西:比如引用、对象头、对象元数据、各种标记位、对象的klass类型对象、锁标记、GC标记、Rset、卡表等.
。
一.TLAB是怎么实现分配一个对象的 。
分配一个对象时,TLAB是只给当前这一个线程使用的,因此当前线程可以直接找到这个TLAB进行对象的分配.
。
那么此时就需要知道TLAB是不是满了、或者对象能不能放得下。如果TLAB剩余内存能放得下,就创建对象。如果TLAB剩余内存放不下就进行如下图示的流程:要么直接堆内存创建对象、要么分配新的TLAB给线程,再继续创建对象.
可见对象在TLAB中能不能放得下是很关键的,那么TLAB中用了什么机制来判断能不能放得下的?
。
二.TLAB是怎么判断对象能否放得下的 。
一个比较简单的思路是先确定分配出去了哪些空间。由于TLAB是一个很小的空间,而且对象的分配是按照连续内存来分配的,所以可以直接遍历整个TLAB,然后找到第一个没有被使用的内存位置。接着用TLAB结束地址减去第一个没有被使用的内存地址,得到剩余大小,再将TLAB剩余大小和对象大小进行比较.
。
但这个思路有一个问题:每一次对象分配都要遍历TLAB,是否有必要?其实每次分配新对象的起始地址,就是上一次分配对象的结束地址。所以可以用一个指针(top指针),记下上次分配对象的结束地址,然后下次直接用这个作为起始位置进行直接分配.
。
如下图示:在分配对象obj3时,TLAB里的top指针记录的就是obj2对象的结束位置.
当obj3分配完成时,此时就把指针更新一下,更新到最新的位置上去.
但是分配对象时肯定不能直接进入TLAB去分配,因为有可能空间会不够用。所以在分配对象时会判断一下剩余内存空间是否能够分配这个对象.
。
那么具体应该怎么判断剩余内存空间是否能够分配这个对象呢?此时就需要记录一下整个TLAB的结束位置(end指针)。这样在分配对象时,对比下待分配对象的空间(objSize)和剩余的空间即可.
知道end指针位置,那么判断关系就很容易:
如果objSize <= end - top,可分配对象.
如果objSize > end - top,不能分配对象.
。
问题:因为TLAB是一个固定的长度,而对象很有可能有的大有的小,所以有可能会产生一部分内存空间无法被使用的情况,也就是产生了内存碎片,那么这个内存碎片应该怎么处理呢?
。
(2)dummy哑元对象的作用是处理TLAB内存碎片 。
由于TLAB不大,TLAB大小的计算公式是:(Eden * 2 * 1%)/ 线程个数。所以如果TLAB有内存碎片,实际上也就是比一个普通小对象的大小还要小一点.
对于一个系统来说:可能几百个线程,总共加起来的内存碎片也就几百K到几M之间。所以为了这么小的空间,专门设计一个内存压缩机制,肯定是不太合理的。而且也不太好压缩,因为每个线程都是独立的TLAB。把所有对象压缩一下,进入STW,然后把对象集中放到线程的TLAB吗?如果对象在线程1的TLAB分配,压缩后出现在线程2的TLAB里面,那此时该对象应该由谁管理,所以压缩肯定是不合理的.
。
所以这块小碎片如果对内存的占用不大,能否直接放弃掉?答案是可以的,而G1也确实是这么做的,这块内存碎片直接放弃不使用。而且在线程申请一个新的TLAB时,这个TLAB也会被废弃掉。这个废弃指的不是直接销毁,而是不再使用该TLAB,进入等待GC状态.
。
此时会有一个新的问题:在GC时,遍历一个对象,是可以直接跳过这个对象长度的内存的。因为对象属性信息中有对象长度,故遍历对象时拿到对象长度就可跳过。但是TLAB里的小碎片,由于没有对象属性信息,所以不能直接跳过。只能把这块小碎片的内存一点一点进行遍历,这样性能就会下降.
。
所以G1使用了填充方式来解决遍历碎片空间时性能低下的问题,G1会直接在碎片里面填充一个dummy对象。这样GC遍历到这块内存时:就可以按照dummy对象的长度,跳过这块碎片的遍历.
问题:如果没有办法用TLAB分配对象,那么此时应该怎么办?新建一个TLAB?那么如果新建一个TLAB失败了,怎么办?
。
(3)如果实在无法在TLAB分配对象,应该怎么处理 。
一.对旧TLAB填充dummy对象 。
TLAB剩余内存太小,无法分配对象,会有不同情况:如果对象大于refill_waste,直接通过堆内存分配。如果对象小于refill_waste,这时会重新分配一个TLAB来用。在重新分配一个TLAB之前,会对旧的TLAB填充一个dummy对象.
。
二.分配新TLAB时先快速无锁(CAS)分配再慢速分配(堆加锁) 。
重新分配一个TLAB时,先进行快速无锁分配(CAS),再进行慢速分配(堆加锁).
。
快速无锁分配(CAS):如果通过CAS重新分配一个新TLAB成功,也就是Region分区空间足够使CAS分配TLAB成功,则在新TLAB上分配对象.
。
慢速分配(堆加锁):如果通过CAS重新分配一个新TLAB失败,则进行堆加锁分配新TLAB。如Region分区空间不足导致CAS分配TLAB失败,需要将轻量级锁升级到重量级锁.
。
三.堆加锁分配时可能扩展Region分区 。
进行堆加锁分配一个新的TLAB时:如果堆加锁分配一个新TLAB成功,就在Region上分配一个新的TLAB(堆加锁分配TLAB成功)。如果堆加锁分配一个新TLAB失败,就尝试扩展分区,申请新的Region(堆加锁分配TLAB失败).
。
四.扩展Region分区时可能GC + OOM 。
扩展分区成功就继续分配对象,扩展分区失败就进行GC垃圾回收。如果垃圾回收的次数超过了某个阈值,就直接结束报OOM异常.
。
解释一下最后的这个垃圾回收:
如果因为内存空间不够,导致无法分配对象时,那么肯定需要垃圾回收。如果垃圾回收后空间还是不够,说明存活对象太多,堆内存实在不够了。这时程序肯定无法分配对象、无法运行,所以准备OOM。那么OOM前,可能还会尝试几次垃圾回收,直到尝试次数达到某个阈值。比如达到了3次回收还是无法分配新对象,才会OOM.
。
4.什么是快速分配 + 什么是慢速分配 。
(1)什么叫快速分配 + 什么叫慢速分配 。
(2)慢速分配是什么 + 有几种情况 。
。
(1)什么叫快速分配 + 什么叫慢速分配 。
分配对象速度快、流程少的就叫快速分配.
分配对象速度慢、流程多的就叫慢速分配.
。
快速分配:TLAB分配对象的过程就叫做快速分配。多个线程通过TLAB就可以分配对象,不需要加锁就可以并行创建对象。TLAB分配对象具有的特点:创建快、并发度高、无锁化.
慢速分配:没有办法使用TLAB快速分配的就是慢速分配。因为慢速分配需要加锁,甚至可能要涉及GC过程,分配的速度会非常慢.
整个对象分配流程如下,注意上图中的慢速分配包括:慢速TLAB分配 + 慢速对象分配 。
。
一.TLAB剩余内存太小,无法分配对象,则判断refill_waste 。
如果对象大小大于refill_waste,直接通过堆内存分配,不进行TLAB分配。如果对象大小小于refill_waste,这时会重新分配一个TLAB.
。
二.进行重新分配一个TLAB时,会通过CAS来分配一个新的TLAB 。
如果CAS分配成功,则在新的TLAB上分配对象(快速无锁分配)。如果CAS分配失败,就会对堆内存加锁再去分配一个TLAB(慢速分配)。如果堆内存加锁分配新TLAB成功,则可直接在新的TLAB上分配对象.
。
三.如果堆内存加锁分配失败,就尝试扩展分区,再申请一些新的Region 。
成功扩展了Region就分配TLAB,然后分配对象,如果不成功就进行GC.
。
四.如果GC的次数超过了阈值(默认为2次),就直接结束报OOM异常 。
。
问题:什么情况下会出现慢速分配,有几种慢速分配的情况?
。
(2)慢速分配是什么 + 有几种情况 。
慢速分配其实和快速分配相比起来就是多了一些流程,在对象创建这个层面上是没有效率区别的.
。
慢速之所以称为慢速,是因为在分配对象时:需要进行申请内存空间、加锁等一系列耗时的操作,并且慢速分配除了会加锁,还可能涉及到垃圾回收的过程.
。
慢速分配大概有两种情况:
。
情况一:TLAB空间不够,要重新申请TLAB,但CAS申请TLAB失败了 。
这种情况就是refill_waste判断已通过,TLAB中对象太多,导致对象放不下。此时会创建新的TLAB,但是CAS分配TLAB失败,于是慢速分配TLAB。这个过程的慢速分配是指:慢速分配一个TLAB.
。
情况二:判断无法进行TLAB分配,只能通过堆内存分配对象 。
这种情况就是refill_waste判断没通过,对象太大了,导致不能进行TLAB分配。此时会触发慢速分配,并且不是去申请TLAB,而是直接进入慢速分配。也就是直接在堆内存的Eden区去分配对象,这个过程的慢速分配是指慢速分配一个对象.
。
慢速分配的两种情况如下图示:
所以快速TLAB分配失败后进入的慢速分配,是个慢速分配TLAB的过程。随后可能会发生更慢的慢速分配,即慢速分配TLAB失败,此时会GC.
。
问题:上面一直说的对象分配,默认认为对象可以在整个TLAB中放得下。那么如果有一个大对象,整个TLAB都根本放不下了,怎么办?此时的对象分配是快速还是慢速?
。
5.大对象分配的过程 + 与TLAB的关系 。
(1)大对象分配会使用TLAB吗 + 它属于快速分配还是慢速分配 。
(2)大对象的慢速分配有什么特点 + 和普通的慢速分配有没有什么区别 。
。
(1)大对象分配会使用TLAB吗 + 它属于快速分配还是慢速分配 。
要确定大对象能不能进行TLAB分配,首先得知道TLAB的大小,TLAB的大小和大对象是相关的.
。
一.什么是大对象 + 大对象的特点 。
大对象的定义:
如果一个对象的大小大于RegionSize的一半,那么这个对象就是大对象。也就是ObjSize > RegionSize / 2的时候,就可以称它为大对象.
。
大对象的分配:
大对象不会通过新生代来分配,而是直接分配在大对象的Region分区中。问题:为什么它要直接存储在大对象的分区中?不经过新生代?
。
大对象的特点:
一.大对象太大,并且存活时间可能很长 。
二.大对象数量少 。
。
二.大对象能否在新生代分配 + TLAB的上限 。
如果大对象在新生代分配会怎么样?如果大对象在新生代,那么GC时就会很被动。因为需要来回复制,并且占用的空间还大,每次GC大概率又回收不掉。而且它本身数量相对来说比较少,所以直接将大对象分配到一个单独的区域来管理才比较合理.
。
G1如何根据大对象的特点来设计TLAB上限?由于大对象的ObjSize > RegionSize / 2,所以G1把TLAB的最大值限定为RegionSize / 2,这样大对象就一定会大于TLAB的大小。然后就可以直接进入慢速分配,到对应的Region里去.
G1设定TLAB最大值为大对象最小值的原因总结:
。
原因一:大对象一般比较少,如果进入TLAB则会导致普通对象慢速分配 。
一个系统产生的大对象一般是比较少的,如果一个大对象来了就占满TLAB了或占用多个TLAB,那么会造成其他普通对象需要进入慢速分配。大对象占满了TLAB后,其他对象就需要重新分配新的TLAB,这就降低系统的整体效率; 。
。
原因二:在GC时不方便标记大对象 。
一个大对象引用的东西可能比较多,引用它的可能也比较多,所以GC时不太方便去标记大对象; 。
。
原因三:大对象成为垃圾对象的概率小,不适合在GC过程中来回复制 。
。
新生代GC不想管大对象,并且管理起来影响效率,所以新生代最好是不管大对象的。因此干脆让大对象直接进行慢速分配,反而能提升一些效率。所以G1设定TLAB上限就是Region的一半大小,TLAB上限即大对象下限,这个设定就会让大对象直接进行慢速分配.
。
(2)大对象的慢速分配有什么特点 + 和普通的慢速分配有没有什么区别 。
大对象和TLAB中的慢速分配类似,区别是:
区别一:大对象分配前会尝试进行垃圾回收 。
区别二:大对象可能因大小的不同,造成分配过程稍微有一些不同 。
。
大对象的慢速分配步骤如下:
步骤一:先判断是否需要GC,需要则尝试垃圾回收 + 启动并发标记 。
和普通对象的慢速分配不同点在于:大对象分配时,先判断是否需要GC,是否需要启动并发标记,如果需要则尝试进行垃圾回收(YGC或Mixed GC) + 启动并发标记.
步骤二:如果大对象大于HeapRegionSize的一半,但小于一个分区的大小 。
此时一个完整分区就能放得下,可以直接从空闲列表拿一个分区给它。或者空闲列表里面没有,就分配一个新的Region分区,扩展堆分区.
步骤三:如果大对象大于一个完整分区的大小,此时就要分配多个Region分区 。
步骤四:如果上面的分配过程失败,就尝试垃圾回收,然后再继续尝试分配 。
步骤五:最终成功分配,或失败到一定次数分配失败 。
问题:如果失败了就GC,尝试达到了某个次数就分配失败。那么失败了以后,JVM就直接OOM了吗?如果不是OOM,有没有什么方式补救.
。
6.救命的稻草—JVM的最终分配尝试 。
(1)慢速分配总结 。
(2)大概率会成功的快速 + 慢速尝试 。
(3)慢速分配失败以后G1会怎么拯救 。
(4)FGC在哪里触发 + 会执行几次 + 执行的过程中会做什么操作 。
(5)总结 。
。
(1)慢速分配总结 。
一.慢速分配是什么 + 快速分配是什么 。
二.慢速分配有几种场景 。
三.慢速分配的流程是什么 。
四.大对象的分配属于什么流程 。
。
(2)大概率会成功的快速 + 慢速尝试 。
一般即使内存不够,扩展一下Region,就能获取足够内存做对象分配了。实在不够才会尝试GC,GC之后继续去做分配.
。
其实百分之九十九点九的概率是可以成功分配的,极端情况下才会出现尝试了好多次分配,最后都还是失败了的情形.
上图中的1、2、3步就是扩展、回收的过程,很多情况下直接在1、3步就直接成功了。比如通过TLAB去分配对象,那么其实扩展一个新的TLAB就基本成功了,不会走到垃圾回收这一步.
。
如果扩展TLAB不成功,那么就直接堆内存分配(慢速分配)、扩展分区。如果堆内存分配 + 扩展分区还是不成功,才会尝试触发YGC,再来一次。如果再来一次还是无法成功就只能返回失败了,那么返回失败之后就直接OOM了吗?没有挽救的余地了吗?前面的失败,经历的GC都是先YGC或Mixed GC,然后进入拯救环节.
。
(3)慢速分配失败以后G1会怎么拯救 。
首先需要明确:在慢速分配的过程中,肯定是会尝试去GC的,但是触发的GC要么是YGC要么是Mixed GC。那就说明,还没有到山穷水尽的地步,因为还有一个FGC没有用.
。
所以慢速分配失败后肯定不是直接OOM,而会有一个最终的兜底过程。这个过程会进入最恐怖的FGC过程,是极慢极慢的.
。
那这个过程到底会做什么?FGC会在哪里触发?会执行几次?执行的过程中会做什么操作?
。
(4)FGC在哪里触发 + 会执行几次 + 执行的过程中会做什么操作 。
如果上面的过程结束后还是没有返回一个对象,代表慢速分配也失败了。过程中进行的GC也无法腾出空间,那就会走向最后一步,触发FGC。这个GC过程会比较复杂,流程图如下:
一.尝试扩展分区成功就可以分配对象.
二.如果尝试扩展分区不成功,则会进行一次GC。注意这次GC是FGC,但是这次GC不回收软引用。这次GC后会再次尝试分配对象,如果成功了就结束.
三.如果尝试分配对象还是不成功,就进行FGC。这次FGC会把软引用回收掉,然后再次尝试分配对象。如果再次分配对象成功了,就结束返回。如果再次分配对象还是不成功,就只能OOM,无法挽救.
。
从上面的流程可以看出:假如一次对象分配失败造成了OOM,很有可能会出现大量GC。这也符合有时看GC日志会发现OOM前多了好几次GC记录的情况.
。
(5)总结 。
总的来说,对象分配涉及到的GC过程,在不同的阶段是不一样的。比如在使用TLAB进行快速分配的过程中:第一次进入慢速分配,扩展分区失败时,就是YGC或者Mixed GC。再次进入慢速分配,有可能还会执行YGC或者Mixed GC(没达阈值)。当慢速分配也失败时,才会进行最终的尝试。在最终的尝试中,会尝试执行两次FGC。第一次FGC不回收软引用,第二次FGC会回收软引用.
。
另外,对象分配一般都是进入快速分配,慢速分配的场景比较少:一般是TLAB大小不合理造成短暂慢速分配,或者是大对象的分配直接进入慢速分配.
。
慢速分配的过程需要的时间非常长,因为要做很多扩展分区的处理、加锁的处理、甚至GC的处理.
。
最后此篇关于G1原理—2.G1是如何提升分配对象效率的文章就讲到这里了,如果你想了解更多关于G1原理—2.G1是如何提升分配对象效率的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我有一个应用程序,它会抛出 GKSession 并在各种条件下(连接超时、 session 失败等)创建一个新的 GKSession。不过,我遇到了内存泄漏问题,并且有时会在重新连接几次循环后崩溃。
比如我在宿主代码中有一个浮点指针 float *p 是否可以确定他指向的内存类型(设备/主机)? 最佳答案 在 UVA system 中, 运行时 API 函数 cudaPointerGetAttri
我已将项目转换为 .Net 4.0 并且以下代码不起作用: typeof(RuntimeTypeHandle).GetMethod("Allocate", BindingFlags.Instance
当我声明 char ch = 'ab' 时,ch 只包含 'b',为什么它不存储 'a'? #include int main() { char ch = 'ab'; printf("%c"
我对 Disk Sector 和 Block 有疑问。扇区是一个单位,通常为 512 字节或 1k、2k、4k 等取决于硬件。文件系统 block 大小是一组扇区大小。 假设我正在存储一个 5KB 的
假设我有 8 个人和5000 个苹果。 我想将所有苹果分发给所有 8 个人,这样我就没有苹果了。 但每个人都应该得到不同数量 将它们全部分发出去的最佳方式是什么? 我是这样开始的: let peopl
我正在构建的网站顶部有一个搜索栏。与 Trello 或 Gmail 类似,我希望当用户按下“/”键时,他们的焦点就会转到该搜索框。 我的 JavaScript 看起来像这样: document.onk
我有一小段代码: if (PZ_APP.dom.isAnyDomElement($textInputs)){ $textInputs.on("focus", function(){
我观察到以下行为。 接受了两个属性变量。 @property (nonatomic, retain) NSString *stringOne; @property (nonatomic, assign
我正在解决这样的问题 - 实现一个计算由以下内容组成的表达式的函数以下操作数:“(”、“)”、“+”、“-”、“*”、“/”。中的每个数字表达式可能很大(与由字符串表示的一样大)1000 位)。 “/
我有一组主机和一组任务。 每个主机都有 cpu、mem 和任务容量,每个任务都有 cpu、mem 要求。 每个主机都属于一个延迟类别,并且可以与具有特定延迟值的其他主机通信。 每个任务可能需要以等于或
该程序的作用:从文件中读取一个包含 nrRows 行和 nrColomns 列的矩阵(二维数组)。矩阵的所有元素都是 [0,100) 之间的整数。程序必须重新排列矩阵内的所有元素,使每个元素等于其所在
世界!我有个问题。今天我尝试创建一个代码,它可以找到加泰罗尼亚语号码。但是在我的程序中可以是长数字。我找到了分子和分母。但我不能分割长数字!此外,只有标准库必须在此程序中使用。请帮帮我。这是我的代码
我确定我遗漏了一些明显的东西,但我想在 Objective C 中创建一个 NSInteger 指针的实例。 -(NSInteger*) getIntegerPointer{ NSInteger
这个问题在这里已经有了答案: Difference between self.ivar and ivar? (4 个答案) 关闭 9 年前。
我如何将 v[i] 分配给一系列整数(v 的类型是 vector )而无需最初填充 最佳答案 你的意思是将 std::vector 初始化为一系列整数? int i[] = {1, 2, 3, 4,
我想寻求分配方面的帮助....我把这个作业带到了学校......我必须编写程序来加载一个 G 矩阵和第二个 G 矩阵,并搜索第二个 G 矩阵以获取存在数第一个 G 矩阵的......但是,当我尝试运行
我必须管理资源。它基本上是一个唯一的编号,用于标识交换机中的第 2 层连接。可以有 16k 个这样的连接,因此每次用户希望配置连接时,他/她都需要分配一个唯一索引。同样,当用户希望删除连接时,资源(号
是否有任何通用的命名约定来区分已分配和未分配的字符串?我正在寻找的是希望类似于 us/s 来自 Making Wrong Code Look Wrong ,但我宁愿使用常见的东西也不愿自己动手。 最佳
我需要读取一个 .txt 文件并将文件中的每个单词分配到一个结构中,该结构从结构 vector 指向。我将在下面更好地解释。 感谢您的帮助。 我的程序只分配文件的第一个字... 我知道问题出在函数 i
我是一名优秀的程序员,十分优秀!