Java堆空间详解之新生代和老年代

Java运行时内存区主要分为 运行时栈(虚拟机栈)、本地方法栈、程序计数器、堆空间、方法区(JDK1.8之后是元空间),今天来聊一聊我们的堆空间.

一个对象或者数组的创建是在堆空间中完成的,堆的大小是有限的(固定的),所以,必不可少的我们要考虑一下堆的空间分配问题和对象的分配问题.

空间分配问题:

堆空间默认的初始化内存最小值为 系统内存/64,最大值为系统内存/4;我们可以通过命令 -Xms666m -Xmx666m,来将堆空间的初始化大小最小值和最大值都设置为666m;

public class HeapSpaceInitial {
    public static void main(String[] args) {

        //返回Java虚拟机中的堆内存总量
        long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
        //返回Java虚拟机试图使用的最大堆内存量
        long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;

        System.out.println("-Xms : " + initialMemory + "M");
        System.out.println("-Xmx : " + maxMemory + "M");
    }
}

输出结果如下:

因为我的系统内存是16G,所以对应的 243M 和3602M.

我们通过

设置堆空间的最小和最大内存之后,输出结果如下:

看到这里大家也许会有疑问,为什么这里输出的数据会跟我们的实际分配的数据有一定的差别呢? 来,我们来详细探究一下.运行起来刚才的程序,然后我们通过命令行来进行查看

这里 可以看到具体的堆空间的每个空间分配的大小情况.大家用电脑中的计算机进行加一下,可以发现.

S0C(S0区)+S1C(S1区)+EC(Eden区)+OC(老年代区) = 681984/1024 = 666M~ 

咦,这里怎么会又一样了呢, 那我们再做一下试验,S0C(S0区)+EC(Eden区)+OC(老年代区) = 653824/1024=638M,这个结果跟我们输出的结果是一样的.

这是因为在JVM规定当中,新生代区分为Eden区(伊甸园区)、S0区(Survivor0区,幸存者0区)S1区(Survivor1区,幸存者1区) ,而在两个幸存者区中只有一个空间会存放数据,另外一个空间是空的,所以在计算堆空间大小的时候,我们只计算一个空间大小.这也就是为什么会有一定偏差的原因

 通过上面的实验结果,我们也可以发现新生代区和老年代区的默认分配大小比例为1:2(222M:444M).我们可以通过参数-XX:NewRatio=a,来设置新生代和老年代的大小比例为1:a,

 

我们在通过随便跑一个没有设置过的分配比例的代码(如图,我随便用了一个EdenSurvivorTest方法),

代码如下:

public class EdenSurvivorTest {
    public static void main(String[] args) {
        System.out.println("进行测试,进行测试");
        try {
            //睡一会,方便我们查看命令
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

然后再cmd中打入命令jsp,查看当前运行的方法对应的pid,然后用命令 jinfo -flag NewRatio pid 来查看,得出结论,默认的新生区和老年区的大小比例为1:2.

对象分配过程:

首先,我们说一下,一个对象的分配一般过程,如图:

对象最一开始创建的时候是分配在在Eden区中的,当我们不断的创建对象,Eden区不可避免的会存放满,这个时候就会触发YGC,而此时根据算法得出那些对象是还有引用的,这个时候就会将这些对象放入到Survivor区中的S0区(随机放入S0和S1,此处我们用S0举例子),此时S0区就是FROM区,S1区就是TO区. 这些放入到Survivor区中的对象会有一个age(年龄计数器),记录这个对象在Survivor区中存放的迭代次数.每次迭代之后,age+1.

随后在不断迭代这个过程中,如果Survivor区中有兑现给的age到达了 我们设置的-XX:MaxTenuringThreshold 次数(默认是15,但是不同的JVM和不同的GC ,此数值都不相同),此时我们就会将Survivor区中的对象放到到Old(老年代区).

通过代码来验证一下:

public class HeapInstanceTest {
    //创建一个随机大小的字节数组
    byte[] buffer = new byte[new Random().nextInt(1024 * 200)];

    public static void main(String[] args) {
        ArrayList<HeapInstanceTest> list = new ArrayList<HeapInstanceTest>();
        while (true) {
            list.add(new HeapInstanceTest());
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

程序最终会以OOM异常结束.在结束前,我们可以使用JDK自带的Java VisualVM工具来查看此时内存的情况:

由图中可以看出.Eden区的大小是不断增多到达满值之后触发GC一下降低到0,然后继续增多....而对应的,Eden区GC之后,有引用的对象放入到了S0区,然后迭代次数多了之后Old区的对象开始多了起来.

我们再说一下对象分配的特殊情况,如图:

当我们创建的新对象太大,超出了Eden区大小或者是本来Eden区就已经存放了一些数据的时候,此时新对象创建,判断Eden区放不下,这时候就会触发YGC,如果此时Eden区放得下,那么我们就会将之放入Eden区,这也是我们上面说的一般情况中的.

而如果是因为创建的新对象太大,YGC之后,Eden区依然放不下的话,我们就判断Old区是否能放下,如果能放下的话,直接分配到Old区,而如果放不下的话,我们就会触发FGC,FGC之后如果能放下,我们就会放入到Old区;FGC之后Old区依然放不下的话,JVM就会直接报出OOM异常,退出程序.

当我们对象放入Eden区之后,Eden区满,触发YGC,此时会将还有引用的对象放入到Survivor区,如果此时Survivor区放不下的话,就会直接晋升到老年代.

还有一种情况是,如果在Survivor区中相同年龄的对象的所有大小之和超过Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到-XX:MaxTenuringThreshold中要求的年龄。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JVM将内存分为新生代年代的原因是为了更好地管理内存和垃圾回收。新生代主要存放新创建的对象,内存大小相对较小,垃圾回收频率较高。而年代主要存放JVM认为生命周期较长的对象,内存大小相对较大,垃圾回收频率较低。这种分代的设计可以提高垃圾回收的效率。 在新生代中,一般情况下,新创建的对象会被分配到Eden区,经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每经历一次Minor GC,年龄就会增加1岁,当年龄增加到一定程度时,就会被移动到年代中。因为新生代中的对象大部分是朝生夕死的,所以在新生代的垃圾回收算法使用的是复制算法,即将内存分为两块,每次只使用其中一块,当一块内存用完时,将还存活的对象复制到另一块上。这种算法不会产生内存碎片。 而年代中的对象生命周期较长,所以采用的是标记-清除算法或标记-整理算法来进行垃圾回收。这些算法会标记并清除或整理不再使用的对象,以释放内存空间。 总之,将内存分为新生代年代可以根据对象的生命周期和内存使用情况来进行更有效的垃圾回收和内存管理。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *3* [JVM中的新生代年代详解](https://blog.csdn.net/Xeon_CC/article/details/109080695)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [JVM内存为什么要分成新生代年代,持久代。新生代中为什么要分为Eden和Survivor](https://blog.csdn.net/weixin_57321519/article/details/122829043)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值