【刨根问底】之JVMpart2(java堆结构、分代算法机制及过程,对象的一生,元空间与方法区,GC触发条件)

1.6堆内存
1.其存储方式为随机存储,存入时,需要先寻找会存放的区域,因此存取速度较慢,其存取算法与栈不同,并不需要具体的数据存在时间

2.堆可以动态扩容,其大小上限由参数-Xmx(最大内存)和-Xms(初始内存)决定,因此可以用于存放一些在代码编写时并不确定具体大小的数据(比如我的对象是一个符合某种特征的列表,可能是一个非常大,且我并不能确定其大小的对象),但是当其扩容到极限时,依然会报oom错误

3.综合以上两种特性,java认为堆适合用于存放对象,实际上堆也只用于存放对象,继而,出现了新的问题,我们已知内存并不是无限的,而程序员并没有指定对象的具体存活时间,因而,堆内存的清理是一个常见的议题

4.在java中堆内存的清理交付于垃圾收集器机制,其为了提高清理效率使用了分代算法对堆中的对象进行清理(需要注意堆中存放的是对象,而GC清理的目标自然也是对象),如图中所示,其被分为新生代和老年代,其中新生代又可继续细分为Eden、survive to和survive from
【刨根问底】之JVMpart2(java堆结构、分代算法机制及过程,对象的一生,元空间与方法区,GC触发条件)_第1张图片
5.堆内存发生的GC有两种发生于新生代的minorGC和针对老年代的FullGC,其中,FullGC所要花费的时间有时会是minorGC的10倍以上

pass: 关于慢的具体原因,这本身是建立在于比较对象上的,minorGC本身的算法是使用空间换时间的复制算法,在下面讲到的对象的一生会进行详细讲解。

而FullGC的慢也并不都是因为其算法的原因,如,在jdk4至jdk8之间的虚拟机中,最常用于执行FullGC的是CMS垃圾收集器,其使用的是标记清除算法,这种算法速度其实很快,但是会留下大量的碎片空间,因为其只是单纯的释放死亡的对象,但是并没有整理存活的对象,比如,虽然空闲内存有100m,但是最大的连续空间却只有10m,若此时要存入一个12m的对象,就会触发concurrent mode fail错误,此时CMS收集器将会请求使用标记整理算法Serial Old收集器执行一次fullGC,该收集器在清理的同时会将内存空间进行整理,而代价是极其漫长的GC时间——实际上,是从几十微秒暴涨到数秒级别的延迟
可以参考实例深入理解Major GC, Full GC, CMS

6.一般来说,新建的对象将会出生在新生代——但是,若一个对象的体积过大,将会直接出生在老年代,而且,将会导致只有FullGC才可以将其清除 ,且若将会持续的产生这种大对象,将会导致频繁的FullGC而使得程序卡顿

7.详细讨论对象的一生,为了更加透彻的讲解,我将以模拟现实的运行场景的顺序讨论

7.1.大量的对象在eden中诞生,因而eden内存很快就会被占满,由于大部分的对象朝生暮死,此时,只需要把已死亡的对象进行清除即可
【刨根问底】之JVMpart2(java堆结构、分代算法机制及过程,对象的一生,元空间与方法区,GC触发条件)_第2张图片
7.2.如图所示,minorGC被触发后,Eden和Surive From区内的存活对象将会被复制到Surive to区域,之后,除了Surive to以外的两个区域全部直接清空,并且Surive to将会和Surive From交换引用,即存放了存活的对象的区域更名为Surive From,并且这些经过了一轮minorGC依然活着的对象,其年龄计数器将会+1,默认来说,年龄至15时,将会进入老年区
【刨根问底】之JVMpart2(java堆结构、分代算法机制及过程,对象的一生,元空间与方法区,GC触发条件)_第3张图片
7.3.如图所示,Eden再次爆满,再次触发minorGC,循环该流程,15岁以上的对象迁移至老年代
【刨根问底】之JVMpart2(java堆结构、分代算法机制及过程,对象的一生,元空间与方法区,GC触发条件)_第4张图片
【刨根问底】之JVMpart2(java堆结构、分代算法机制及过程,对象的一生,元空间与方法区,GC触发条件)_第5张图片
8.minorGC与FullGC的触发条件:
一:Minor GC触发条件:当Eden区满时,触发Minor GC,实际上这是非常频繁被触发的GC,但是其本质并没有

二:FullGC:之前以提过,FullGC所消耗的时间是minorGC的10倍以上,而且,在某些特殊的情况下可能会发送多次持续的FullGC,从而导致程序卡死
1.代码中调用system.gc()方法时,本质是在向jvm发出进行fullGC的建议,此种建议可能会发生fullGC也可能不会
2.老年代空间不足
2.1将要晋升的对象 比老年代剩余空间大,
2.2出现了体积过于巨大的对象无法存入新生代以至于直接存放于旧生代,而该对象比旧生代剩余空间要大
2.3jvm函数统计得出经过了一次Minor GC后,存活下来的对象的平均大小大于旧生代剩余空间——实际上,CMS需要执行环境,其希望老年代并不是处于百分之一百爆满的情况下

3.元空间&永生代内存不足
3.1永生代是什么及与元空间的关系
在旧的jdk8版本之前的java堆中,包含一个特殊的区域,其与java堆是隔离开的,存放的数据是需要使用的类的元数据——包括类的信息以及全部的static方法,我们所讨论的两类GC并不会针对其进行垃圾收集,因此其名为永生代,又称方法区
但是jdk8开始从对中剔除了永生带,转而使用元空间进行替代,原因是动态生成类的出现
3.2动态类是什么
具体配置方式详见引用:
动态类配置方法
是一种可以动态配置特征的类,定义时只声明其为object类,但是成员,方法细节等都由配置文件来动态定义——这大大提高了程序的灵活性,降低耦合度,但是也因此,该种类的具体大小极其难在jvm启动时就预测好,这导致了永生代在初始化时很难设置出合适的大小——若永生代过大,抢占jvm的内存,将会提升fullGC的可能性,同样的若永生代过小时,同样会触发fullGC,因而,永生代需要被更加优秀的机制替代
3.3元空间是什么
1.与永生代作为堆的一部分被放置于jvm内存中不同,其存储空间转化为放在jvm以外的本地内存之中,且与jvm一样,可以配置初始大小与最大大小
2.元空间存在GC么,为什么他会触发FullGC:存在,他将会判定一个类是否僵死,判断依据为:
1.jvm中它的实例对象是否已全部被释放
2.它的类加载器对象是否已被释放
3.它的java.lang.class对象是否已全部释放——也就是没有可以通过反射访问到类的方法

其中1和3很好理解,那么2呢?首先,
3.3.1类加载器是什么
是jre的一部分,作为dll库文件存放在jre的lib文件夹中,也就是前文提到的本地代码,加载类时,需要使用他们创建出类加载器对象,其也正常存放于jvm之中
3.3.2类加载器对象何时被GC?
或者说,何时会被判定为死亡?对于类加载器对象来说,其对应的类的所有对象全部被释放时,就会被GC,也就是说,本质上第二条是第一条达成后就会达成的条件
但是,只是被判定为死亡还不够,在什么时候会被清除掉?——其并不不会在minorGC时进行处理,而是**只有fullGC被触发才会被清除!**这是因为在minorGC时,类加载器对象是作为GCroot使用的
所以,我们可以得出一个结论,元空间中的类要被清理的三个条件约束下,元空间若存储爆满的话只能调用FullGC以期望更多的类达成被清理的条件

你可能感兴趣的:(刨根问底系列)