首页 > 系统相关 >Java 内存的使用流程与机制

Java 内存的使用流程与机制

时间:2024-10-12 10:51:41浏览次数:9  
标签:存储 Java 对象 流程 内存 JVM 方法

Java 的内存结构(Memory Structure)是 Java 虚拟机(JVM)在运行时管理内存的方式,它直接关系到 Java 程序的性能和运行的稳定性。

Java 的内存结构可以总结为以下几个关键部分:

  • 堆内存:存储对象和数组,是垃圾回收的主要目标。
  • 栈内存:存储局部变量、方法调用栈帧,线程私有。
  • 方法区:存储类的元数据、常量池、静态变量等,线程共享。
  • 程序计数器:记录当前线程执行的位置。
  • 本地方法栈:存储本地方法调用信息。
  • 直接内存:操作系统本地内存,通过 NIO 进行快速数据传输。

1. 堆内存(Heap Memory)

堆内存是 Java 内存结构中最大的一部分,主要用于存储所有的对象实例和数组。在 Java 程序运行时,所有对象的创建都是在堆中分配内存。堆内存也是垃圾回收(Garbage Collection,GC)的主要工作区域。

堆内存可以进一步分为两个主要区域:

  • 新生代(Young Generation)

    • 新生代又分为三个部分:Eden 区 和两个相同大小的 Survivor 区(一般命名为 Survivor 0 和 Survivor 1 或 From 和 To)。
    • 新生代存放的是新创建的对象,大部分对象在这里很快就会被标记为垃圾并被清理(因为大多数对象生命周期很短)。当对象经过多次 GC 后没有被回收,会从 Eden 区和 Survivor 区晋升到老年代。
  • 老年代(Old Generation)

    • 老年代存储的是生命周期较长的对象,这些对象是从新生代中经过多次垃圾回收存活下来的。垃圾回收在老年代发生频率较低,但一旦发生,通常会是一个 “Major GC” 或 “Full GC”。

2. 栈内存(Stack Memory)

每个线程在 JVM 中都有自己的栈,栈内存用于存储局部变量、方法调用和线程信息。每当一个方法被调用时,都会为该方法创建一个栈帧(Stack Frame),这个栈帧包含了该方法的局部变量表、操作数栈和返回地址等信息。

  • 栈中的变量存储的是基本数据类型(如 intchar)的值,以及对象的引用,而对象本身存储在堆中。
  • 当方法调用完成后,栈帧将会出栈,内存将被释放。

栈内存的特点:

  • 线程私有:每个线程都有自己的栈,栈内存不共享。
  • 自动管理:栈的大小是有限的,如果栈空间不足,会抛出 StackOverflowError

3. 方法区(Method Area)

方法区(Method Area)是 JVM 内存的一部分,它存储了每个类的结构信息,如类的元数据、常量池、静态变量和类构造方法(即类的字节码)。方法区在 JVM 启动时被创建,是所有线程共享的。

  • 类信息:当一个类第一次被加载时,它的类信息会被存储在方法区中。包括类名、父类名、修饰符、字段、方法和接口等。
  • 常量池:常量池存储的是编译期生成的一些常量(如字符串常量、数字常量等),以及方法和字段的引用。
  • 静态变量:方法区还保存类的静态变量和方法。

注意:在早期的 Java 实现中,方法区被称为永久代(Permanent Generation,PermGen),从 Java 8 开始,永久代被移除,取而代之的是元空间(Metaspace)。元空间位于本地内存而非堆内存中,解决了以前永久代中的内存限制问题。

4. 程序计数器(Program Counter, PC Register)

程序计数器是每个线程私有的,存储的是当前线程执行的字节码指令的地址。JVM 在多线程环境下,通过程序计数器来保存每个线程的执行状态(即每个线程执行到哪一条指令)。当线程切换时,程序计数器可以保证线程恢复执行时能够从正确的位置开始。

  • 如果当前线程执行的是 Java 方法,程序计数器存储的是正在执行的字节码指令地址。
  • 如果执行的是本地方法(Native Method),则程序计数器为空(Undefined)。

5. 本地方法栈(Native Method Stack)

本地方法栈用于存储本地方法调用的信息。当 Java 调用非 Java 语言编写的方法时,如 C 或 C++ 编写的代码,相关的信息会存储在本地方法栈中。

  • 与 Java 栈类似,本地方法栈也是线程私有的。
  • 如果调用的本地方法使用了 JNI(Java Native Interface),它的调用信息会被保存到本地方法栈中。

6. 直接内存(Direct Memory)

直接内存并不是 JVM 内存结构的严格一部分,但它经常被 JVM 使用。直接内存使用的是操作系统的本地内存,而不是堆内存。通过 NIO(New I/O),Java 程序可以通过直接内存进行快速的数据传输,避免在堆内存和操作系统内存之间进行频繁的数据复制。

直接内存的大小并不受 JVM 堆内存大小的限制,而是受限于操作系统的总内存。

7. 垃圾回收机制(Garbage Collection, GC)

Java 的垃圾回收机制是 JVM 自动管理内存的重要部分,它负责清理不再使用的对象,释放堆内存空间。垃圾回收主要作用在堆内存上,GC 的触发和算法直接影响程序的性能。

常见的垃圾回收算法包括:

  • 标记-清除(Mark-Sweep):标记所有存活的对象,随后清理未标记的对象。
  • 标记-整理(Mark-Compact):标记存活对象后,通过整理移动对象来消除碎片化。
  • 复制算法(Copying):将存活的对象从一块内存复制到另一块内存,清理掉不再存活的对象。

下面以几个常见的场景为例,分别讲解不同内存区域的作用和内存的管理机制。

场景 1:基本数据类型与局部变量的分配(栈内存)

public class TestMemory {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        int sum = a + b;
        System.out.println(sum);
    }
}
内存使用流程:
  1. 栈内存的分配

    • main 方法开始执行时,JVM 会为该方法在栈内存中分配一个栈帧(Stack Frame),该栈帧用来存储方法的局部变量表、操作数栈和返回地址。
    • 在局部变量表中,absum 三个变量都是 int 类型,属于基本数据类型,它们的值分别存储在栈内存中,而不是堆内存。
  2. 方法执行

    • JVM 在栈帧中执行 int a = 10;,将 10 存储在局部变量 a 的位置。
    • 同理,b = 20 也存储在栈中。
    • 计算 sum = a + b 后,sum 的值为 30,存储在栈帧中。
  3. 方法结束

    • main 方法执行完成时,栈帧被移除,分配给 main 方法的内存也被释放。这些局部变量随着方法的结束而消失。

场景 2:对象的创建与垃圾回收(堆内存)

public class TestObject {
    public static void main(String[] args) {
        Person p1 = new Person("Alice");
        Person p2 = new Person("Bob");
        p1.sayHello();
    }
}

class Person {
    String name;

    Person(String name) {
        this.name = name;
    }

    void sayHello() {
        System.out.println("Hello, my name is " + name);
    }
}
内存使用流程:
  1. 对象创建(堆内存分配)

    • main 方法开始时,JVM 在栈中为 main 方法创建一个栈帧,存储局部变量 p1p2
    • 当执行 Person p1 = new Person("Alice"); 时:
      • new 关键字表示需要在堆内存中创建一个新的 Person 对象。
      • JVM 在堆内存中分配空间,存储 Person 对象的实例(包括 name 字段)。
      • p1 存储在栈内存中,它只是对堆中 Person 对象的引用,指向堆中分配的 Person 对象。
      • 同样地,p2 是对 Person("Bob") 对象的引用。
  2. 对象的生命周期和垃圾回收

    • p1.sayHello(); 调用 Person 类的 sayHello() 方法时,JVM 会通过 p1 的引用找到堆内存中的 Person 对象,执行方法。
    • 方法结束后,p1p2 仍然在栈中存活,但一旦 main 方法结束,这些对象的引用会失效。
    • 垃圾回收机制(GC)将会负责回收堆中不再被引用的 Person 对象(即当没有任何栈帧中指向这些对象时,它们将被标记为垃圾,并在适当的时候回收内存)。

场景 3:静态变量与方法区的使用

public class TestStatic {
    public static void main(String[] args) {
        StaticClass.incrementCount();
        StaticClass.incrementCount();
        System.out.println(StaticClass.count);
    }
}

class StaticClass {
    static int count = 0;

    static void incrementCount() {
        count++;
    }
}
内存使用流程:
  1. 静态变量的存储(方法区/元空间)

    • StaticClass 类的 count 是一个 static 变量,它的内存分配不同于实例变量。静态变量存储在方法区(在 Java 8 之后是元空间),该区域是所有线程共享的,而不是属于某个对象的实例。
    • 当 JVM 加载 StaticClass 类时,count 变量被分配在方法区,并初始化为 0。
  2. 方法执行

    • 每次调用 StaticClass.incrementCount(),静态方法在栈中被调用(创建栈帧),count 变量被增加,但它始终位于方法区,而不是堆中或栈中。
    • 静态变量的生命周期与类的生命周期一致,类在程序执行过程中只加载一次,count 也只分配一次,无论创建多少个 StaticClass 实例,count 的值都不会改变位置。

场景 4:字符串常量池与方法区

public class TestStringPool {
    public static void main(String[] args) {
        String str1 = "Hello";
        String str2 = "Hello";
        String str3 = new String("Hello");
        
        System.out.println(str1 == str2); // true
        System.out.println(str1 == str3); // false
    }
}
内存使用流程:
  1. 字符串常量池

    • String str1 = "Hello"; 将字符串 "Hello" 分配到方法区的字符串常量池中。Java 会在加载类时检查常量池中是否已经存在该字符串,如果存在,直接返回引用;如果不存在,则在常量池中创建该字符串。
    • String str2 = "Hello"; 执行时,JVM 检查常量池,发现 "Hello" 已经存在,因此 str1str2 都引用同一个常量池中的字符串对象。
  2. 堆内存中的字符串对象

    • String str3 = new String("Hello"); 这一行创建了一个新的字符串对象,它被分配到堆内存中,因此 str3 引用的是堆中的对象,而不是常量池中的字符串。
  3. 比较结果

    • str1 == str2 返回 true,因为它们引用的是同一个常量池中的字符串。
    • str1 == str3 返回 false,因为 str3 引用的是堆中的对象,而不是常量池中的那个对象。

垃圾回收的触发与内存管理

垃圾回收(Garbage Collection,GC)是 Java 内存管理中的重要机制,它负责清理那些不再被引用的对象,从而释放堆内存。常见的垃圾回收机制包括:

  • 新生代 GC(Minor GC):主要作用于新生代(Eden 和 Survivor 区域),处理短生命周期的对象。
  • 老年代 GC(Major GC 或 Full GC):主要作用于老年代,回收生命周期较长的对象。
  • GC 的触发条件:当堆内存不足,特别是 Eden 区满时,GC 会被触发来回收无用的对象。

通过合理的垃圾回收策略,JVM 保证了堆内存的有效利用,避免内存泄漏和程序崩溃。

总结

Java 内存的使用流程与机制在不同场景下体现出不同的特性:

  • 基本数据类型和局部变量 在栈内存中分配,生命周期随方法的执行结束而结束。
  • 对象 在堆内存中分配,JVM 通过垃圾回收机制自动管理对象的生命周期。
  • 静态变量字符串常量 存储在方法区,生命周期与类的生命周期一致。
  • 字符串常量池 通过优化重复字符串的内存使用来提升性能。

标签:存储,Java,对象,流程,内存,JVM,方法
From: https://blog.csdn.net/2404_87410060/article/details/142735656

相关文章

  • java.util.Arrays#sort
    基本数据类型数组/***java.util.Arrays#sort(int[])*publicstaticvoidsort(int[]a){*DualPivotQuicksort.sort(a,0,a.length-1,null,0,0);//DualPivotQuicksort*}*/Obje......
  • java.util.Collections#sort(java.util.List<T>)
    java.util.ArrayList/java.util.LinkedList/***java.util.Collections#sort(java.util.List)*publicstatic<TextendsComparable<?superT>>voidsort(List<T>list){*list.sort(null);*......
  • JAVA反射是什么(反射的作用)
    反射概述反射是什么反射(Reflection)是Java的特征之一,它允许运行中的Java程序获取自身的信息,并且可以操作类或对象的内部属性。Oracle官方对反射的解释是:ReflectionenablesJavacodetodiscoverinformationaboutthefields,methodsandconstructorsofloa......
  • Java日总结---JDBC
    JDBC就是使用java语言操作关系型数据库的一种API1.JDBAAPI---DriverManager(两个作用)注册驱动:在mysql8.0后是ClassforName("com.mysql.cj.jdbc.Driver");来实现的查看源码后发现是由Driver类中静态代码块,底层是由DriverManager中的registerDriver方法来做的提示:在mysql5......
  • JavaScript 第4章:函数与作用域
    在JavaScript中,函数是程序设计中的重要组成部分,它们用于封装一段代码以执行特定的任务。下面我们将逐一探讨第4章提到的各个概念。1.函数声明vs函数表达式函数声明(FunctionDeclaration)是使用function关键字定义一个函数,并给它命名的一种方式。这种方式定义的函数会......
  • 微信公众号推送每日天气(Java版)
    准备工作公众号必须经过企业认证,个人公众号的无法使用这是获取到微信公众号的appId、secret网址贴这儿了:https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index还有测试的模板申请每日一言,我这里使用的彩虹屁,地址:https://www.tianapi.com/......
  • Java在图片上写字生成新图片的代码实现
    引言在图像处理领域,有时我们需要在图片上添加文字,以生成带有特定信息的新图片。Java作为一种功能强大的编程语言,提供了多种库和工具来实现这一需求。本文将详细介绍如何使用Java在图片上写字,并生成一张新的图片。准备工作在开始编写代码之前,我们需要确保已经安装了Java开......
  • taozige/Java语言的Netty框架+云快充协议1.5+充电桩系统+新能源汽车充电桩系统源码
    云快充协议+云快充1.5协议+云快充1.6+云快充协议开源代码+云快充底层协议+云快充桩直连+桩直连协议+充电桩协议+云快充源码介绍云快充协议+云快充1.5协议+云快充1.6+云快充协议开源代码+云快充底层协议+云快充桩直连+桩直连协议+充电桩协议+云快充源码软件架构1、提供云快......
  • 【Java】创建对象的几种方式
    【Java】创建对象的几种方式合集-Java基础(11)1.【Java】Integer包装类缓存(cache)05-092.【java】Java之关于基本数据类型和引用数据类型的存放位置05-093.【java】ArrayList和LinkedList的区别05-104.【java】【集合类】HashMap与HashTable的区别05-105.【Java】的四种引用......
  • java计算机毕业设计管易tms运输智能监控管理系统(开题+程序+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景在现代物流行业中,运输管理系统的智能化与高效化已成为提升企业竞争力的关键因素之一。随着电子商务和快递业务的迅猛发展,货物运输量急剧增加,传统的人......