HTML · 2022年2月20日 0

JVM内存分配和回收策略

1、对象优先在Eden区
    首先,new出来的对象都放在堆内存里面,而堆内存又划分为老年代,年轻待,而年轻代又划分为:Eden区,Survior区,Survior区又分为:From区和To区

大多数情况下,对象在Eden中分配,当Eden中没有足够空间进行分配时,虚拟机会发起一次Minor Gc
Minor Gc 和 Full GC有什么不同呢
Minor Gc又称为Young Gc:指发生在新生代的垃圾收集动作,非常频繁,回收速度一遍比较快
Fill Gc又称为Major Gc:一般是回收老年代,年轻代,方法区的垃圾,速度一般比Minor Gc慢十倍以上

2、大对象直接进入老年代(为什么大对象直接进入老年代?为了避免大对象分配内存的赋值操作而降低效率)
    大对象就是需要大量连续内存空间的对象,如:数组,字符串,JVM可以通过参数:-XX:PretenureSizeThreshold 来设置大对象的大小,如果对象超过设置值的大小,就会直接进入老年代,不会进入年轻代(这个参数只在Serial和ParNew两个收集器有效)

3、长期存活的对象将进入老年代
    虚拟机给每个对象一个年龄计数器,如果对象在Eden区出生,并经过第一次 Minor Gc 后能存活,并且能被 Survior 容纳,将会被移动到 Survior 空间中,给对象年龄设置为1,对象在Survior中每熬过一次 Minor Gc 年龄将加1岁,当它的age增加到一定程度(默认15岁)就会被晋升到老年代
    Minor Gc 存活的对象如果Survior区放不下:部分放入老年代,部分放入Survior区

4、Eden和Survior区的默认比例是 8:1:1
    大量的对象会被分配到Eden区,Eden区满了后会触发Minor Gc 可能会有大部分对象成为垃圾被回收掉,剩余存活的对象会被移动到 Survior区。

5、对象动态年龄判断
    如果当前放对象的 Survior区里一批对象的总大小大于这块区域的50%,那么此时大于等于这批对象age00最大的值的对象,就可以直接进入老年代了,对象动态年龄判断机制一般是在Minor Gc 后

    年龄1+年龄2+年龄3+年龄N的对象加起来的空间,大于survivor区域的一半,就会让年龄N和年龄N以上的对象进入老年代。动态年龄判断应该是这样子的。说的通俗一点:就是年龄从小到大对象的占据空间的累加和,而不是某一个特定年龄对象占据的空间。

6、老年代空间分配担保机制
    年轻代Minor Gc 之前,JVM会计算老年代剩余可用空间,如果这个可用空间<年轻代现有所有对象大小之和(包括垃圾对象),就会看是否设置:-XX:-HandlePromotionFaile,是否设置(JDK1.8默认开启),如果有,会看老年代可用内存大小是否>之前Minor Gc 后进入老年代的对象平均大小,如果小于或者没设置参数,就会触发一次 Full Gc,分配担保机制如图

如果回收完还是没有足够空间存放新的对象,就会发生OOM

Java虚拟机如何判断对象是否存活
    1、引用计数法:
        对象A具有引用计数器,当对象B对A产生引用时,引用计数器的值+1,当引用断开时,引用计数器的值-1。(会有一个问题:如果A和B循环引用,计数器的值无法为0),当对象引用计数器为0时,即无其他对象引用,判定为死亡,可被回收,但是存在循环引用问题,导致对象一直存活
    2、可达性分析法:
        从Gc Root开始向下直接找到对象或者间接找到对象,称为对象可达,反之不可达,直接或间接可达的对象,即存活(哪些对象可以作为Gc Root:虚拟机栈引用对象,本地方法站引用对象,静态属性引用对象,常量引用对象)
        对象引用的分类:
            1、强引用:程序中普遍存在的对象引用
            2、软引用:SoftReference实现,内存移除前回收
            3、弱引用:WeakReference实现,下一次垃圾前回收
            4、虚引用:PhantomReference实现,形同虚设
        对象怎样"起死回生":
            首先A被标记不可达,进行筛选,是否覆盖finalize()方法并且没有被调用过,会放入F-Queue中,JVM会启动进程进行重新标记,此时对象和引用链上任意一个对象建立管理就可以起死回生。(不建议,运行代价高,不确定性大,无法保证对象调用顺序)
 JVM垃圾回收算法:
    1、标记清除算法
        标记出所有需要回收的对象,标记完成后同一回收,这种方式会产生大量不连续的碎片,当有大对象需要连续的内存时,有可能会再次触发垃圾回收
    2、为了解决以上问题,引入了标记整理算法
        标记的过程和之前一样,但是在回收后会集中整理内存区域,得到较为连续的内存区,但是效率比较低,避免产生大量的内存碎片
    3、复制算法
        将内存分为大小相同的两块,每次只用其中一块,第一块的内存用完了,就将还存活的对象复制到第二块内存,然后再把第一块的空间一次性清理掉,效率高,没碎片,内存利用率低,不适合在存活率高的老年代使用
    4、JVM使用的是,分代回收算法
        对于新生代对象,使用复制算法,把内存比例划分为8:1:1,对于老年代/元数据区的对象存活率较高,采用标记清楚/整理的算法
 JVM垃圾收集器:
    1、Serial收集器
        如果收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现

  上图展示7种不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用
Serial收集器:
    单线程的收集器,意味着只会使用一个cpu或一条线程去完成垃圾收集工作,也意味这它在进行垃圾收集时必须暂停其它线程的所有工作Stop The Word,Serial收集器的收集算法是:新生代采用复制算法,老年代采用标记整理算法,Serial收集器:简单,高效没有线程交互的开销。

Serial Old收集器:
    是Serial收集器的老年代版本,单线程,用途:JDK1.5以及以前的版本中与Parllel Scavenge收集器搭配使用,用途2:作为CMS收集器的后备方案

ParNew收集器:
    其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集以外,其它和Serial收集器一样,ParNew收集器默认的收集线程数和cpu核数相同,也可以使用-XX:ParallelGCThreads指定线程收集数,一般不推荐修改,是运行在Server模式下的虚拟机的首要选择,除了Serial收集器外,只有它能与CMS收集器配合工作

Parallel Scavenge收集器:
    类似与ParNew收集器,是server模式下的默认收集器,采用的收集算法是:新生代采用复制算法,老年代采用标记-整理算法,特点:吞吐量(高效率的利用cpu)吞吐量:运行用户代码的时间/(运行用户代码的时间+垃圾收集时间),提供了很多参数,供用户找到最合适的停顿时间,或最大吞吐量。如果对于垃圾收集器运作原理不太了解,使用Parallel Scavenge收集器配合自适应调节策略,把内存管理的调优交友虚拟机区完成

Parallel Old收集器:
    是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法,在注重吞吐量以及cpu资源的场合,都可以考虑Parallel Scavenge 和Parallel Old

CMS收集器:
    是以获取最短回收停顿时间为目标的收集器,是HotSpot虚拟机第一款真正意义上的并发收集器,是标记-清除算法实现的,过程分为4个步骤。
        1.初始标记:暂停其它线程,并标记GCRoot能直接关联到的对象
        2.并发标记:用一个闭包结构区记录可达对象,但在这个阶段结束,这个闭包结构不能保证包含当前所有的可达对象。会跟踪记录发生引用更新的地方。这个过程占用整个GC的70%-80%的时间
        3.重写标记:修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。停顿时间比初始标记阶段稍长
        4.并发清除:开启用户线程,同时GC线程开始对未标记的区域做清扫
    优点:并发收集,低停顿。
    缺点:
        1.对CPU资源敏感。
        2.无法处理浮动垃圾。
        3.使用标记-清除算法会产生大量空间碎片产生
        4.执行过程中的不确定性,会存在上一次垃圾回收还没执行完,然后垃圾回收又被触发的情况,特别是在 并发标记,并发清理阶段出现

G1收集器:
    是一款面向服务器的垃圾收集器,针对配备多颗处理器及大容量内存的机器,以及高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征
    基本特性:
        1.G1将堆划分未多个大小相等的对立区域(Region),JVM最多可以有2048个Region
        2.一般Region大小等于堆大小除以2048
        3.G1保留了年轻代和老年代的概念,但不再是物理隔阂了,他们都是(可以不连续)Region的集合
        4.默认年轻代对堆内存的占比是5%
        5.Region的区域功能可能会动态变化(之前是年轻代,垃圾回收后可能又可能变成老年代)
    G1对大对象的处理:
        G1有专门分配大对象的Region叫Humongous区
        在G1中,大对象的判断规则就是一个大对象超过了一个Region大小的50%
    G1垃圾收集过程:
        1.初始标记(STW):暂停所有的其它线程,并记录下GC Roots直接能引用的对象
        2.并发标记:用一个闭包机构区记录可达对象;跟踪记录发生引用更新的地方
        3.最终标记(STW):修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录
        4.筛选回收(STW):首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来定制回收计划