JVM实战(17)——模拟对象晋升

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析

阶段4、深入jdk其余源码解析

阶段5、深入jvm源码解析

一、简介

上一章,我们已经进行了一次Young GC日志的分析,本章我们继续结合代码示例做实验,来看看对象是如何从新生代进入老年代的。我们之前讲过新生代对象晋升到老年代的几种场景:

  • 躲过15次GC
  • 符合动态年龄判断规则
  • Young GC后存活对象放不进Survivor
  • 大对象直接进入老年代

本章,我们通过示例代码模拟最常见的一种场景------Young GC后存活对象放不进Survivor。

1.1 JVM内存参数

我们的示例程序基于JDK1.8,JVM参数如下:
-XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=10485760 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log

上述给新生代分配了10MB空间,老年代也是10MB,参数注意两点:

  • -XX:PretenureSizeThreshold=10485760:超过10MB的大对象直接进入老年代
  • -XX:MaxTenuringThreshold=15:对象年龄到达15时进入老年代

二、示例程序

2.1 程序源码

示例程序代码如下:

复制代码
    public class Demo1 {
        public static void main(String[] args) {
            byte[] array1 = new byte[2 * 1024 * 1024];
            array1 = new byte[2 * 1024 * 1024];
            array1 = new byte[2 * 1024 * 1024];
    
            byte[] array2 = new byte[128 * 1024];
            array2 = null;
    
            byte[] array3 = new byte[2 * 1024 * 1024];
        }
    }

2.2 JVM内存模型

我们根据上述代码来分析下内存中的对象分配。首先连续创建了三个2MB的数组对象,将array1指向最后一个数组对象,然后创建了一个128KB的数组,将array2赋null:

注意,Eden区里会有一些"未知对象",根据模拟Young GC一文中的分析,对象大小在500KB左右,我们后续会通过工具分析这些"未知对象"到底是什么。

然后,执行代码byte[] array3 = new byte[2 * 1024 * 1024],希望在Eden区继续创建一个2MB的数组。显然,Eden区的空间不足了,此时就会触发Young GC。

2.3 程序执行

我们执行程序,得到以下GC日志:

复制代码
    0.352: [GC (Allocation Failure) 0.353: [ParNew: 8106K->623K(9216K), 0.0021991 secs] 8106K->2673K(19456K), 0.0033689 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    Heap
     par new generation   total 9216K, used 2837K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
      eden space 8192K,  27% used [0x00000000fec00000, 0x00000000fee297c0, 0x00000000ff400000)
      from space 1024K,  60% used [0x00000000ff500000, 0x00000000ff59be50, 0x00000000ff600000)
      to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
     concurrent mark-sweep generation total 10240K, used 2050K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
     Metaspace       used 3147K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 343K, capacity 388K, committed 512K, reserved 1048576K

三、日志分析

我们先来看下日志中的下面这行,这是本次GC情况的概要说明:

复制代码
    0.352: [GC (Allocation Failure) 0.353: [ParNew: 8106K->623K(9216K), 0.0021991 secs] 8106K->2673K(19456K), 0.0033689 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

ParNew: 8106K->623K(9216K): 可以看到,本次Young GC后,新生代只剩下了623KB(未知对象)。但是明明array1还引用着一个2MB的数组:

我们注意下Survivor的大小,只有1MB,是容纳不下2MB数组和未知对象的。根据"Young GC后存活对象放不进Survivor会进入老年代"规则,ParNew会将2MB数组转移到老年代,未知对象转移到Survivor:

通过观察GC日志,也印证了这一点:
from space 1024K, 60% used: Survivor中有600多KB的数据,就是未知对象;
concurrent mark-sweep generation total 10240K, used 2050K: 老年代中的2MB对象就是array3引用的数组对象。

四、总结

本章通过GC日志分析了一种新生代对象进入老年代的示例,即Young GC后存活对象放不进Survivor,则会进行老年代。

需要注意的是,并不是所有存活对象都会进入老年代,可能会有部分对象留在Survivor区,部分对象进入老年代。

相关推荐
金銀銅鐵7 小时前
同一个 Hello World,Kotlin 和 Java 在字节码层面差在哪
jvm·后端·kotlin
yeccheng1 天前
MySQL 索引超详解:从底层 B+ 树到面试高频考点
java·开发语言·jvm·mysql·面试
On The Road :4 天前
深入理解 JVM 垃圾回收:GC 是什么、如何判定垃圾、四大回收算法全解析
jvm·算法
Yves..4 天前
JVM 学习笔记:内存区域、类加载机制与垃圾回收
jvm
akinggw5 天前
libagentcore - 一个基于c++的AI多agent系统升级到2.0版本
开发语言·jvm·c++
yh弓长5 天前
JVM 核心机制
jvm
tg_xianheyun6 天前
腾讯云国际账号注册代充值适合哪些海外业务场景?
jvm
yuniko-n6 天前
【JVM】对象内存布局
java·开发语言·jvm
wuminyu6 天前
JVM执行引擎学习总结:热点字节码编译成本地机器码
java·linux·c语言·jvm·c++
wuminyu6 天前
JVM规范学习总结:解释器与JIT编译器如何处理JVM字节码
java·linux·c语言·jvm·c++