内存管理 Java

⚡ 智能摘要

内存管理 Java 解释了 JVM 如何将运行时内存划分到栈、堆中。 Code以及静态区域,对象引用在方法调用期间如何流动,以及垃圾回收器如何回收不可达对象,从而使应用程序保持稳定且无内存泄漏。

  • 🧠 JVM内存布局: Java 内存分为堆(用于对象)、栈(用于方法帧和局部变量),以及专门用于字节码和静态数据的区域。
  • 📚 栈与堆: 栈帧遵循后进先出(LIFO)顺序,存储基本类型和引用,而堆保存使用 new 运算符创建的每个对象。
  • ♻️ 垃圾收集: 垃圾收集器 tracks 通过引用链可达,并自动从任何线程都无法访问的对象中回收内存。
  • 现代收藏家: 生产工作负载越来越依赖 G1、ZGC 和 Shenandoah 来进行跨多 GB 堆的低暂停垃圾回收。
  • 🧪 实际操作示例: 通过对 Student 类的逐步演示,我们展示了何时引用可以被回收,以及如何将引用置空以释放底层对象。

内存管理 Java

什么是堆栈内存? Java?

堆栈内存 Java 栈是 JVM 内存中存储每个线程的方法帧、局部变量和引用变量的区域。栈始终按照后进先出 (LIFO) 的顺序访问,因此最近调用的方法位于栈顶,其局部变量会随方法帧一起被压入和弹出。

每根线 Java 虚拟机拥有自己的栈,其设计旨在确保方法调用的隔离性和线程安全性。诸如 int、boolean 和 double 之类的基本局部变量直接存在于帧内,而存储在栈上的对象引用则指向堆上分配的对象。

什么是堆内存? Java?

堆内存是 JVM 共享的区域,用于保存所有内存。 Java 使用 `new` 运算符创建的对象和数组,以及作为实例字段属于这些对象的任何引用变量。与栈不同,堆在所有线程之间共享,因此对象访问通常需要同步。

堆是垃圾回收器管理的区域。现代 HotSpot JVM 将堆划分为用于存放生命周期短的对象的年轻区和用于存放生命周期长的对象的老区,类元数据则存储在一个名为元空间的独立本地区域中。

内存分配 Java

内存分配 Java 内存分配是指 JVM 在程序执行期间为变量、类实例和结构体实例分配虚拟内存区域的过程。内存并非在声明对象时分配,而只是创建引用。实际的对象分配是通过 `new` 运算符完成的,因此每个对象都存在于堆上。

此 Java 内存分配分为以下几个部分:

  1. Code
  2. 静止

这种内存划分对于有效管理运行时环境是必要的。

  • Code 本节包含您编译的内容。 字节码.
  • 百货商店 方法、局部变量和参考变量.
  • 部分包含 对象 也可能包含作为实例字段保存的引用变量。
  • 静止 本节包含 静态数据和静态方法 所有实例共享。

局部变量和实例变量之间的区别

了解每种变量在 JVM 中的位置有助于解释栈和堆的行为。 实例变量 被宣布 在类内部,但在任何方法之外它作为对象的一部分存在于堆中。

class Student{ 
int num; // num is instance variable 
public void showData{}

A 局部变量 被宣布 方法内部,包括方法参数并且存在于活动帧内的堆栈中。

public void sum(int a){

int x = a + 3;

// a, x are local variables

}

栈和堆的区别

栈和堆在 JVM 中分别解决不同的问题。栈为每个线程提供快速、确定性的内存分配,用于存储与方法作用域相关的短生命周期数据;而堆则提供了一个共享区域,用于存储任何线程都可以访问的长生命周期对象。下面的短视频概述了二者的区别,为接下来的详细讲解做准备。

点击 开始 如果视频无法访问

为了了解栈和堆是如何协同工作的,考虑一个调用方法 m1 的主方法。

public void m1{
int x = 20;
}

在 JVM 堆栈中,为方法 m1 创建一个帧。

Java 栈和堆

m1 中的变量 x 也是在堆栈上 m1 的帧中创建的,如下图所示。

Java 栈和堆

方法 m1 然后调用方法 m2。在堆栈中,在 m1 的帧之上为 m2 创建一个新帧。

Java 栈和堆

Java 栈和堆

局部变量 b 和 c 也在栈上 m2 的帧内创建。

public void m2(int b){
boolean c;
}

接下来,m2 调用方法 m3。同样,在堆栈顶部创建一个 m3 的帧,如下所示。

Java 栈和堆

Java 栈和堆

假设方法 m3 为 Account 类创建了一个对象,该对象有两个 实例变量 int p 和 int q。

class Account {
    int p;
    int q;
}

以下是方法 m3 的代码。

public void m3(){
    Account ref = new Account();
    // more code
}

语句 new Account() 在堆上创建一个 Account 对象。

Java 栈和堆

引用变量 ref 在 m3 的帧内的堆栈上创建。

Java 栈和堆

赋值运算符使引用变量指向堆上的对象。

Java 栈和堆

方法执行完毕后,控制权将返回给调用方法,在本例中为方法 m2。

Java 栈和堆

方法 m3 的帧从堆栈中刷新出来。

Java 栈和堆

由于引用变量不再指向堆上的对象,因此该对象符合垃圾回收的条件。

Java 栈和堆

方法 m2 执行完毕后,会从堆栈中弹出,其所有变量都将失效。方法 m1 也一样,最终控制权返回到主方法。

如果一个对象持有另一个引用作为其实例变量会怎样?

public static void main(String args[]) {
    A parent = new A();
    // more code
}
class A {
    B child = new B();
    int e;
}
class B {
    int c;
    int d;
}

在这种情况下,引用变量 child 作为 A 对象的一部分存在于堆上,并且指向它自己的 B 对象,如下所示。

Java 栈和堆

什么是垃圾收集 Java?

垃圾收集 Java 垃圾回收器是 JVM 自动执行内存管理的过程。它会查找任何不再可通过任何活动引用访问的对象,并回收其占用的内存。动态内存分配通过 `new` 运算符实现,并且内存会一直保持分配状态,直到程序不再持有对该对象的任何引用为止。

当所有引用都不存在时,该对象就被认为不再需要,其占用的内存可以被回收。无需显式地销毁对象,因为 Java 通过垃圾回收器自动处理内存释放。

这项技术背后的原理被称为 垃圾收集未能释放内存的程序最终会在没有内存可供分配时崩溃。这类程序被称为内存不足程序。 内存泄漏. 垃圾收集 Java 自动运行 在项目的整个生命周期内,这消除了手动分配的负担,并降低了泄露的风险。

相比之下,在 C 语言中,程序员负责通过 free() 函数释放动态分配的内存。 Java 内存管理具有显著优势。

现代 HotSpot JVM 搭载了多个针对不同工作负载优化的垃圾回收器。默认的 G1 垃圾回收器旨在平衡多 GB 堆的吞吐量和暂停时间。ZGC 和 Shenandoah 则力求在超大堆上实现亚毫秒级的暂停,因此它们在处理大型堆方面表现出色。trac对于延迟敏感型服务而言,选择合适的垃圾回收器并调整新生代、老年代和元空间的大小是一项核心技能。 Java 表演作品。

注意: 所有对象都在内存的堆区创建,该区域由垃圾回收器管理。

示例:学习垃圾收集器机制 Java

本演练演示了在简单程序中,引用何时符合垃圾回收的条件。

步骤1) 将以下代码复制到编辑器中。

class Student{
int a;
int b;
  public void setData(int c,int d){
    a=c;
    b=d;
  }
  public void showData(){
    System.out.println("Value of a = "+a);
    System.out.println("Value of b = "+b);
  }
  public static void main(String args[]){
    Student s1 = new Student();
    Student s2 = new Student();
    s1.setData(1,2);
    s2.setData(3,4);
    s1.showData();
    s2.showData();
    //Student s3;
    //s3=s2;
    //s3.showData();
    //s2=null;
    //s3.showData();
    //s3=null;
    //s3.showData();
  }
}

步骤2) 保存、编译并运行代码。如图所示,将创建两个对象和两个引用变量。

 垃圾收集机制

步骤3) 取消注释第 20、21 和 22 行。保存、编译并运行代码。

步骤4) 如下图所示,两个引用变量现在指向同一个对象。

垃圾收集机制

步骤5) 取消第 23 行和第 24 行的注释。保存、编译并运行代码。

步骤6) 如下所示,s2 变为 null,但 s3 仍然指向该对象,因此该对象还不符合垃圾回收的条件。

 垃圾收集机制

步骤7) 取消第 25 行和第 26 行的注释。保存、编译并运行代码。

步骤8) 此时,没有任何引用指向该对象,因此它符合垃圾回收的条件。垃圾回收器会将其从内存中移除,并且无法再将其检索出来。

 学习垃圾收集器

如何删除对象 Java?

Java 它不提供手动删除操作符,因此标​​准做法是删除对该对象的所有引用,以便垃圾回收器可以回收它。

1) 要使对象符合垃圾回收的条件,请将指向该对象的所有引用变量赋值为 null。

2) 基本类型不是对象,因此不能赋值为 null。当周围的栈帧弹出时,它们的存储空间会自动回收。

如何删除对象 Java

常见问题

栈(Stack)按照后进先出(LIFO)的顺序,按线程存储方法帧、局部变量和引用。堆(Heap)存储所有使用 `new` 创建的对象,并在线程间共享,因此由垃圾回收器进行管理。

垃圾回收器会遍历从 GC 根节点(例如活动线程和静态字段)开始的引用链。任何无法从根节点访问的对象都被视为不可达对象,并在下一个垃圾回收周期中符合回收条件。

年轻世代(Young Generation)将生命周期较短的对象保存在伊甸区(Eden)和幸存者区(Survivor)。老世代(Old Generation)则保存生命周期较长的对象,这些对象会在多次回收中存活下来。元空间(Metaspace)是一个本地内存区域,用于存储类元数据,它取代了旧的永久世代(PermGen)。 Java 8.

G1 是默认的垃圾回收机制,适用于大多数运行在多 GB 堆上的工作负载。ZGC 和 Shenandoah 的目标是在非常大的堆上实现亚毫秒级的暂停,是运行在对延迟敏感的服务器上的服务的理想选择。 Java 17或更高版本。

将所有指向该对象的引用置为 null,或者让这些引用在栈帧弹出时超出作用域。一旦没有存活的引用指向该对象,垃圾回收器就会回收其堆内存。

是的。AI驱动的调优工具会分析GC日志、内存分配率和暂停直方图,从而推荐堆大小、垃圾回收标志和新生代比例。与手动调优相比,它们可以缩短反馈周期,尤其是在G1、ZGC和Shenandoah工作负载下。

基于人工智能的可观测性平台会摄取堆转储和实时指标,然后对保留对象图进行聚类,以标记可疑的类和引用链。这比手动堆分析更快地发现泄漏候选对象,并帮助团队精确定位生产环境中的根本原因。

总结一下这篇文章: