JVM 简单内存结构及例子

Java虚拟机(JVM)内存结构是Java程序运行时内存分配和管理的方式。JVM内存结构通常分为以下几个主要部分:
方法区(Method Area):

存储类信息、常量、静态变量以及即时编译后的代码等数据。

这部分内存在JVM启动时创建,并且其大小在JVM终止时销毁。
堆(Heap):

存储所有的对象实例和数组。

堆内存被划分为年轻代(Young Generation)和老年代(Old Generation)。

年轻代进一步划分为Eden Space(伊甸园)和Survivor Space(幸存者空间)。

老年代通常包含大对象和长期存活的对象。


堆内存结构的详细说明:
年轻代(Young Generation):

年轻代是堆的一部分,用于存储新创建的对象。这些对象通常生命周期较短,因此年轻代的垃圾回收(GC)非常频繁。

年轻代通常分为三个区域:Eden Space(伊甸园)、【Survivor Space(幸存者空间)和To Space(过渡空间)---(也可说幸存者0区与幸存者1区)】。

新创建的对象首先存放在Eden Space。当Eden Space满时,会触发Minor GC(垃圾回收),存活下来的对象会被移动到Survivor Space。

在下一次Minor GC时,Survivor Space中存活的对象会被移动到To Space,而To Space中的对象则会被移动到Survivor Space,Eden Space则被清空并用于新一轮的对象分配。
老年代(Old Generation):

老年代用于存储生命周期较长的对象,即在年轻代中经过多次垃圾回收后仍然存活的对象。

老年代的垃圾回收不如年轻代频繁,因为老年代的对象通常较为稳定,垃圾回收的频率和范围较大,通常称为Major GC或Full GC。
永久代(Permanent Generation):

永久代用于存储类加载器加载的类信息、常量池、静态变量等。

永久代的大小固定,且在JVM运行期间不会改变,垃圾回收不会回收永久代中的对象。


栈(Stack):

每个线程拥有自己的栈,用于存储局部变量、操作数栈、方法调用和返回地址。

栈内存随着方法调用分配,随着方法执行结束而回收。
本地方法栈(Native Method Stack):

为JVM使用到本地方法接口(JNI)的本地方法调用提供空间。
程序计数器(PC Registers):

每个线程有自己的程序计数器,用于存储指向下一个将要执行的指令的地址。
直接内存分配堆(Direct Memory):

JVM可以通过ByteBuffer.allocateDirect()方法直接在堆外内存分配内存,用于NIO操作,以减少内存拷贝。
内存结构举例

假设我们创建了一个Person类的对象并将其引用存储在一个名为personRef的引用变量中:

java 复制代码
public class Person {
    private String name;
    // 构造函数、getter和setter等
}

public class Main {
    public static void main(String[] args) {
        Person person = new Person();
        person.name = "John Doe";
        String personRef = person.toString(); // 假设toString()返回对象的字符串表示
    }
}

在这个例子中:

Person类的.class文件被加载到方法区。

person对象被创建在堆内存中。

personRef字符串引用存储在栈内存中。

如果Person对象在方法执行过程中不再被引用,它将被标记为垃圾回收的候选,最终可能被垃圾收集器回收。

堆信息:

Person类的信息(类元数据)存储在永久代中。

person对象实例存储在堆的年轻代中。

person.name字符串常量存储在永久代中。

引用变量person存储在栈内存中。

对象和引用存储

对象:存储在堆内存中,包括实例变量和数组。

引用:存储在栈内存中,指向堆内存中的对象。

JVM内存管理是自动的,开发者通常不需要手动管理对象的创建和销毁,但理解内存结构有助于优化程序性能和减少内存泄漏的风险。


方法区和永久代的区分

在Java虚拟机(JVM)中,方法区(Method Area)和永久代(Permanent Generation)确实都存储类的信息、常量和静态变量,但它们在JVM中扮演着不同的角色,并且有不同的生命周期和用途。

方法区(Method Area)

方法区主要用于存储以下内容:

  1. 类信息:包括类的版本、访问修饰符、类名称、父类名称、接口等。
  2. 字段信息:类中定义的字段的名称、类型、修饰符等。
  3. 方法信息:类中定义的方法的名称、返回类型、参数类型、修饰符等。
  4. 常量池:存储编译期间生成的各种字面量,如字符串常量、final修饰的类字段等。
  5. 静态变量:类的静态变量(static变量)在方法区中分配内存。

方法区是JVM启动时创建的,其大小在启动时确定,并且在JVM终止时销毁。方法区中的内容在类加载时被加载,并且随着类的卸载而消失。

永久代(Permanent Generation)

永久代主要用于存储以下内容:

  1. 类信息:类的元数据,如类的全限定名、类的直接父类、类的接口等。
  2. 常量池:存储字符串字面量、数字常量等,这些常量在编译时确定,并在JVM的整个生命周期中保持不变。
  3. 静态变量:类的静态变量(static变量)在永久代中分配内存,它们的生命周期与JVM相同。

永久代的大小是固定的,通常不会进行垃圾回收。永久代中的内容在整个JVM的生命周期中都存在,除非类被卸载。

区别

尽管方法区和永久代都存储类信息、常量和静态变量,但它们有以下主要区别:

  1. 生命周期:方法区的生命周期较短,随着类的卸载而消失;永久代的生命周期较长,与JVM的生命周期相同。
  2. 存储内容:方法区存储类的详细信息和运行时数据;永久代主要存储类元数据、常量池和静态变量。
  3. 垃圾回收:方法区的内容在类卸载时被回收;永久代的内容通常不参与垃圾回收。

示例

假设我们有一个简单的Java类:

java 复制代码
public class MyClass {
    public static final String CONSTANT = "Hello, World!";
    public static void printConstant() {
       System.out.println(CONSTANT);
   }
}
  • MyClass.class文件被加载到方法区。
  • CONSTANT字符串常量存储在永久代的常量池中。
  • printConstant方法的信息存储在方法区。
  • MyClass类的静态变量(如果有)存储在永久代中。

理解这些内存区域的不同用途和特性有助于开发者更好地理解JVM的内存管理和优化应用程序的性能。

相关推荐
yanjiaweiya8 分钟前
云原生-集群管理
java·开发语言·云原生
gadiaola16 分钟前
【JavaSE面试篇】Java集合部分高频八股汇总
java·面试
艾迪的技术之路39 分钟前
redisson使用lock导致死锁问题
java·后端·面试
今天背单词了吗9801 小时前
算法学习笔记:8.Bellman-Ford 算法——从原理到实战,涵盖 LeetCode 与考研 408 例题
java·开发语言·后端·算法·最短路径问题
天天摸鱼的java工程师1 小时前
使用 Spring Boot 整合高德地图实现路线规划功能
java·后端
东阳马生架构1 小时前
订单初版—2.生单链路中的技术问题说明文档
java
咖啡啡不加糖1 小时前
暴力破解漏洞与命令执行漏洞
java·后端·web安全
风象南2 小时前
SpringBoot敏感配置项加密与解密实战
java·spring boot·后端
DKPT2 小时前
Java享元模式实现方式与应用场景分析
java·笔记·学习·设计模式·享元模式