Java 中JVM内存模型详解

137 阅读5分钟

JVM(Java Virtual Machine)是Java语言的核心,它是一个能够运行Java字节码的虚拟机。Java应用程序在JVM上运行,因此了解JVM内存模型对于开发高性能、健壮的Java应用程序非常重要。JVM内存模型是指JVM对计算机内存的抽象,在JVM中定义了Java程序内存的分配、访问和回收方式,可以提高Java应用程序的运行效率和稳定性。

JVM内存模型分为程序计数器、虚拟机栈、本地方法栈、堆和方法区五个部分,这些部分在JVM中都有着特定的作用和分配规则。下面将详细介绍JVM内存模型的各个部分。

程序计数器

程序计数器是一块较小的内存区域,它是当前线程所执行的字节码的行号指示器,它会保存当前执行的字节码指令的地址或行号。当程序执行Java方法时,程序计数器被初始化为0,随着Java方法执行,程序计数器会不断的指向下一条需要执行的指令。在多线程情况下,每个线程都会有自己的程序计数器,以便线程切换时能让CPU回到正确的执行位置,保证线程独立运行。

虚拟机栈

虚拟机栈是一块以线程为单位的内存区域,用于存储局部变量、方法参数、返回值和操作数栈等信息。在Java程序运行时,每个线程都会拥有自己的虚拟机栈,用于存储当前线程执行方法的上下文信息,包括方法调用和返回地址等。当一个方法被执行时,JVM会为它分配一块栈空间,用来保存方法的局部变量和操作数栈等信息。当方法返回时,这个栈空间就会被释放。

虚拟机栈的大小是固定的,通常为1M,可以通过-Xss参数进行调整。虚拟机栈有可能因为递归调用或者方法嵌套过深而导致栈内存溢出。

本地方法栈

本地方法栈与虚拟机栈类似,只不过它是为虚拟机调用Native方法服务的,用于保存Native方法的上下文信息。Native方法是指使用非Java语言编写的代码,在JVM中执行时需要将其转换成对应的Java代码,然后才能在JVM上运行。因此本地方法栈并不是所有的JVM都具有的,只有在使用Native方法时才会存在。

堆是JVM中最大的一块内存区域,用于存储对象实例和数组等信息。堆是Java程序中最为重要的内存区域,也是GC的主要工作区域。

在JVM中,堆内存被划分为年轻代(Young Generation)和老年代(Old Generation),年轻代又被分为Eden空间、Survivor0空间、Survivor1空间。当一个新对象被创建时,它会被分配到年轻代的Eden空间,如果Eden空间满了,JVM就会进行一次Minor GC,将所有存活的对象复制到其中一个Survivor空间中,并清空Eden空间和上一个Survivor空间。当Survivor空间也满了时,JVM就会将所有存活的对象复制到另一个Survivor空间中,并清空原Survivor空间。当Survivor空间分配不下时,就会将其中的对象晋升到老年代中。

当对象在老年代中存活了一定时间后,就会被判定为“可触及性”,稍后就可以被GC回收。老年代中的GC称为Major GC,它通常耗费较长时间,造成较大的停顿,因此应该尽可能地减少Major GC的频率。

堆大小可以通过-Xmx和-Xms参数进行调整,一般建议将-Xmx和-Xms设置为相同的值,避免堆内存频繁扩容。

方法区

方法区也是JVM内存模型的一部分,用于存储已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区与堆一样,也是所有线程共享的内存区域。方法区在JVM规范中也被称为永久代(PermGen),但在Java 8中已经被元空间(Metaspace)所取代。

方法区的大小也可以通过-XX:PermSize和-XX:MaxPermSize参数进行调整,但建议不要设置过大,避免出现方法区溢出的问题。

内存模型操作

在Java中,为了保证数据的一致性和有序性,JVM提供了一些操作,包括volatile、synchronized和final等。这些操作可以保证数据的一致性和有序性,从而避免出现并发问题。在Java 5以后,JVM中引入了“happens-before”原则,即前面的操作“happens-before”后面的操作,它可以保证线程之间的有序性和可见性,避免出现并发问题。

总结

JVM内存模型是Java程序的基础,它对计算机内存进行了抽象,并定义了Java程序内存的分配、访问和回收方式。了解JVM内存模型对于编写高性能、健壮的Java程序非常重要,只有深入了解JVM内存模型,才能更好地编写高质量的Java应用程序。