堆空间
概述
什么是堆空间?
定义:虚拟机中 [最大的存储空间]{.red}
作用:[存放所有被创建的对象和数组的空间]{.red}
+++danger 对象一定被分配在堆空间中吗?
+++
特点:
[堆空间可以处于物理上并不连续的内存空间中]{.red}
物理上不连续:操作系统采用了虚拟内存技术,所以分配给虚拟机的内存显然是不连续的
逻辑上连续:虚拟机的使用者并不关心实际内存是怎么存储的,从表面上看来就是连续的
[如果创建的是大对象通常会要求使用连续的堆空间]{.red}
:::info
大对象:需要占用的堆内存非常大的对象,诸如数组对象 new byte[1024 * 1024 * 1024]
:::
[堆空间中的对象的销毁不取决于方法是否结束,而是取决于垃圾回收机制]{.red}
[堆空间既存在垃圾回收机制,也存在 OutOfMemoryError 异常]{.red}
[堆空间是所有线程共享的区域:线程可以互相访问彼此创建的对象和数组]{.red}
[堆空间主要管理数据的存储,所以存储的是对象和数组的实际数据]{.red}
空间划分
核心:[分代思想]{.red}
虚拟机规范划分:新生代 + 老年代
新生代:
- 伊甸园区:TLAB(Thread Local Allocation Buffer) + 共享区域
- 幸存者区:幸存者0区 + 幸存者1区
老年代
方法区:[逻辑上归属堆区,物理上并不属于堆区,甚至可以称为非堆区]{.red}
:::info
① 类似于中国大陆和中国台湾的关系,逻辑上台湾归属于中国,但是目前中国大陆的政策无法管理台湾
② 方法区和堆区实际存储的内容完全不同,所以进行区分也是正常的
:::
JDK 7:[方法区采用永久代实现:使用内存是虚拟机提供的内存]{.red}
JDK 8:[方法区采用元空间实现:使用的内存是操作系统提供的物理内存]{.red}
+++danger 为什么堆空间要采用分代思想进行管理?
① 显然堆空间即使不分代也是可以进行垃圾回收的
② 但是如果所有的对象都被杂乱无章地放在一起,每次执行垃圾回收的时候就需要挨个查找
③ 哪些是短生命周期的,可以回收,哪些是长生命周期的,不能回收,就相当于每次都要问一问老年代需不需要回收
④ 这样垃圾回收的性能显然就会下降,执行了太多没有意义的操作
⑤ 核心:优化垃圾回收机制的性能
+++
空间大小
堆空间大小设置
特点:[堆空间大小可以是固定容量也可以是动态扩展的]{.red}
固定容量:虚拟机默认采用的是动态扩展,固定容量需要自行设置
+++info 如何将堆空间设置为固定容量?
① 只需要将堆空间的的初始容量和最大容量设置为相同的即可
② 再继续想想,为什么要将两者设置为相同?或者说为什么要使用固定容量?
+++
动态扩展
[默认初始化容量:物理内存 / 64]{.blue}
[默认最大容量:物理内存 / 4]{.blue}
现状:大多数虚拟机都支持堆空间的动态扩展,[但是实际开发中都是采用固定容量]{.blue}
// Runtime: 运行时数据区
public static void main(String[] args)
{
// 初始内存:245MB
long initialMemory = Runtime.getRuntime().totalMemory();
// 最大内存:3625MB
long maxMemory = Runtime.getRuntime().maxMemory();
// 输出内存提示信息
System.out.println("堆空间初始化内存: " + initialMemory / 1024 /1024 + "MB");
System.out.println("堆空间最大内存: " + maxMemory / 1024 / 1024 + "MB");
// 计算物理机的实际内存:15680MB 14500MB
System.out.println("物理机实际内存: " + initialMemory / 1024 /1024 * 64 + "MB");
System.out.println("物理机实际最大内存: " + maxMemory / 1024 /1024 * 4 + "MB");
}问题:
+++danger 为什么要将堆空间的初始化大小和最大容量设置为相同的呢?
① 核心:避免频繁扩展堆空间的大小,同时也避免在垃圾回收之后重新计算堆空间大小,从而提高虚拟机的执行性能
② 此前提到大多数的虚拟机都支持堆的动态扩展,并且需要使用垃圾回收机制
③ 如果允许堆采用动态扩展,那么每次垃圾回收之后都会重新计算堆空间大小
④ 因为对象被回收了,堆需要的空间就没那么大了,所以需要重新计算,这显然是浪费性能的
+++
+++ 为什么计算得到的物理机内存和实际内存不一样呢?
① Runtime.getRuntime().totalMemory(); 获取的堆内存是只有正在使用的内存
② 幸存者区只会将其中一个区用作存放对象,另外一个区空着不放对象,所以获得的内存每次就少了一部分幸存者区的内存
+++
设置堆空间大小命令
[-Xms Size 设置堆空间的初始化大小]{.blue}
# 设置堆空间初始化大小
# 等价于 -XX:InitialHeapSize
#(实际上这个参数我并没有使用过,网上也没有提到如何使用,很奇怪,就连官方文档都没有提到)
-Xms1m[-Xmx Size 设置堆空间的最大容量]{.blue}:
# 设置堆空间最大大小
# 等价于 -XX:MaxHeapSize
-Xmx1m[-Xmn Size 设置堆空间中年轻代的大小]{.blue}:堆剩下的空间就是老年代占有的
# 设置堆空间中年轻代的大小
-Xmn1m
新生代和老年代大小设置
默认大小比例
[新生代 : 老年代 = 1 : 2]{.blue}
[伊甸园区 : 幸存者0区 : 幸存者1区 = 8 : 1 : 1]{.blue}
细节:
实际开发中通常使用默认的比例设置
实际上虚拟机会采用 [自适应策略]{.red},新生代和老年代的比例是 1:2,但是新生代内部的比例却不是 8:1:1
设置大小比例命令
[-XX:+UseAdaptiveSizePolicy:关闭虚拟机自适应策略]{.blue}
:::info
实际上这个命令并不会生效,自适应策略是无法关闭的,想要让比例为默认的8:1:1,直接设置就好
:::
[-XX:NewRatio=ration:设置新生代和老年代的比例大小]{.blue}
[-XX:SurvivorRation=ratio:设置伊甸园区和幸存者区的比例大小]{.blue}
+++ 为什么两个幸存者区的比例始终为 1:1 呢?
涉及到垃圾回收算法:复制标记算法
+++
堆空间溢出
:::primary
警告:jvm 内存泄露与内存溢出、jvm内存泄漏这些博客的例子全部都是错的,但凡写的人自己试一试他写的代码就会发现错了
:::
异常:OutOfMemoryError
原因:[内存泄露或者内存溢出]{.red}
内存泄露(Memory Leak)
定义:[应该被垃圾回收机制清除的对象无法被顺利清除,造成空间浪费,最终可能造成 OOM 异常]{.red}
情况:(这部分笔记暂时存在问题,先了解什么是内存泄露就行)
+++danger ① 静态集合类变量长期引用短生命周期的对象
测试代码
public static void main(String[] args) throws InterruptedException
{
// 线程休眠: 便于观察上升曲线
Thread.sleep(10000);
int count = 0;
for (int i = 0; i < 1000000; i++)
{
Object object = new Object();
// 链表不被销毁就会长期持有这个对象的引用
list.add(object);
// 短生命周期引用不再指向对象, 链表依然持有该对象的引用
// 注意: 这里将引用置为空的目的是代表这个对象我们不想再使用了
object = null;
System.out.println("创建对象数量: " + ++count);
}
// 线程休眠: 便于观察上升曲线
Thread.sleep(10000000);
}测试图示
测试堆内存结果
测试结论:
- 每个添加进入链表的对象我们都不想继续使用,但是由于链表是静态变量,生命周期和类加载器一致,所以垃圾回收无法回收
- 堆内存结果会再创建对象完成之后长时间保持水平线,意味着垃圾回收一直没有生效
:::info
疑惑:为什么将 static 关键字去掉之后依然没有触发垃圾回收机制?
:::
+++
② [未关闭不再使用的连接资源]{.red}:创建的数据库连接、流对象等连接资源再使用之后没有合理关闭,就会造成内存泄露
③ [未重写相应的 equals 和 hashCode 方法]{.red}
④ 外部类引用内部类对象:
⑤ 重写 finalize 方法不得当:
内存溢出(Memory OverFlow)
定义:[堆空间需要使用的内存超过虚拟机分配的内存,造成 OOM 异常]{.red}
测试:
测试代码
public static void main(String[] args)
{
// 虚拟机参数设置: -Xms1m -Xmx1m
ArrayList<Object> list = new ArrayList<>();
// 注意:尽可能将对象数量设置多点,否则不会溢出
for (int i = 0; i < 10000000; i++)
{
// 循环创建对象
list.add(new Object());
}
}测试结果