JVM内存区域

内存区域

简介

首先,我们为什么需要对JVM内存结构有深入了解?

深入了解JVM(Java虚拟机)内存结构对于开发和维护Java应用程序非常重要,因为它直接影响着应用程序的性能、稳定性和资源利用。好处如下:

  1. 性能优化:我们可以通过JVM调优,选择适当的垃圾回收器和内存分配策略,减少垃圾回收的频率,提高程序执行的效率。
  2. 避免内存泄露:有助于识别额避免内存泄露问题。
  3. 有效利用资源:了解内存区域,可以帮助我i们更好的分配内存资源,合理的配置堆,方法区,直接内存等,可以使程序在有效的资源上高效运行。
  4. 调优程序:帮助我们识别和解决程序的性能瓶颈。
  5. 稳定性:程序正常运行,避免因为内存管理差异导致不稳定性。
  6. 代码安全性:有助于编写安全可靠的Java代码。

JVM内存区域概览

JVM的主要内存区域,包括程序计数器、Java堆、方法区(或元空间Metaspace)、虚拟机栈、本地方法栈等。

JVM内存区域_第1张图片

Java堆(Java Heap)

堆的作用

主要作用:存储对象实例和数组。

  • 存储对象:当我们new一个对象的时候,实际上Java会帮我们在堆上分配内存给这个对象,几乎所有对象都分配在堆内存上面,但是例如通过逃逸分析逃逸的对象,为了提升效率,对象会分配在栈上面。

  • 存储数组:首先数组也是对象,因为它继承Object对象,请看如下验证代码。

    public static void main(String[] args) {
        int[] test = new int[5];
        System.out.println(test instanceof  Object);--返回true
    }
    

    数组是一种包含相同类型元素的数据结构,Java堆除了存储对象实例,也用于存储各种类型的数组。

    Java堆除了分配对象和数组的作用外,还提供了动态内存分配,自动垃圾回收功能,使得Java程序在内存管理上更加灵活和高效。

新生代和老年代

在Java堆内存中,新生代(Young Generation)和老年代(Old Generation)是两个主要的内存区域。这样划分主要是为了更好的进行垃圾回收和内存利用。

JVM内存区域_第2张图片

新生代

  • Eden区:用于存储新创建的对象,大多数新创建的对象都被分配到Eden区。
  • Survivor区:分为两部分,S0区(from survivor)和S1区(to survivor)。当垃圾回收时,存活的对象被移动到另外一个S区,当前S区会被清空。

老年代

Java堆内存的另外一个部分是老年代,通常,生存时间较长的对象会晋升为老年代。主要特点:

  1. 对象存活时间长
  2. 垃圾回收次数少

什么样的对象会进入老年代?

  • 大对象直接进入老年代。比如很长的字符串,或者很大的数组等。可以用-XX:PretenureSizeThreshold来设置经过多少此垃圾回收进入老年代。
  • 长期存活的对象进入老年代。在堆中分配内存的对象,其内存布局的对象头中(Header)包含了 GC 分代年龄标记信息。如果对象在 eden 区出生,那么它的 GC 分代年龄会初始值为 1,每熬过一次 Minor GC 而不被回收,这个值就会增加 1 岁。当它的年龄到达一定的数值时(jdk1.7 默认是 15 岁),就会晋升到老年代中。可以用-XX:MaxTenuringThreshold来设置经过多少此垃圾回收进入老年代。
  • 动态对象年龄判定。当 Survivor 空间中相同年龄所有对象的大小总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,而不需要达到默认的分代年龄。

Eden区和Survivor区

Eden区和Survivor的作用

Eden区是新生代的主要分配区域,用于存储新创建的对象;

Survivor区有S0和S1,这两个区域在垃圾回收过程中交替使用。

回收策略

Eden的回收策略

当Eden区空间满了时,会触发Minor GC;垃圾对象会被回收,存活的对象会被移动到Survivor区。

Survivor回收策略
  • 年龄计数法:对象在S区存活会被记录“年龄”,每次Minor GC,年龄会增加“一岁”,年龄达到15(默认情况下)后,对象晋升到老年代。
  • 对象移动:S0区和S1区交替使用,垃圾回收过程中,将存活对象移动到另外一个S区,清除掉当前区域对象,因此,总保持一个完全空的S区。

方法区(Metaspace)

**方法区(method area)或者元空间(metaspace)**是JVM的一个内存区域。用于存储元数据信息,静态变量,常量池以及编译后的代码。

JDK7及以前的版本称为方法区,JDK8及以后版本称为元空间。

元空间存储的信息:

  • 元数据信息:包括类的结构,方法的签名,字段的描述,方法的访问权限等等。这些元数据信息在JVM运行时进行类的加载,验证,解析,初始化操作至关重要。
  • 静态变量和常量池:静态变量属于类本身,它们的值在整个类加载周期内保持不变。这些静态变量被存储在方法区(或元空间)。另外,常量池也存储在方法区。
  • 代码缓存:元空间还存储了编译后的代码信息。包括编译后的字节码和方法调用的相关信息。

JDK8及以后版本,用元空间代替了永久代。如下图:

JVM内存区域_第3张图片

永久代和元空间区别

  1. 内存位置
    • 永久代:在堆内存中
    • 元空间:直接内存
  2. 内存管理
    • 永久代:需要手动设置永久代大小,是固定的,可能永久代内存溢出。
    • 元空间:不受大小限制,动态调整,避免内存溢出。

元空间的优势

  1. 避免内存溢出:永久代在堆中,需要手动设置大小,而元空间在直接内存之中,动态调整大小,避免了内存溢出。
  2. 性能好:元空间在直接内存之中,减少了和堆的交互,提升了访问效率。

栈(Stack)

Java的栈分为虚拟机栈(Virtual Machine Stack)和本地方法栈(Native Method Stack),都是用于方法的调用和管理局部变量。

虚拟机栈用于执行Java方法,本地方法栈用于执行本地方法调用,本地方法指其他编程语言如C语言,C++语言,在Java程序中是以native关键字声明的。

程序计数器

作用

记录线程执行的指令地址。

程序计数器(Program Counter)是Java虚拟机中的重要概念,它是每个线程私有的内存区域,用于记录程序执行的位置和下一条要执行的字节码指令。

程序计数器为什么设计成私有的?

将程序计数器设计为线程私有是为了确保多线程环境下的线程独立性、方法调用和返回的正确性以及更高的性能。每个线程都有自己的程序计数器,可以独立记录和管理自己的指令执行位置,从而支持多线程的并发执行。

直接内存(Direct Memory)

直接内存(Direct Memory)在Java中是使用java.io.ByteBuffer等类来分配和管理的一块内存区域,它是运行在Java虚拟机之外的一部分内存,不由Java虚拟机管理,需要通过调用操作系统本地方法来分配和释放。

字符串常量池

字符串常量池(String Constant Pool)也是Java的内存区域,用于存储字符串字字面值和字符串对象。字符串常量池作用在于优化字符串的存储和共享,提高性能并减少内存消耗。

JDK6及之前版本:字符串常量池位于永久代,而永久代是堆内存的一部分,这可能导致永久代内存溢出。

JDK7及之后版本:字符串常量池发生了变化,被移到了堆内存中,不在位于永久代,避免了内存溢出。

你可能感兴趣的:(JVM,jvm)