JVM 运行时内存空间详解——堆
JVM 运行时内存空间详解——堆
通过上一篇文章,我们大体了解了JVM的整体架构,其分为:元数据(JDK7是方法区)、堆、虚拟机栈、本地方法栈、程序计数器几个部分。

本篇文章,咱们对堆进行剖析,一探究竟。
1. 什么是Java 堆
对于Java应用程序来说, Java堆(Java Heap) 是虚拟机所管理的内存中最大的一块。 Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。
此内存区域的唯一目的就是存放对象实例java堆内存设置,Java世界里“几乎”所有的对象实例都在这里分配内存。
“几乎”是指从实现角度来看JVM 运行时内存空间详解——堆, 随着Java语言的发展,现在已经能看到些许迹象表明日后可能出现值类型的支持, 即使只考虑现在, 由于即时编译技术的进步, 尤其是逃逸分析技术的日渐强大, 栈上分配、标量替换优化手段已经导致一些微妙的变化悄然发生, 所以说Java对象实例都分配在堆上也渐渐变得不是那么绝对了。

2. 堆的特点 是Java虚拟机所管理的内存中最大的一块。堆是jvm所有线程共享的。
堆中也包含私有的线程缓冲区 (TLAB)。在虚拟机启动的时候创建。唯一目的就是存放对象实例,几乎所有的对象实例以及数组都要在这里分配内存。Java堆是垃圾收集器管理的主要区域。因此很多时候java堆也被称为“GC堆”( Heap)。从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆还可以细分为:新生代和老年代;新生代又可以分为:Eden 空间、From 空间、To 空间。Java堆是计算机物理存储上不连续的、逻辑上是连续的,也是大小可调节的(通过-Xms和-Xmx控制)。方法结束后,堆中对象不会马上移出仅仅在垃圾回收的时候时候才移除。如果在堆中没有内存完成实例的分配,并且堆也无法再扩展时,将会抛出异常 3. 如何设置堆空间的大小
内存大小 -Xmx(最大)/-Xms(最小)
使用示例: – -Xms5m
说明: 当下Java应用最大可用内存为20M, 最小内存为5M
测试代码:
public class TestVm {
public static void main(String[] args) {
// 堆最大空间(如有设置,则和设置的最大值相同)
System.out.print("Xmx=");
System.out.println(Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");
// 空闲空间
System.out.print("free mem=");
System.out.println(Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");
// 总共使用空间
System.out.print("total mem=");
System.out.println(Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");
}
}
在IDEA中设置堆空间:


执行结果:

可以看到,最大20M, 空闲4M多,当前使用了 6M
现在再补充两行代码,给数组分配5M,再看一下结果如何:
public class TestVm {
public static void main(String[] args) {
// 补充, 分配5M给数组
byte[] b=new byte[5*1024*1024];
System.out.println("分配了5M空间给数组");
// 堆最大空间(如有设置,则和设置的最大值相同)
System.out.print("Xmx=");
System.out.println(Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");
// 空闲空间
System.out.print("free mem=");
System.out.println(Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");
// 总共申请空间
System.out.print("total mem=");
System.out.println(Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");
}
}
执行结果:

可以看到,最大还是20M, 空闲4M多,当前使用变成了12M。
那么再将分配数组的5M改成10M呢?
// 补充, 给数组分配10M
byte[] b=new byte[10*1024*1024];
System.out.println("分配了10M空间给数组");

可以看到,最大还是20M, 空闲4M多,当前使用变成了17M。
那么,如果再给数组分配加大到20M呢?
// 补充, 给数组分配20M
byte[] b=new byte[20*1024*1024];
System.out.println("分配了20M空间给数组");

可以看到,因为堆的总大小设置的是20M, 如果再给数组分配20M, 此处自然就会报内存溢出异常。
讲了那么多,有必要解释下代码中的三个核心方法,
1.()
2.()
默认值:

含义:相当于未手动设置-Xmx和-Xms的情况下,操作系统共有4050M可以给java虚拟机申请。虚拟机挖了254M内存出来,挖出来的内存中,真正使用了4M左右,还剩余250M未使用。
以上,可以发现,其实JVM在分配内存过程中是动态的, 按需来分配的。
4. 堆的分类
现在垃圾回收器都使用分代理论,堆空间也分类如下:
在Java7 虚拟机中将Java堆内存分为3个部分:
青年代Young :Eden(伊甸元区)、S0(幸存者0区,存放伊甸元区无法销毁的实例)、S1(幸存者1区,存放伊甸元区无法销毁的实例)
老年代Old :S1区经过很多轮GC后,超过一定阈值,实例就会进入老年代,说明这个对象的生命周期比较长。
永久代 : JDK7才有,JDK8换成了元空间。

在Java8以后,由于方法区的内存不再分配在Java堆上,而是存储于本地内存元空间中,所以永久代就不存在了JVM 运行时内存空间详解——堆,在2018年9月25日正式发布以后,在关于中垃圾收集器的官方文档中没有提到“永久代”,而只有青年代和老年代。

通过日志查看堆空间:
public class TestVm {
public static void main(String[] args) {
// 堆最大空间(如有设置,则和设置的最大值相同)
System.out.print("Xmx=");
System.out.println(Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");
// 空闲空间
System.out.print("free mem=");
System.out.println(Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");
// 总共使用空间
System.out.print("total mem=");
System.out.println(Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");
}
}
在如上代码中,设置GC日志打印:-XX:+

选择JRE11运行

可以看到,有元空间大小的显示,每个JDK版本打印可能不相同,下面是JDK8的控制台显示

同理java堆内存设置,将JRE换成1.7,就没有元空间了

5. 年轻代和老年代
1.JVM中存储java对象可以被分为两类
1)年轻代(Young Gen):年轻代主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。年轻代分成1个Eden Space和2个 Space(from 和to)。
2)年老代( Gen):年老代主要存放JVM认为生命周期比较长的对象(经过几次的Young Gen的垃圾回收后仍然存在),内存大小相对会比较大,垃圾回收也相对没有那么频繁。

2.配置新生代和老年代堆结构占比
默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX: 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。
其中,新生代 ( Young ) 被细分为 Eden 和 两个 区域,这两个 区域分别被命名为 from 和 to,以示区分。
默认的,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX: 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to= 1/10 的新生代空间大小。
JVM 每次只会使用 Eden 和其中的一块 区域来为对象服务,所以无论什么时候,总是有一块 区域 是空闲着的。因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。
案例:
/**
* 1.设置年轻代和老年代的比例 1 : 4
* 2.设置 伊甸园 from to 8: 1: 1
*/
public class YoungOldTest {
public static void main(String[] args) throws InterruptedException {
System.out.println("hello");
Thread.sleep(3000000);
}
}
设置以上程序的堆空间比例分配:年轻代和老年代分配比例为1:4,年轻代Eden区和区占比8:1:1
采用JDK1.8的工具 .exe查看JVM堆的大小(直接点开即可)

安装插件 GC


运行程序后,点开这里的对应程序,查看 GC窗口中堆的占用情况:

6. 对象分配过程
分配过程

1.new的对象先放在伊甸园区,该区域有大小限制 ;
2.当伊甸园区域填满时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园预期进行垃圾回收(Minor GC),将伊甸园区域中不再被其他对象引用的额对象进行销毁,再加载新的对象放到伊甸园区;
3.然后将伊甸园区中的剩余对象移动到幸存者0区;
tips:
Minor GC:YGC(Young GC),即年轻代的垃圾回收。

4.如果再次触发垃圾回收(Minor GC),Eden区的对象直接放到空的幸存者1区。然后上次幸存下来放在幸存者0区的对象,如果还没有回收,也会放到幸存者1区,并且计数加1 。此时幸存者0区就空着了。
tips:

5.如果再次触发垃圾回收(Minor GC),Eden区的对象直接放到空的幸存者0区。然后上次幸存下来放在幸存者1区的对象,如果还没有回收,也会放到幸存者0区,并且计数加1 。此时幸存者1区就空着了。
6.如果累计次数到达默认的15次,这会进入养老区。
可以通过设置参数,调整阈值 -XX:=N
7.养老区内存不足时,会再次出发GC:Major GC 进行养老区的内存清理
8.如果养老区执行了Major GC后仍然没有办法进行对象的保存,就会报OOM异常。
tips:
Major GC:即老年代的垃圾回收。
图解分配对象的流程:

描述:
申请对象时,先判断Eden区域是否装得下,装不下则进行YGC(YGC后保留的对象会在S0和S1区切换存放,直至达到阈值放入老年代),进行YGC后,还装不下,则判断老年代是否装的下java堆内存设置,老年代装不下,再进行FGC(FUll GC),进行FGC后,还装不下,则报OOM异常。
7.堆GC
Java 中的堆也是 GC 收集垃圾的主要区域。GC 分为两种:一种是部分收集器( GC)另一类是整堆收集器(Full GC)
部分收集器: 不是完整收集java堆的的收集器,它又分为:
年轻代GC触发条件:
老年代GC (Major GC)触发机制
触发机制:
注意:Full GC也会引发stop the world,时间非常长。调优过程中,尽量避免的执行
1. 本站所有资源来源于用户上传和网络,如有侵权请联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系站长处理!
6. 本站不售卖代码,资源标价只是站长收集整理的辛苦费!如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。
7. 站长QQ号码 2205675299
资源库 - 资源分享下载网 » JVM 运行时内存空间详解——堆
常见问题FAQ
- 关于资源售价和售后服务的说明?
- 代码有没有售后服务和技术支持?
- 有没有搭建服务?
- 链接地址失效了怎么办?
- 关于解压密码

