65.9K
CodeProject 正在变化。 阅读更多。
Home

Java 元编程(内存分配与管理)

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2012 年 4 月 16 日

CPOL

6分钟阅读

viewsIcon

25334

本文讨论了 Java 应用程序中的内存管理、不同数据类型的处理以及垃圾回收的作用。

引言

本文讨论了 Java 编程中的内存分配和内存管理。文章描述了 Java 编程之外的内容,以及每个编程词的实际影响。之后,讨论了垃圾回收的细节。

阅读完本文后,您将熟悉 Java 应用程序中的内存管理,并能预见您的代码对内存使用的积极和消极影响。

数据类型

Java 编程语言有两种不同的数据类型:原始数据类型和非原始(引用)数据类型。每种类型在内存中的存储和处理方式都不同。

原始数据类型

在 Java 中,有 8 种原始数据类型(boolean、byte、short、int、long、float、double、char)。根据这些数据类型创建的变量存储在栈中。它们的处理方式与 C、C++ 中旧式变量类似。变量在声明后即在栈中创建并存储。变量作用域(块、函数等)结束后,变量将被销毁,其位置将被释放。

图 1:原始变量声明

图 1 中的代码执行后,会创建一个栈位置,如图 2 所示。

图 2:原始变量声明后的栈视图

函数 `myFunction` 结束后,栈内存中的位置将被移除。这种栈分配用于在函数或块中声明为局部变量的原始变量。作为对象一部分的原始属性将像对象本身一样被处理。对象(非原始数据类型)的处理将在下一节中描述。

非原始(引用)数据类型

Java 中变量的大部分(作为纯面向对象编程语言)是引用对象部分。引用对象类型是

  • 内置包装类(Boolean、Integer、Long……)
  • 其他内置类(String、Object……)
  • 任何用户定义的类

引用对象由两部分组成:引用对象存储在栈中,被引用的对象(实际位置)存储在堆中。下图描述了对象声明和初始化的影响。

图 3:非原始对象声明

图 3 中的代码片段运行后,栈内存将受到图 4 所示的影响。

图 4:非原始对象声明后的栈视图

如果您现在尝试使用此对象,您将收到一个空指针异常,因为引用“X”到目前为止没有指向任何内存位置。

图 5:非原始对象声明和初始化

图 5 中的代码片段在堆中为该学生对象分配了内存位置。

图 6:非原始对象初始化后的内存视图

现在,实际对象的位置存储在堆内存中,一个保存该位置地址的引用存储在栈中。这有点像 C 和 C++ 中的指针,但有一些区别,例如,在 Java 中,您无法控制指针移动到不同的内存位置,如 C 中那样,这些指针的管理由 JVM 负责。

此规则适用于 Java 中的所有非原始对象。数组也被视为非原始对象。数组的引用存储在栈中,而实际的数组节点在堆中分配。此规则解释了为什么开发人员在初始化数组时(即使是原始数据类型的数组)需要调用“`new`”,如图 7 所示。

图 7:数组对象声明和初始化

`String` 类是 Java 中类的一个特殊情况。您可以通过图 8 中所示的一种方式定义字符串对象。

图 8:字符串对象声明和初始化

为了简单起见,Java 允许您在不调用“`new`”的情况下定义 `String` 对象,如图 8 中的“`y1`”对象所示。但是图 8 中定义的两个变量都有一个栈中的引用对象,以及一个堆位置,其中包含实际的字符串值。

虽然 `String` 类是不可变的,但对象值的任何更改都会导致在堆中预留新的位置,而旧的位置将保持无引用。图 9 和图 10 展示了示例代码及其在内存位置上的体现。

图 9:字符串声明和初始化示例

图 10:字符串示例的内存视图

“John”对象现在不需要了,因为没有引用指向它。这一点引出了“垃圾回收”这个话题。

垃圾回收

垃圾回收是 Java 提供的一种功能,用于管理内存并在堆内存中处理所有非原始对象。垃圾收集器是一个低优先级线程,会不时运行,检查是否有已使用的内存位置且没有引用指向这些位置。垃圾收集器将这些已使用的位置返回为堆空闲位置,并在需要时进行重用。在图 9 和图 10 的字符串示例中,“John”内存位置可以进行垃圾回收,因为没有对象引用它,而“Ali”和“Adams”则不能,因为它们被对象“`x1`”和“`y1`”使用。

在垃圾回收释放对象到内存之前,“`finalize()`”函数会自动调用该对象。Java 提供此功能是为了让开发人员有机会执行一些析构操作(释放资源等)。

垃圾回收的执行与主应用程序的执行并行进行,因此开发人员无法强制垃圾收集器工作,只能通过“`System.gc()`”推荐垃圾执行。虽然开发人员有多种内存管理方式,但其中一种方式是设置启动和最大堆大小,如图 11 所示。

图 11:堆内存参数示例

如果我们查看堆内存的详细信息,我们会发现 JVM 将堆内存划分为三个主要部分,如图 12 所示。

图 12:堆内存详细信息

如图 12 所示,堆内存由“年轻代”、“老年代”和“永久代”组成。永久代不用于应用程序对象,因此不在垃圾回收范围内。新对象在第一个阶段(Eden 区)插入年轻代区域。每隔一段时间,垃圾回收会执行一次小型回收活动;如果对象在后续的小型回收后仍然有引用,对象将被移动到 S1,然后如果仍然被使用,则移动到 S2。

同时,会不时执行一次主要的回收过程(这会占用主应用程序的一些性能时间,因为它比小型回收活动更复杂)。如果对象仍然需要,它将被移到“老年代”区域。这些年轻代和老年代的比例可以通过 JVM 参数来管理,如图 13 所示,这意味着年轻代:老年代的比例为 1:2。

图 13:堆内存分区设置参数

参考文献

历史

  • 版本 1:2012 年 4 月 15 日,初始版本。
© . All rights reserved.