JVM中的堆

71 阅读5分钟

对于GC垃圾回收,主要在伊甸园和养老区。 假设内存满了,OOM,堆内存不够!java.lang.OutOfMemoryError: java heap space

在JDK8以后,永久存储区改了名字(元空间);

什么是堆内存?

JVM中的堆内存是一个非常关键的内存区域。用于存储对象实例和数组。堆内存是所有线程共享的,并且是垃圾回收的主要目标区域。堆内存通常可以分为多个区域,主要包括 新生代(Young Generation)、老年代(Old Generation)、和永久代(PermGen) (或元空间Metaspace,在JDK8以后,永久代被替代为元空间),这些区域的管理和垃圾回收策略各不相同。

堆内存的特征:

  • 共享内存区域:所有线程共享堆内存。它不属于任何特定的线程,因此不同线程之间可以访问堆上的对象。
  • 垃圾回收的主战场:堆内存中存储的对象在垃圾回收过程中会被检查、标记并回收。

1. 新生代(Young Generation)

新生代是堆内存的一个区域,专门用来存放 新创建的对象。大多数对象都会在这里诞生,然而大部分对象在短时间内会被回收。

新生代被进一步分为 三个区

  • Eden 区:是新生代的主区域,用于存放新创建的对象。
  • From Survivor 区:用于存放从 Eden 区和另一 Survivor 区(To Survivor)晋升过来的对象。
  • To Survivor 区:与 From Survivor 区配对,也是新生代的一个区域,存放一些暂时存活的对象。
新生代的垃圾回收
  • 新生代的垃圾回收称为 Minor GC,通常发生在 Eden 区 满时。
  • Minor GC 比较频繁,但通常是快速的,因为它只涉及新生代的回收。
  • 大多数对象会在新生代经历若干次回收后被提升到老年代。
新生代的特点:
  • 短生命周期:新生代的对象大多是短命的,许多对象创建后很快就会被垃圾回收。
  • 垃圾回收频繁:由于新生代对象的生命周期短,垃圾回收发生得频繁,回收速度也较快。

2. 老年代(Old Generation)

老年代是堆内存的另一部分,用于存放存活时间较长的对象。这些对象通常在新生代经过多次垃圾回收后,由于长时间存活而被晋升到老年代。

老年代的垃圾回收称为 Major GCFull GC。它通常比 Minor GC 更耗时,因为它涉及到整个堆的回收,包括新生代和老年代。

老年代的特点:
  • 生命周期长:老年代的对象是那些存活了较长时间的对象,通常它们较为稳定,不会频繁被销毁。
  • 垃圾回收较少:老年代的垃圾回收比较少发生,但每次发生时会比较耗时,因为老年代中的对象往往占用大量内存空间。
老年代晋升:
  • 对象在新生代经历若干次 Minor GC 后,如果仍然存活,会被晋升到老年代。
  • 对象晋升到老年代的条件和机制是基于其存活的年龄(即对象存活的 GC 次数)。

3. 永久代(PermGen)与元空间(Metaspace)

在 JDK 8 之前,永久代(PermGen) 是堆内存的一部分,用于存储 类信息方法信息常量池 等与类加载相关的数据。PermGen 是固定大小的,不受堆内存的动态调整限制,因此在运行过程中容易发生 OutOfMemoryError: PermGen space 错误。

永久代的特点:
  • 固定大小:永久代的大小是固定的,无法动态扩展。
  • 存储类信息:它主要存储类的结构信息(如字段、方法、常量池等)。
  • 常常发生内存溢出:如果加载的类太多,或者常驻内存的类没有及时被回收,永久代空间就会耗尽,导致内存溢出。
元空间(Metaspace):
  • 从 JDK 8 开始,永久代元空间(Metaspace) 替代。元空间不再是 JVM 堆内存的一部分,而是直接使用本地内存(Native Memory)。
  • 元空间的大小是动态可扩展的,不再受到固定大小的限制。
  • 元空间的容量受限于操作系统的本地内存,而不再受 JVM 堆内存的限制。
元空间的特点:
  • 动态大小:JVM 可以动态调整元空间的大小,受限于物理内存。
  • 存储类的元数据:与永久代一样,元空间存储类的元数据,包括类信息、方法信息等。
  • 可以通过参数调整:可以通过 JVM 参数 -XX:MaxMetaspaceSize 来设置元空间的最大大小。

4.堆内存管理的参数

  • -Xms:设置堆的初始大小。
  • -Xmx:设置堆的最大大小。
  • -Xmn:设置新生代的大小。
  • -XX:MaxMetaspaceSize:设置元空间的最大大小。
  • -XX:PermSize:JDK 8 之前用于设置永久代的初始大小。
  • -XX:MaxPermSize:JDK 8 之前用于设置永久代的最大大小。

模拟堆内存OOM

默认情况下 分配的总内存是系统的1/4 ,而初始化的内存: 1/64

IDEA中设置堆内存大小操作

-Xms1024m -Xmx1024m --XX:+PrintGCDetails

初始值与最大值设成一致 --XX:+PrintGCDetails 是打印GC去除垃圾的细节

public class Demo {
    public static void main(String[] args) {
        //返回虚拟机试图使用的最大内存
        long max = Runtime.getRuntime().maxMemory();
        //返回JVM的初始化内存
        long total = Runtime.getRuntime().totalMemory();

        System.out.println("max:"+max+"字节\t" + (max/(double)1024/1024)+"MB");
        System.out.println("total:"+total+"字节\t" + (total/(double)1024/1024)+"MB");
    }
}

public class Demo {
    public static void main(String[] args) {
        //返回虚拟机试图使用的最大内存
        long max = Runtime.getRuntime().maxMemory();
        //返回JVM的初始化内存
        long total = Runtime.getRuntime().totalMemory();

        System.out.println("max:"+max+"字节\t" + (max/(double)1024/1024)+"MB");
        System.out.println("total:"+total+"字节\t" + (total/(double)1024/1024)+"MB");

        String srt = "java";
        while(true){
            srt += srt + new Random().nextInt(999999999)+new Random().nextInt(999999999);
        }
    }
}