Skip to content

一、Heap 堆

  • 含义:主要将存储对象实例内存 中, 为 对象实例开辟内存空间。
  • 作用:解决的是数据存储问题。(即数据怎么放、放哪儿)
  • 是否共享:线程(数据)共享存在线程安全问题

二、Heap 堆内存详细结构

堆内存组成部分主要由:

Young Gen 新生代

  • 主要存储新的对象,到该区域中,为该对象开辟空间。

eden 区

  • 是新生代内存中的最大的一块空间,主要用于存储刚刚创建的对象
  • 由于大部分对象在很短的时间内就会变成垃圾,Eden 区采用了特定的垃圾回收策略,以便更快地清理无用对象,以节省内存空间

Survivor区

分为2大区域:

  • Survivor0区(from区)
  • Survivor1区(to区)
  • 作用: 用于存放在Eden区另一个Survivor区之间进行对象复制的存活对象

例如:

第一次 Minor GC(新生代垃圾回收) -> A: Eden区中存活的对象 -> 会被移动 S0(from)区

第二次 Minor GC(新生代垃圾回收) -> B: 将前一次GC时存放存活对象 S0(from)区 -> 复制到 S1(to)区 和 另一个Survivor区

这样来回复制的过程在不断重复,直到达到一定的阈值或者年龄后 -> 对象会被晋升到老年代。

Old Gen 老年代

  • 主要存储老的对象(达到垃圾回收阈值,最后存活下来对象)到该区域中,为该对象开辟空间。

Metaspace 元空间

含义:主要存放类的信息

  • 类的信息主要包含:类的信息局部变量表方法信息常量池
  • 线程安全问题:线程(数据)共享,存在线程安全问题。

三、Heap 堆内存-产生问题

内存泄漏(Memory Leak)

是指程序中 已动态分配的堆内存,由于某种原因程序 未释放无法释放,造成系统 内存的浪费,导致 程序运行速度减慢 甚至 系统崩溃等严重后果。

例如: 在 JAVA 中创建过多的对象, 由于 GC 无法回收这些垃圾对象,导致内存浪费,导致程序运行速度的减慢或最终导致系统崩溃

java
List list = new ArrayList<>(10);
for (int i = 0; i < 100 ; i++) {
    HeapMemoryOutOfExample.User user = new HeapMemoryOutOfExample.User();
    list.add(user);
    user = null;
}

try {
    System.gc();
    Thread.sleep(100000);
} catch (Exception e) {
    e.printStackTrace();
}

内存溢出(Memory Overflow)

  1. 无法回收的内存使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存
  2. 此时程序就运行不了,系统会提示内存溢出,有时候会自动关闭软件,重启电脑或者软件后释放掉一部分内存又可以正常运行该软件。
  3. 而由系统配置、数据流、用户代码等原因而导致的内存溢出错误,即使用户重新执行任务依然无法避免。

例如:在 JAVA 中程序设置最大内存为 8MB, 但由于创建对象在堆内存中超过了程序最大内存 8MB, 就会引发内存溢出。

  • 如下代码:
java
   /**
    * 案例一: 递归深度长 -> 导致内存溢出
    */
   public static void recursionDeepLong() {
       List list = new ArrayList<>(0);
       while (true) {
           list.add(new User());
       }
   }
  • 运行结果:java.lang.OutOfMemoryError: Java heap space (堆内存溢出)
log
 # 运行结果: java.lang.OutOfMemoryError: Java heap space (堆内存溢出)
  Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  at java.util.Arrays.copyOf(Arrays.java:3210)
  at java.util.Arrays.copyOf(Arrays.java:3181)
  at java.util.ArrayList.grow(ArrayList.java:267)
  at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:241)
  at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:233)
  at java.util.ArrayList.add(ArrayList.java:464)
  at com.calvin.jvm.structure.example.HeapMemoryOutOfExample.main(HeapMemoryOutOfExample.java:35)

java
    /**
     * 用户
     *
     * @author calvin
     * @date 2023/06/28
     */
    public static class User {
        /**
         * 指定对象创建为1MB
         */
        private byte[] bytes = new byte[1024 * 1024];
    }

    /**
     * 案例二: 对象设置指定内存,超过设置内存 -> 导致内存溢出
     */
    public static void moreThanHeapSize() {
        List list = new ArrayList<>(0);
        int count = 0;
        try {
            while (true) {
                list.add(new User());
                count++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("HeapMemoryOutOfExample.moreThanHeapSize: 执行次数" + count);
        }

    }
  • 启动程序,设置程序最大内存为2MB
shell
$ java -Xmx2m 程序名称
  • 输出结果
log
# 运行结果: java.lang.OutOfMemoryError: Java heap space (堆内存溢出)
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.calvin.jvm.structure.example.HeapMemoryOutOfExample$User.<init>(HeapMemoryOutOfExample.java:25)
	at com.calvin.jvm.structure.example.HeapMemoryOutOfExample.moreThanHeapSize(HeapMemoryOutOfExample.java:63)
	at com.calvin.jvm.structure.example.HeapMemoryOutOfExample.main(HeapMemoryOutOfExample.java:82)
HeapMemoryOutOfExample.moreThanHeapSize: 执行次数6

java
   /**
    * 案例三: 一次性查询MySQL大数据表,超过设置内存, 导致内存溢出
    */
   private static void findBigDataByMySQL() {
       // 伪代码,大数据
       // List<User> bigDataUsers = listByUser();
       // 使用该对象进行业务逻辑处理
   }