其他分享
首页 > 其他分享> > jvm分代模型

jvm分代模型

作者:互联网

分代模型介绍

根据写代码方式的不同,采用不同的方式来创建和使用对象,其实对象的生存周期不同,所以JVM将Java堆内存划分为两个区域:年轻代、老年代

在这里插入图片描述

通过下面的代码,来看下方法区,Java虚拟机栈和Java堆内存的关系图

public class HelloWorld {
    private static Demo1 demo1 = new Demo1();

    public static void main(String[] args) throws InterruptedException {
        executeDemo2();
        while (true) {
            executeDemo1();
            Thread.sleep(6000);
        }
    }

    public static void executeDemo2() {
        Demo2 demo2 = new Demo2();
        demo2.execute();
    }

    public static void executeDemo1() {
        demo1.execute();
    }
}

HelloWorld类中一个静态变量demo1引用了Demo1对象,由于静态变量会长期留存在内存中使用,则demo1对象会在年轻代中留存一会,然后最终进入老年代,此时的关系图如下:
在这里插入图片描述

进入main方法后,会先调用executeDemo2(),执行Demo2对象的execute()方法,在executeDemo2方法会创建Demo2对象,这个对象他是用完就会回收,所以是会放在年轻代里的,由栈帧里的局部变量来引用

在这里插入图片描述

一旦executeDemo2()方法执行完毕后,方法的栈帧则会出栈内栈,栈内存立即回收,对应的年代里的Demo2对象会被称为垃圾对象,等待垃圾回收机制回收

在这里插入图片描述

紧接着会执行while循环代码,会周期性的调用executeDemo1()方法
在这里插入图片描述

垃圾回收机制算法

复制算法

针对新生代的垃圾回收算法,叫做复制算法,顾名思义是将存货的对象复制到另外的地方。

这就是所谓的“复制算法“,把新生代内存划分为两块内存区域,然后只使用其中一块内存

待那块内存快满的时候,就把里面的存活对象一次性转移到另外一块内存区域,保证没有内存碎片

接着一次性回收原来那块内存区域的垃圾对象,再次空出来一块内存区域。两块内存区域就这么重复着循环使用

在这里插入图片描述

​ 按照上面的思路,从图中可以看出,每次都需要空出一般的内存不使用,非常的浪费内存,加入给新生代1G的内存空间,但是按照上面的模型只有512MB的空间可以使用,这样的话对内存的使用率只有50%

​ 我们的代码中不停的创建对象然后分配在新生代中,但是一般很快创建的对象没人引用,成为垃圾对象,此时被垃圾回收机制回收,绝大多数的对象都是存活周期非常短的对象,可能被创建出来1毫秒之后就没人引用了,所以在每一次新生代垃圾回收后,绝大多数数对象都被垃圾回收,只有极少个数的对象存活下来。

​ 所以JVM内存模型中,把新生代内存区域划分为三块:

1个Eden区,2个Survivor区,其中Eden区占80%内存空间,每一块Survivor区各占10%内存空间,比如说Eden区有800MB内存,每一块Survivor区就100MB内存,如下图:

在这里插入图片描述

平时可以使用的,就是Eden区和其中一块Survivor区,那么相当于就是有900MB的内存是可以使用的

刚开始对象都是分配在Eden区内的,如果Eden区快满了,此时就会触发垃圾回收,此时就会把Eden区中的存活对象都一次性转移到一块空着的Survivor区。接着Eden区就会被清空,然后再次分配新对象到Eden区里,此时:Eden区和一块Survivor区里是有对象的,其中Survivor区里放的是上一次Minor GC后存活的对象。

如果下次再次Eden区满,那么再次触发Minor GC,就会把Eden区和放着上一次Minor GC后存活对象的Survivor区内的存活对象,转移到另外一块Survivor区去

在这里插入图片描述

这么做最大的好处,就是只有10%的内存空间是被闲置的,90%的内存都被使用上了

无论是垃圾回收的性能,内存碎片的控制,还是说内存使用的效率,都非常的好

内存标记算法

被使用的内存区域中的垃圾对象进行标记,标记出哪些对象可以被垃圾回收,然后直接对内存区域中的对象进行垃圾回收,把内存空出来,在被内存使用的区域里,回收掉了大量的垃圾对象,但是保留了一些被人引用的存活对象。但是存活的对象在内存区域中东一个西一个,非常的凌乱,而且造成了大量的内存碎片

在这里插入图片描述

图中圈红的则为出来的内存碎片,有些内存太小则无法被其他对象使用,则会造成内存浪费

年轻代

老年代

新生代里的对象一般在什么场景下会进入老年代?

在这里插入图片描述

在经过GC后,Edenl区里还有150MB的对象存活,Survivor区的内存只有100MB,此时无法放入S区,则将这些对象直接转移到老年代中去

在这里插入图片描述

老年代空间分配担保规则

​ 存在一个问题:如果新生代里有大量对象存活下来,确实是Survivor区装不下,必须转移到老年代中去,但是如果老年代里空间也不够放这些对象,此时JVM会怎么操作?

​ 首先,在执行任何一次Minor GC之前,JVM会先检查一下老年代可用的可用内存空间,是否大于新生代所有对象的总大小。

​ **为啥检查这个呢?**因为最极端的情况下,可能新生代Minor GC过后,所有对象都存活下来了,那岂不是新生代所有对象全部要进入老年代?

​ 如果发现老年代的内存大小是大于新生代所有对象的,就可以放心大胆的对新生代发起一次MinorGC,因为即使Minor GC之后所有对象存活,Survivor区放不下了,也可以转移到老年代去。

但 是假如执行Minor GC之前,发现老年代的可用内存已经小于了新生代的全部对象大小了

​ 那么这个时候是不是有可能在Minor GC之后新生代的对象全部存活下来,然后全部需要转移到老年代去,但是老年代空间又不够?

​ 所以假如Minor GC之前,发现老年代的可用内存已经小于了新生代的全部对象大小了,就会看一个“-XX:-

**HandlePromotionFailure”**的参数是否设置

​ 下一步判断,就是看看老年代的内存大小,是否大于之前每一次Minor GC后进入老年代的对象的平均大小

​ 举个例子,之前每次Minor GC后,平均都有10MB左右的对象会进入老年代,那么此时老年代可用内存大于10MB。

​ 这就说明,很可能这次Minor GC过后也是差不多10MB左右的对象会进入老年代,此时老年代空间是够的

​ 如果上面那个步骤判断失败了,或者是“-XX:-HandlePromotionFailure”参数没设置,此时就会直接触发一次“Full GC”,就是对老年代进行垃圾回收,尽量腾出来一些内存空间,然后再执行Minor GC

​ 如果上面两个步骤都判断成功了,那么就是说可以冒点风险尝试一下Minor GC。此时进行Minor GC有几种可能

Full GC就是对老年代进行垃圾回收,同时也一般会对新生代进行垃圾回收。

因为这个时候必须得把老年代里的没人引用的对象给回收掉,然后才可能让Minor GC过后剩余的存活对象进入老年代里面。

如果要是Full GC过后,老年代还是没有足够的空间存放Minor GC过后的剩余存活对象,那么此时就会导致所谓的

“OOM”内存溢出

上面的文字描述比较绕,可参考下面的流程图查看:

在这里插入图片描述

老年代垃圾回收算法

触发垃圾回收的时机

老年代采取的是标记整理算法

如果系统频繁出现老年代的Full GC垃圾回收,会导致系统性能被严重影响,出现频繁卡顿的情况

标签:Survivor,对象,模型,分代,GC,内存,jvm,年代,Minor
来源: https://blog.csdn.net/cen50958/article/details/116277668