哈喽哈喽大家猴,我是把代码写成bug的大头菜。公众号:大头菜技术(bigheadit)。原创不易,但欢迎转载。

上一篇文章:JVM-动态年龄判断介绍了对象进入老年代的四种方式

  • 大对象
  • 动态年龄判断
  • minor gc后,survivor区空间不能容纳全部存活对象
  • 存活对象达到年龄阈值。比如15

接下来,我们将用代码方式来验证这四种方式。

知识回顾

在实战开始之前,我们先复习一下知识点:

0.134: [GC (Allocation Failure) 0.134: [ParNew: 7444K->685K(9216K), 0.0011650 secs]
7444K->685K(19456K), 0.0012583 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]

因为涉及到GC日志的查看,我就简单介绍一下日志的大概含义:

  • 0.134。含义就是在程序启动后多久,发生了垃圾回收。单位:秒
  • GC (Allocation Failure)。GC就是发生了垃圾回收的意思,这个不用说都清楚。然后括号里面的Allocation Failure,就是发生垃圾回收的原因。这里Allocation Failure,就是空间分配失败导致发生的垃圾回收。
  • [ParNew:ParNew就是新生代垃圾回收使用的垃圾收集器,同时也代表发生了minor gc。
  • 7444K->685K(9216K), 0.0011650 secs]。接着7444K,是新生代垃圾回收前已使用的空间。685K,是新生代垃圾回收后已使用的空间。9216K,是新生代的总空间。0.0011650 secs,就是垃圾回收需要耗费的时间。
  • 7444K->685K(19456K), 0.0012583 secs],7444K是整个堆(新生代+老年代)垃圾回收前已使用的空间。685K是整个堆垃圾回收后已使用的空间。19456K是整个堆的总空间。
  • [Times: user=0.00 sys=0.00, real=0.01 secs] 。就是本次GC消耗的时间,因为这里保持了2为小数,四舍五入嘛,因此都是0。

好了,差不多了,直接开始吧!!!!

大对象

首先,我们简单回顾一下。

书本,也没说明,多大的对象才是大对象,比较抽象。

我们这里直接具体点:

-XX:PretenureSizeThreshold=3m

大于等于3m的对象,就是大对象。

-XX:NewSize=10m -XX:MaxNewSize=10m -XX:InitialHeapSize=20m

这里,我们给新生代10m,堆20m。

也就是说,老年代的空间=20m-10m=10m

-XX:SurvivorRatio=8

eden:s0:s1等于8:1:1

所以eden区为8m,s0为1m,s1为1m

使用的垃圾收集器为ParNew和CMS

-XX:+UseParNewGC -XX:+UseConcMarkSweepGC

打印GC的详细信息

-XX:+PrintGCDetails

打印GC的时间戳

-XX:+PrintGCTimeStamps

那这些GC日志输出在哪里,当然是文件啦。

-Xloggc:bigobject.log

好了,JVM的配置差不多了,直接上代码。

JVM配置参数

-XX:NewSize=10m -XX:MaxNewSize=10m -XX:InitialHeapSize=20m -XX:MaxHeapSize=20m -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3m -XX:MaxTenuringThreshold=15 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:bigobject.log

代码:

byte[] array1 = new byte[2*_1MB];
byte[] array2 = new byte[3*_1MB];

因为2m的对象不是大对象,因此分配到eden区。而3m的对象是大对象,因此分配到old区。

上面,这些都是我们根据理论来推测出来的。

接下来,我们运行一下代码,然后查询日志文件bigobject.log

Java HotSpot(TM) 64-Bit Server VM (25.261-b12) for bsd-amd64 JRE (1.8.0_261-b12), built on Jun 18 2020 06:38:55 by "java_re" with gcc 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.11.45.5)
Memory: 4k page, physical 33554432k(382392k free)/proc/meminfo:CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15 -XX:NewSize=10485760 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=3145728 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
Heappar new generation   total 9216K, used 4849K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)eden space 8192K,  59% used [0x00000007bec00000, 0x00000007bf0bc580, 0x00000007bf400000)from space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)concurrent mark-sweep generation total 10240K, used 3072K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)Metaspace       used 3074K, capacity 4496K, committed 4864K, reserved 1056768Kclass space    used 337K, capacity 388K, committed 512K, reserved 1048576K

现在,我们分析一下日志内容,来验证我们的猜测是否正确。

CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15 -XX:NewSize=10485760 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=3145728 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
Heap

上面这一堆,就是我们自己设置的JVM参数,也有一些系统帮我们添加的。比如:-XX:+UseCompressedClassPointers -XX:+UseCompressedOops

这些不是重点,你了解一下就好。

重点是这个:

Heap  par new generation   total 9216K, used 4849K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)eden space 8192K,  59% used [0x00000007bec00000, 0x00000007bf0bc580, 0x00000007bf400000)from space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)concurrent mark-sweep generation total 10240K, used 3072K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)

代表的含义是:GC后堆的情况,注意是GC后。这里特别强调一下。

par new generation total 9216K, used 4849K
代表的新生代,现在总的空间大小是9216K,已使用的空间大小是4849K。

其实我们知道,有2M对象是进入了eden区的,但是现在4849K明显是大于2048K(2M)的。

那说明了什么?

说明,其实JVM除了加载我们自己写的对象外,还会加载一些其他未知对象。未知对象,主要由JVM本身产生,这部分大家先忽略就好

回到主要问题上:大对象是否会直接进入老年代。

我们看以下这段日志:

concurrent mark-sweep generation total 10240K, used 3072K

老年代的总空间大小是10240K,目前已经使用了3072K。

看到这里,其实相信大家已经可以明白了。我们把大对象定义为大于等于3m的对象。而日志也告诉我们,目前老年代已经被占用了3072K,即3m。

因此,到这里我们已经用代码验证了:大对象会直接进入老年代。

动态年龄判断

首先,我们还是先了解一下什么是动态年龄判断?

书本的解释如下:

总结一下:就是说survivor区中,如果相同年龄的所有对象大小所占用的空间大于survivor空间的一半,年龄大于或等于该年龄对象的,都可以直接进入老年代。

这是书本的说法。。

但其实这个说法是错误的。

相信看过我另一篇文章JVM-动态年龄判断的小伙伴,是了解正确的说法应该是:在survivor区中,所有年龄的对象的所占空间的累加和大于survivor空间的一半,大于或等于该年龄的对象,都可以进入老年代。

接下来我们直接上代码和JVM配置参数:

JVM配置参数:

-XX:NewSize=10m -XX:MaxNewSize=10m -XX:InitialHeapSize=20m -XX:MaxHeapSize=20m -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=10m -XX:MaxTenuringThreshold=15 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:dynamicheck.log

代码:

我们先直接把代码跑起来,然后直接看日志文件:

Java HotSpot(TM) 64-Bit Server VM (25.281-b09) for bsd-amd64 JRE (1.8.0_281-b09), built on Dec  9 2020 12:44:49 by "java_re" with gcc 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.11.45.5)
Memory: 4k page, physical 16777216k(106332k free)/proc/meminfo:CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15 -XX:NewSize=10485760 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=10485760 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC 0.117: [GC (Allocation Failure) 0.118: [ParNew: 7115K->619K(9216K), 0.0029447 secs] 7115K->619K(19456K), 0.0033789 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.122: [GC (Allocation Failure) 0.122: [ParNew: 7223K->0K(9216K), 0.0020215 secs] 7223K->601K(19456K), 0.0020509 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
Heappar new generation   total 9216K, used 2212K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)eden space 8192K,  27% used [0x00000007bec00000, 0x00000007bee290e0, 0x00000007bf400000)from space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)concurrent mark-sweep generation total 10240K, used 601K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)Metaspace       used 2713K, capacity 4486K, committed 4864K, reserved 1056768Kclass space    used 291K, capacity 386K, committed 512K, reserved 1048576K

部分代码剖析:

现在连续分配了3个2M的对象,和1个300K的对象,最后还把array1置为null。此时的堆图应该是这样子的

接下来,还要继续分配一个2M的对象,这个时候eden区还能继续分配空间吗?

肯定不可以,因为eden区只有8M。

那这个时候,只好执行young gc来清理空间了。

接着,我们看一下日志文件:

ParNew: 7115K->619K(9216K)

说明GG前,占用了7115K,这里大概包括3个2m对象+300k对象+几百K未知对象。经过GC后,只剩下619K对象,包括300K对象和未知对象。

然后给新创建的2m对象分配到eden区

此时,我们得观察一个重点:就是from区是1m,就是1024K。现在呢,有619K对象已经来到了from区,是超过from区的一半的。

接着我们继续看代码:

执行完这个代码。会在堆里会新增2个2m对象,1个300K对象,最后array3置为null。

如果要继续执行byte[] array4 = new byte[2*_1MB];

那么eden区就不够空间了。这个时候会触发第二次young gc了。

我们继续看一下第二次young gc的日志:

ParNew: 7223K->0K(9216K)

说明了啥?

GC前,一共使用了7223K,包括eden区的3个2m对象+300K对象和from区的300K对象+未知对象,GC后,整个新生代都有空了。

理论上,GC后,array2还引用着300K对象的。所以,可以肯定的是,这300K对象,肯定不会被回收。

但现在GC日志明显告诉我们,新生代在GC后的空间使用率为0。

这是为什么呢?

不着急,我们继续看一下老年代的空间日志:

concurrent mark-sweep generation total 10240K, used 601K

你看,老年代竟然被使用了601K。其实这601K,就是300K对象和未知对象的空间。

为什么它们会在老年代?

就是因为触发了动态年龄判断呀。

你想想,首先,300K不是大对象吧。(-XX:PretenureSizeThreshold=10m)

也没到达15岁,因为才young gc两次。(-XX:MaxTenuringThreshold=15)

而且现在survivor是1024K空间,是足以容纳601K的存活对象的。所以,这些都不是导致对象进入老年代的原因。

现在因为2次young gc,那600K对象都存活着,并且占用的空间是超过survivor区的空间大小一半。从而触发动态空间判断,进入老年代。

说到这里,相信大家应该都能明白了。如果还不明白,欢迎加大头菜微信一起探讨。

纸上得来终觉浅,剩下的2种情况:

  • minor gc后,survivor区空间不能容纳全部存活对象
  • 存活对象达到年龄阈值。比如15

这两种情况的代码,你们可以自己试试实战一下。

留一个问题给大家思考一下,欢迎大家留言交流:

survivor空间不足以容纳存活对象时,是不是所有对象都会进入老年代?还是会有部分进入老年代,剩下部分留在survivor区?

我呢,代码已经写好了。大家自行在后台获取即可。

在公众号回复:jvm代码。可以查看这四种情况的实战代码。

絮叨

本来这种涉及到代码实战的技术分享,应该录视频讲比较好的。

但是实在太难了。大头菜自认为是一个脸皮厚的人,但是昨晚录了3个小时,都没录完。

要么就是中途结巴,要么就是中途紧张,总之,害,写文章和录视频,真的两回事。写文章上,我思路算是比较清晰的。但录视频真的紧脏呜呜呜。。。。。。

我忍不住想吐槽一下自己的声音,录完后,我听了一下自己的声音。

咦惹。。。妈呀。。。。。什么鬼。。辣耳朵

果然,听自己的声音是需要勇气的。。。。。

但,我还是会继续尝试录视频的,剪完后,给大家分享。

JVM-对象什么时候进入老年代(实战篇)相关推荐

  1. jvm对象从新生代到老年代_深入理解jvm内存模型以及gc原理

    整体架构 Jvm = 类加载器 + 执行引擎 + 运行时数据区域 类加载器 ● 作用 类加载器是将编译好的class文件加载到内存中,并进行验证.初始化等步骤,形成能被jvm直接使用的类型. ● 加载 ...

  2. jvm对象从新生代到老年代_JVM内存管理、JVM垃圾回收机制、新生代、老年代以及永久代...

    内存模型 JVM运行时数据区由程序计数器.堆.虚拟机栈.本地方法栈.方法区部分组成,结构图如下所示. JVM内存结构由程序计数器.堆.栈.本地方法栈.方法区等部分组成,结构图如下所示: 1)程序计数器 ...

  3. jvm对象从新生代到老年代_一文搞懂JVM新生代、老年代和永久代

    Java堆是垃圾收集器管理的主要区域,因此很多时候也被称为"GC堆". 从内存回收角度看,由于现在收集器基本都采用分代收集算法,所以Java堆中还可以细分为:新生代和老年代.再细致 ...

  4. JVM的内存结构,Eden和Survivor比例;JVM中一次完整的GC流程,对象如何晋升到老年代,说说你知道的几种主要的JVM参数;CMS 常见参数解析;.你知道哪几种垃圾收集器,各自的优缺点

    47.JVM的内存结构,Eden和Survivor比例 49.JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代,说说你知道的几种主要的JVM参数 50.-XX:+CMSScavengeBefo ...

  5. 深入理解JVM—满足什么条件的对象才会进入老年代?

    我们可能知道jvm调优,但是jvm调优到底是为了调整什么呢?或者说是优化什么?可能并不是特别清楚. 其实很简单,就是为了减少STW(stop the world),什么会造成这一现象呢,学过gc的应该 ...

  6. jvm配置参数,查看大对象直接分配到老年代

    看下如下代码: 配置参数 -Xms60m -Xms60m -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+PrintGCDetails 运行后看下如下,可以看到ParO ...

  7. jvm中的年轻代 老年代 持久代 gc ----------转载

    jvm中的年轻代 老年代 持久代 gc 虚拟机中的共划分为三个代:年轻代(Young Generation).老年代(Old Generation)和持久代(Permanent Generation) ...

  8. jvm中年轻代,老年代

    jvm中新生代,老年代 一.年轻代 二.老年代 三.永久代 Java中的Heap(堆)是JVM所管理的最大的一块内存空间,主要用于存放各种类的实例对象. 由于Java堆是垃圾收集器管理的主要区域,因此 ...

  9. JVM 中一次完整的 GC 流程是什么样子的,对象如何晋升到老年代,

    一次完整的gc过程 gc是通过垃圾收集器来实现的,现代垃圾收集器大部分都是基于分代收集理论设计的,也就是将对象划分为新生代,老年代.其中新生代分为Eden区和两块Survivor区,比例为8:1:1. ...

  10. jvm详解 - 新生代与老年代

    *************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 J ...

最新文章

  1. linux c配置文件书写格式,读取配置文件源代码[linux c]
  2. Angular单元测试里pipe的mock设计
  3. 消除左递归c++代码_「leetcode」129. 求根到叶子节点数字之和【递归中隐藏着回溯】详解...
  4. mysql5.6修改默认目录_MySQL修改默认存储路径
  5. ios 代码设置控件宽高比_ios-自动布局以保持视图大小比例
  6. suse系统_据传 SUSE 将进行 IPO,高达 60 亿美元 | 新闻拍一拍
  7. python 高性能http服务器_Python高性能HTTP客户端
  8. 编译通用无界面版Emacs27.1
  9. iOS ViewController利用代理页面传值
  10. 数据库连接池的深入理解
  11. MATLAB快速搭建一个神经网络以及神经网络工具箱的使用
  12. [二十一]深度学习Pytorch-正则化Regularization之weight decay
  13. 120. Triangle(三角矩阵)
  14. 六张思维导图,读懂项目管理
  15. Netbackup 8.1: bparchive 成功备份文件,但无法删除
  16. ISO、快门、光圈、曝光
  17. GOOGLE卫星地图 计算方式
  18. P1460 健康的荷斯坦奶牛
  19. Pyecharts坑之一---“画图不显示“
  20. 为什么delphi编译生成的exe文件这么大?

热门文章

  1. modelsim仿真系列之基于ISE的IP核的独立仿真(二)
  2. 【Yolact训练自己的数据集(踩坑总结)】
  3. linker command failed with exit code 1 (use -v to see invocat)错误的处理方法
  4. python字典操作题_python字典练习题
  5. 10kw全固态中波dam广播发射机的计算机监控系统分析与设计[,【中波发射机】关于DAM10kW中波广播发射机欠激励故障维修总结...
  6. VS语音信号处理(6) C语言调用SoundTouch进行变速不变调工程实例
  7. 剑灵电信6区服务器位置,剑灵电信6区哪个服务器人多
  8. ThinkPhp6框架基本使用实践教程
  9. 2023年入门网络安全(黑客)收藏这个就够了
  10. 存储备份从入门到精通