【拥抱大厂系列】百度面试官问过的 “JVM内存分配与回收策略原理”,我用这篇文章搞定了

点个赞,看一看,好习惯!本文 GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了3个月总结的一线大厂Java面试总结,本人已拿腾讯等大厂offer。html

在前面的一篇文章深刻理解Java虚拟机-如何利用VisualVM进行性能分析中讲到了一些关于JVM调优的知识,可是,其实,仍是有一些问题没有很是清楚的能够回答的,这里先给出几个问题,而后,咱们再展开这篇文章须要讲解的知识。java

  • 咱们生成的对象最开始在哪分配?Eden?Survivor?仍是老年代呢?
  • 进入到老年代须要知足什么条件呢?

接下来,咱们就带着这两个问题展开全文。git

1 对象优先在哪分配

其实,经过前面几篇文章的讲解,这个问题其实已经见怪不怪了,在大多数的状况下,对象都是在新生代Eden区分配的,在前面的文章咱们提到,在Eden区中若是内存不够分配的话,就会进行一次Minor GC。同时,咱们还知道年轻代中默认下Eden:Survivor0:Survivor2 = 8:1:1,同时,还能经过参数-XX:SurvivorRatio来设置这个比例(关于这些参数的分析均可以查看这篇文章:深刻理解Java虚拟机-经常使用vm参数分析)。github

下面咱们经过一个例子来分析是否是这样的。面试

1.1 实例

给定JVM参数:-Xms40M -Xmx40M -Xmn10M -verbose:gc -XX:+PrintGCDetails -XX:SurvivorRatio=4算法

前面三个参数设置Java堆的大小为40M,新生代为10M,紧跟着后面两个是用于输入GC信息。更多参数能够查看这篇文章:深刻理解Java虚拟机-经常使用vm参数分析数组

/**
 * @ClassName Test_01
 * @Description 参数:-Xms40M -Xmx40M -Xmn20M -XX:+PrintGCDetails -XX:+UseSerialGC -XX:SurvivorRatio=8
 * @Author 欧阳思海
 * @Date 2019/12/3 16:00
 * @Version 1.0
 **/
public class Test_01 {

    private static final int M = 1024 * 1024;

    public static void test() {
        byte[] alloc1, alloc2, alloc3, alloc4;
        alloc1 = new byte[5 * M];
        alloc2 = new byte[5 * M];
        alloc3 = new byte[5 * M];
        alloc4 = new byte[10 * M];

    }

    public static void main(String[] args) {
        test();
    }

}

输入结果: 微信

分析数据结构

  • eden:from:to=8:1:1,这个由于前面设置了参数-XX:SurvivorRatio=8
  • 新生代分配了20M的内存,因此前面三个byte数组能够分配,可是,分配第四个的时候,空间不够,因此,须要进行一次Minor GC,GC以后,新生代从12534K变为598K
  • 前面在新生代分配的内存Minor GC以后,进入到了Survivor,可是,Survivor不够分配,因此进入到了老年代,老年代已用内存达到了50%

1.2 回答问题

因此,通过上面的例子咱们发现,对象通常优先在新生代分配的,若是新生代内存不够,就进行Minor GC回收内存。数据结构和算法

2 进入到老年代须要知足什么条件

先给出答案,分为几点。

  • 条件①:大对象直接进入到老年代
  • 条件②:长期存活的对象能够进入到老年代
  • 条件③:若是在Survivor空间中相同年龄全部对象的大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象直接进入到老年代

2.1 分析条件①

  • 哪些属于大对象呢?

通常来讲大对象指的是很长的字符串及数组,或者静态对象

  • 那么须要知足多大才是大对象呢?

这个虚拟机提供了一个参数-XX:PretenureSizeThreshold=n,只须要大于这个参数所设置的值,就能够直接进入到老年代。

step1: 解决了这两个问题,首先,咱们不设置上面的参数的例子,将对象的内存大于Eden的大小看看状况。

/**
 * @ClassName Test_01
 * @Description 参数:-Xms40M -Xmx40M -Xmn20M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC
 * @Author 欧阳思海
 * @Date 2019/12/3 16:00
 * @Version 1.0
 **/
public class Test_01 {

    private static final int M = 1024 * 1024;

    public static void test() {
        byte[] alloc1, alloc2, alloc3, alloc4;
//        alloc1 = new byte[5 * M];
//        alloc2 = new byte[5 * M];
//        alloc3 = new byte[5 * M];
        alloc4 = new byte[22 * M];

    }

    public static void main(String[] args) {
        test();
    }

}

咱们发现分配失败,Java堆溢出,由于超过了最大值。

step2: 下面咱们看一个例子:设置-XX:PretenureSizeThreshold=104,857,600,这个单位是B字节(Byte/bait),因此这里是100M

/**
 * @ClassName Test_01
 * @Description 参数:-Xms2048M -Xmx2048M -Xmn1024M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC -XX:PretenureSizeThreshold=104,857,600
 * @Author 欧阳思海
 * @Date 2019/12/3 16:00
 * @Version 1.0
 **/
public class Test_01 {

    private static final int M = 1024 * 1024;

    public static void test() {
        byte[] alloc1, alloc2, alloc3, alloc4;
//        alloc1 = new byte[5 * M];
//        alloc2 = new byte[5 * M];
//        alloc3 = new byte[5 * M];
        alloc4 = new byte[500 * M];

    }

    public static void main(String[] args) {
        test();
    }

}

发现新生代没有分配,直接在老年代分配。

注意: 参数PretenureSizeThreshold只对SerialParNew两款收集器有效。

2.2 分析条件②

进入老年代规则:这里须要知道虚拟机对每一个对象有个对象年龄计数器,若是对象在Eden出生通过第一次Minor GC后任然存活,而且可以被Survivor容纳,将被移动到Survivor空间中,而且年龄设置为1。接下来,对象在Survivor中每次通过一次Minor GC,年龄就增长1,默认当年龄达到15,就会进入到老年代。

晋升到老年代的年龄阈值,能够经过参数-XX:MaxTenuringThreshold设置。

在下面的实例中,咱们设置-XX:MaxTenuringThreshold=1

/**
 * @ClassName Test_01
 * @Description 参数:-Xms2048M -Xmx2048M -Xmn1024M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC -XX:MaxTenuringThreshold=1
 * @Author 欧阳思海
 * @Date 2019/12/3 16:00
 * @Version 1.0
 **/
public class Test_01 {

    private static final int M = 1024 * 1024;

    public static void test() {
        byte[] alloc1, alloc2, alloc3, alloc4;
        alloc1 = new byte[300 * M];
        alloc2 = new byte[300 * M];
        alloc3 = new byte[300 * M];
        alloc4 = new byte[500 * M];

    }

    public static void main(String[] args) {
        test();
    }

}

从结果能够看出,from和to都没有占用内存,而老年代则占用了不少内存。

2.3 分析条件③

条件③是:若是在Survivor空间中相同年龄全部对象的大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象直接进入到老年代,而不须要等到参数-XX:MaxTenuringThreshold设置的年龄。

实例分析

/**
 * @ClassName Test_01
 * @Description 参数:-Xms2048M -Xmx2048M -Xmn1024M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC
 * @Author 欧阳思海
 * @Date 2019/12/3 16:00
 * @Version 1.0
 **/
public class Test_01 {

    private static final int M = 1024 * 1024;

    public static void test() {
        byte[] alloc1, alloc2, alloc3, alloc4;
        alloc1 = new byte[100 * M];
        alloc2 = new byte[100 * M];
        //分配alloc3以前,空间不够,因此minor GC,接着分配alloc3=900M大于Survivor空间一半,直接到老年代。
        alloc3 = new byte[900 * M];
        
//        alloc4 = new byte[500 * M];

    }

    public static void main(String[] args) {
        test();
    }

}

输入结果:

分配alloc3以前,空间不够,因此minor GC,接着分配alloc3=900M大于Survivor空间一半,直接到老年代。从而发现,survivor占用0,而老年代占用900M。

3 总结

这篇文章主要讲解了JVM内存分配与回收策略的原理,回答了下面的这两个问题。

  • 咱们生成的对象最开始在哪分配?Eden?Survivor?仍是老年代呢?
  • 进入到老年代须要知足什么条件呢? 另外,我花了3个月时间把Java学习和面试的总结整理成了一本电子书!目录以下

如今免费分享你们,在个人公众号好好学java回复Java面试便可获取。

有收获?但愿老铁们来个三连击,给更多的人看到这篇文章

一、老铁们,关注个人原创微信公众号「好好学java」,专一于Java、数据结构和算法、微服务、中间件等技术分享,保证你看完有所收获。

二、给俺点个赞呗,可让更多的人看到这篇文章,顺便激励下我继续写做,嘻嘻。

点赞是对我最大的鼓励 ↓↓↓↓↓↓

相关文章
相关标签/搜索