【jvm 六】堆以及堆分代的详细介绍

前言:

上篇文章详细介绍了java虚拟机栈,本篇博客简单的对堆做一下概述

1、堆的核心概述

  • 一个程序(也就是一个main方法)对应着一个JVM实例,一个JVM实例只存在一个堆内存;
  • java堆区在JVM启动的时候被创建,其空间大小也就确定了,是JVM管理的最大一块内存空间,但是堆内存是可以调节的
  • 堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的
  • 所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)之后会有篇幅专门讲清楚TLAB是什么的。
  • 《java虚拟机规范》中对Java堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上。但是从实际角度上看,应该是几乎所有对象的实例都在这里分配内存。
  • 垃圾回收是在堆中进行的,栈是不涉及GC的

2、堆、栈和方法区之间的关联

在这里插入图片描述
如上图:java栈中的局部变量表的局部变量对应的是java堆中对象的具体地址,而堆中对象的实例又关联了方法区(类的结构和常量池),比如此时我们想知道对象所在的类(getClass)就要到方法区中找了。

3、堆的内存细分

  • 存储在JVM中的java对象可以被划分为两类:一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速,另外一类对象的生命周期却非常长
  • java堆区分为年轻代(YoungGen)和老年代(OldGen),其中年轻代又可以划分为Eden(伊甸园)、Survivor0(幸存者0区)、Survivor1(幸存者1区)(有时候也叫做from区、to区),这里总结一句顺口溜:复制之后有交换,谁空谁是to

在这里插入图片描述

4、堆空间的大小设置

  • 堆的大小在JVM启动时就已经设定好了,可以通过"-Xmx"和"-Xms"来进行设置。

    "-Xms"用于表示堆区的起始内存 等价于-XX:InitialHeapSize
    “-Xmx”则用 于表示堆区的最大内存,等价于-XX:MaxHeapSize

  • 一旦堆区中的内存大小超过“-Xmx"所指定的最大内存时,将会抛出OutOfMemoryError异常。

  • 通常会将-Xms和-Xmx两个参数配置相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小,从而提高性能。
    这里解释一下为什么会提高性能?当Xmx值比Xms值大时,堆可以动态收缩和扩展,避免了扩容机制的发生

  • 默认情况下,初始内存大小:物理电脑内存大小/64 、最大内存大小:物理电脑内存大小/4

  • 老年代和新生代的空间默认比例:2:1
    在这里插入图片描述

  • 在HotSpot中,Eden 空间和另外两个Survivor空间缺省所占的比例是8:1:1
    当然开发人员可以通过选项“-XX:SurvivorRatio”调整这个空间比例。比如-XX: SurvivorRatio=8
    几乎所有的Java对象都是在Eden区被new出来的。
    绝大部分的Java对象的销毁都在新生代进行了。
    ➢IBM公司的专门研究表明,新生代中80%的对象都是“朝生夕死”的。
    可以使用选项"-Xmn"设置新生代最大内存大小
    ➢这个参数一般使用默认值就可以了。

4.1验证
(1)设置 VM options参数为-Xms600m -Xmx600m

在这里插入图片描述

(2)java代码
/**
 * -Xms:用来设置堆空间(年轻代+老年代)的初始内存大小
 *    -X 是jvm的运行参数
 *    ms memory start
 * -Xmx:用来设置堆空间(年轻代+老年代)的最大内存大小
 */
public class HeapSpaceInitial {
    public static void main(String[] args) {
        // 返回Java虚拟机中的堆内存总量,之所以除以1024是因为初始化的单位是字节
        // Runtime就可以理解为运行时数据区
        long initialMemory = Runtime.getRuntime().totalMemory()/ 1024 / 1024;
        // 返回java虚拟机试图使用的最大的堆内存量
        long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;

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

        // 为了让程序不那么快的执行完
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

(3)运行结果

在这里插入图片描述
疑问为什么不是600M而是575M?
接下来给解答

(4)查看设置的参数
方式1:用命令行

a)win+r 输入cmd,进入到一个新界面,然后输入jps命令,我们看到我们刚刚跑的程序对应的端口号为16304
在这里插入图片描述

b)输入命令 jstat -gc 16304
在这里插入图片描述
其中
S0C:表示的是幸存者0区总量
S1C:表示的是幸存者1区总量
S0U:表示的是幸存者0区已经用的空间
S1U:表示的是幸存者1区已经用的空间
EC:Eden 区总量
EU:Eden 区已经用到的空间
OC:表示的是老年代区总量
OU:表示的是老年代区已经用的空间

25600+25600+153600+409600=614400
614400/1024=600

这里总量是600,但是实际上jvm是这么算的

25600+153600+409600=588800
588800/1024=575
这么算的结果是575,这是因为S0区或者S1区二者只能有一个区放对象

方式2:加上参数: -XX: +PrintGCDetails

在这里插入图片描述

public class HeapSpaceInitial {
    public static void main(String[] args) {
        // 返回Java虚拟机中的堆内存总量,之所以除以1024是因为初始化的单位是字节
        // Runtime就可以理解为运行时数据区
        long initialMemory = Runtime.getRuntime().totalMemory()/ 1024 / 1024;
        // 返回java虚拟机试图使用的最大的堆内存量
        long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;

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

在这里插入图片描述
从上我们看到了PSYoungGen(年轻代),ParOldGen(老年代),Metaspace(元空间),和方式一的结果都一样。

5、堆中各个分代中的比例设置

在这里插入图片描述

  • 配置新生代与老年代在堆结构的占比。
    ➢默认-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3
    ➢可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整个堆的1/5

  • 开发人员可以通过选项“-XX:SurvivorRatio”调整这个空间比例。比如-XX: SurvivorRatio=8
    几乎所有的Java对象都是在Eden区被new出来的。

  • 绝大部分的Java对象的销毁都在新生代进行了。新生代中80%的对象都是“朝生夕死”的。

  • 可以使用选项"-Xmn"设置新生代最大内存大小
    ➢这个参数一般使用默认值就可以了。

  • 在HotSpot中,Eden 空间和另外两个Survivor空间缺省所占的比例是8:1:1

但是实际上是6:1:1
在这里插入图片描述
此时我们需要设置-XX:SurvivorRatio=8就ok了

6、堆空间分代思想

分代的唯一理由就是优化GC性能。如果没有分代,那所有的对象都在一块,就如同把一个学校的人都关在一个教室。GC的时 候要找到哪些对象没用,这样就会对堆的所有区域进行扫描。而很多对象都是朝生夕死的,如果分代的话,把新创建的对象放到某一地方,当GC的时候先把这块存储“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。

7、内存分配策略
  • 如果对象在Eden出生并经过第一次MinorGC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象年龄设为1。对象在Survivor区中每熬过一次MinorGC,年龄就增加1
    岁,当它的年龄增加到一定程度(默认为15岁,其实每个JVM、每个GC都有所不同)时,就会被晋升到老年代中。
  • 对象晋升老年代的年龄阈值,可以通过选项-XX: MaxTenuringThreshold来设置。
  • 针对不同年龄段的对象分配原则如下所示:
    ●优先分配到Eden
    ●大对象直接分配到老年代,尽量避免程序中出现过多的大对象
    ●长期存活的对象分配到老年代
    ●动态对象年龄判断,如果Survivor 区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
    ●空间分配担保
    -XX: HandlePromotionFailure
8、堆空间的参数设置

官网链接

-XX:+PrintFlagsInitial :查看所有的参数的默认初始值

-XX: +PrintFlagsFinal : 查看所有的参数的最终值(可能会存在修改,
不再是初始值)

-Xms: 初始堆空间内存 (默认为物理内存的1/64)

-Xmx: 最大堆空间内存(默认为物理内存的1/4)

-Xmn: 设置新生代的大小。(初始值及最大值)

-XX: NewRatio:配置新生代与老年代在堆结构的占比

-XX: SurvivorRatio:设置新生代中Eden和S0/S1空间的比例

-XX: MaxTenuri ngThreshold:设置新生代垃圾的最大年龄

-XX: +PrintGCDetails:输出详细的GC处理日志
➢打印gc简要信息: ①-XX: +PrintGC. ②-verbose:gc

-XX: HandlePromotionFalilure:是否设置空间分配担保

在发生Minor GC之 前,虚拟机 会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间

  • 如果大于,则此次Minor GC是安 全的

  • 如果小于,则虚拟机会查看-XX: HandlePromotionFai lure设置值是否允许担保失败。

    ➢如果HandlePromotipnFailure=true,那么会 继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小。

     	1.如果大于,则尝试进行一次Minor GC, 但这次Minor GC依 然是有风险的;
     	2.如果小于,则改为进行- -次Full GC。
    

    ➢如果HandlePromotionFailure=false, 则改为进行一次Full GC。

只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full GC。也就是默认为true

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值