JVM-(三)运行时数据区-堆中相关参数
本文最后更新于:May 13, 2023 pm
积土成山,风雨兴焉;积水成渊,蛟龙生焉;积善成德,而神明自得,圣心备焉。故不积跬步,无以至千里,不积小流无以成江海。齐骥一跃,不能十步,驽马十驾,功不在舍。面对悬崖峭壁,一百年也看不出一条裂缝来,但用斧凿,能进一寸进一寸,能进一尺进一尺,不断积累,飞跃必来,突破随之。
目录
一个Java程序对应一个进程,一个进程对应一个JVM实例,一个JVM实例中只有一个运行时数据区,一个运行时数据区只有一个方法区和堆,一个进程中的多个线程共享同一个方法区和堆,每一个线程拥有独立的程序计数器、本地方法栈、虚拟机栈。
概述
一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。Java堆区在JVM启动的时候即被创建,其空间大小也就确定了(但堆内存的大小是可以调节的)。堆是JVM管理的最大的一块内存空间。
在《Java虚拟机规范》中规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。
所有的线程共享Java堆,其中堆还可以划分线程私有的缓冲区(TLAB,Thread Local Allocation Buffer)。具体可见《JAVA知识点-Java对象的分配详解 》
实时可视化 Hotspot JVM 垃圾回收监控工具:安装插件 visual GC
堆内存结构
堆空间大小设置和查看
堆空间大小设置方式
Java堆的大小在JVM启动时就已经设定好了,但可以通过选项 “-Xmx
“和”-Xms
“来进行设置。
-Xms
:表示堆的起始内存大小
,等价于-XX:InitialHeapSize
。-Xmx
:表示堆的最大内存
,等价于-XX:MaxHeapSize
。
-X是JVM的运行参数,ms表示memory start。
一旦堆区中的内存大小超过了”-Xmx”所指定的最大内存时,将会抛出OutOfMemoryError异常。
- 通常会将”-Xms”和”-Xmx”两个参数配置相同的值,目的是:为了能够在Java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小,从而提高性能。
默认大小
- 初始内存大小:物理电脑内存大小 / 64。(64分之一)
- 最大内存大小:物理电脑内存大小 / 4。(4分之一)
查看本机内存情况:
1 |
|
手动设置
设置方式可以见《JVM-(二)运行时数据区-线程共享 》
1 |
|
然后再次跑上面的代码查看内存情况,可以发现前两个已经发生了改变。后两个是电脑的内存,所以不变。但是输出的数字并不是设置的数字,为什么呢?往后看。(我显示的是575M)
开发中建议将初始堆内存和最大的堆内存设置成相同的值
- 原因:在初始设置后,如果空间不够时将会进行扩容(没有超过最大内存),当空间有空闲了则又会进行空间的释放。当频繁的进行扩容和释放时,就会造成系统过大的压力。而设置成相同的,就避免了进行空间的大小调整。
数据显示和设置不同
主要是计算的问题。因为幸存区有两个区,但是计算的时候只算了一个区。下面就这两种情况的计算过程进行演示说明:
空间大小设置说明:-Xms600m -Xmx600m,最后输出575M
然后跑以下代码:
1 |
|
然后再打开cmd窗口(我是Mac系统但命令都一样),输入:(前提是配置好了Java环境)
1 |
|
再次输入:jstat -gc xxx(上面的进程id)
1 |
|
因为输出的结果很多,所以比较长。这里我自己画了一个图:
我们只看左边(新生代),不看右边。其中以C结尾的表示总大小,以U结尾的表示已使用的大小。
S0C 、 S1C 表示幸存区S0和S1两个区域的大小,EC表示伊甸区总大小,OC表示老年代的总大小。
三者相加为:(25600+25600+153600+409600)/ 1024 = 600
但是真正计算的时候幸存区的两个区域只需要计算一个:
(25600+153600+409600)/ 1024 = 575
所以,这就是计算的过程。
查看方式二
通过设置参数:-XX:+PrintGCDetails
。打印GC过程的细节。
1 |
|
1 |
|
其中的from和to就是幸存区的S0和S1。这里的数据和上面通过命令的方式获取到的数据是一样的。
注意:PSYoungGen的值是eden +(from或者to)。
新生代和老年代相关参数设置
新生代又分为Eden区,Survivor0区、Survivor1区(又称为from区和to区)。新生代和老年代的比例为1:2;新生代中,Eden:Survivor0:Survivor1为 8:1:1。
默认新生代和老年代在堆结构的占比
- 默认
-XX:NewRatio
=2,表示新生代占1,老年代占2。新生代占堆的1/3。 - 修改为
-XX:NewRatio
=4,表示新生代占1,老年代占4。新生代占堆的1/5。
查看新生代和老年代默认占比
配置参数:
1 |
|
运行代码:
1 |
|
然后看插件监控情况:
可以看见150+25+25 :400 就是1:2。
默认情况下不会修改这个比例,但是,如果已经知道了存活期长的比较多,那么就可以将老年代比例设大。
新生代分布占比
Eden区和幸存区的两个区域所占比例为8:1:1。可以通过 -XX:SurvivorRatio
调整比例。默认值为8。可以通过命令:
1 |
|
但是根据上面插件的监测情况看,实际比例为125:25:25。这是为什么呢?显示为8,但实际不一样?
这主要是和自适用机制有关。默认情况下有一个自适应比例。可以通过设置参数来关闭自适应比例。
1 |
|
📢这样配置后,是没有效果的。
要想真正的实现效果,必须是显示指定 -XX:SurvivorRatio=8
:
1 |
|
这样配置后再次查看,就会发现Eden变成了160M,而幸存区中都是20M了,这样就符合8:1:1的比例了。(因为参数设置的总大小600,新生代:老年代为1:2;所以新生代中总大小为200M)如图:
参数总结
堆空间大小参数
-Xms
:表示堆的起始内存大小
,等价于-XX:InitialHeapSize
。-Xmx
:表示堆的最大内存
,等价于-XX:MaxHeapSize
。
通过设置参数:-XX:+PrintGCDetails
。打印GC过程的细节。
新生代和老年代在堆结构的占比
- 默认
-XX:NewRatio
=2,表示新生代占1,老年代占2。新生代占堆的1/3。
新生代分布占比
- 通过
-XX:SurvivorRatio
调整比例,默认为8。但需要注意自适应机制带来的变化。
本文作者: 墨水记忆
本文链接: https://tothefor.com/DragonOne/4a58ac71.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!