内存管理是编程的一个重要方面,java 为开发人员提供了自动垃圾收集机制来高效处理它。与 c 或 c 等语言开发人员必须手动分配和释放内存不同,java 的垃圾收集器 (gc) 负责回收未使用的内存,从而降低内存泄漏的风险。
在这篇博文中,我们将探讨垃圾回收是什么、为什么它很重要、它在 java 中如何工作以及使用它的一些最佳实践。
什么是垃圾收集?
java中的垃圾收集是自动识别和回收应用程序不再可达或不再需要的对象所占用的内存的过程。这些对象被标记为“垃圾”,并且它们的内存被回收以供重用。
主要特点:
- 自动:java 虚拟机 (jvm) 自动管理内存。
- 非确定性:无法预测垃圾回收发生的确切时间。
- 标记和清除:大多数垃圾收集算法都涉及标记可达对象并清除不可达对象。
为什么垃圾收集很重要?
- 内存优化:释放未使用的内存,以确保应用程序有足够的空间继续运行。
- 简化开发:无需手动内存管理,减少开发人员的工作量和错误。
- 防止内存泄漏:识别并清理未使用的对象,确保不会不必要地消耗内存。
java 中的垃圾收集是如何工作的?
堆内存和对象生命周期
java 的垃圾收集器在堆内存上运行,所有对象都存储在堆内存上。对象有不同的生命周期,垃圾收集器确保:
立即学习“Java免费学习笔记(深入)”;
- 仍在使用的对象保留在内存中。
- 不再可达的对象(即没有引用)被标记为删除。
分代垃圾收集模型
java 使用分代模型来优化垃圾收集,基于以下假设:
- 大多数对象都是短暂的(例如,在方法中创建的临时对象)。
- 寿命长的对象不太可能成为垃圾。
堆分为以下区域:
年轻一代(或新一代):
- 用途:保存短期对象(例如临时变量、循环变量)。
- 部门:
- eden space:分配新对象的地方。
- 幸存者空间(s0 和 s1):在 eden 中的一次或多次垃圾回收中幸存下来后,对象将被移动到幸存者空间。
- 垃圾收集:
- minor gc 用于清理年轻代。
- 经常发生,因为这里的大多数物体的寿命都很短。
- 晋升:如果一个对象在一定数量的gc周期中幸存下来,它就会被晋升到老年代。
老一代(或终身一代):
- 用途:存储长期存在的对象,例如那些在整个应用程序生命周期中持续存在的对象。
- 垃圾收集:
- major gc(或 full gc)发生频率较低,但需要更长的时间,因为它涉及整个堆。
- 年轻代中的对象如果能存活足够的 gc 周期,就会在这里被提升。
- 大小:通常比年轻一代大。
永久代 (permgen) [java 8 之前]:
- 目的:
- 存储有关类、方法和其他 jvm 内部结构的元数据。
- 还存储静态字段和字符串文字。
- gc:
- 不像堆(年轻/老一代)那样动态管理,如果空间耗尽,可能会导致 outofmemoryerror: permgen space。
- 很少收集。
- 弃用:
- 在 java 8 中删除并替换为 metaspace。
- 元空间在本机内存中分配并动态增长,避免了许多与 permgen 相关的问题。
- 目的:
将内存分为几代可以让 gc 通过关注以下方面来优化其性能:
- 频繁、快速地清理短寿命对象(年轻一代)。
- 对长寿命对象(老一代)的清理频率较低,但更彻底。
后 java 8 变化(元空间):
- metaspace 取代了 permgen,并且位于本机内存而不是 jvm 堆中。
- 没有像permgen那样的固定大小;它可以根据需要增长,取决于可用的系统内存。
垃圾收集的阶段
- 标记:
- 通过遍历 gc 根的引用来识别所有可到达的对象(例如,局部变量、活动线程)。
- 扫地:
- 回收未引用对象占用的内存。
- 压缩(可选):
- 重新排列内存中的对象以消除碎片。
java 中的垃圾收集算法
jvm提供了多种垃圾回收算法,可以根据应用的需求进行选择。
串行垃圾收集器
- 最适合:单线程环境或小堆。
- 机制:使用单线程进行垃圾回收。
- 主要特征:更简单,但会导致应用程序在 gc 期间暂停。
并行垃圾收集器(吞吐量收集器)
- 最适合:需要高吞吐量的应用程序(java 8 中默认)。
- 机制:年轻代和老年代都使用多线程进行gc。
- 主要特征:减少暂停时间,但可能会影响 cpu 可用性。
g1 垃圾收集器(垃圾优先)
- 最适合:需要低延迟 gc 的应用程序(java 9 及更高版本中默认)。
- 机制:将堆划分为多个区域,并优先收集垃圾最多的区域。
- 主要特征:平衡暂停时间和吞吐量。
z 垃圾收集器 (zgc)
- 最适合:需要超低延迟 gc 的大型堆应用程序。
- 机制:同时执行大部分工作,并在毫秒范围内暂停。
- 主要特征:非常适合高达 tb 大小的堆。
shenandoah 垃圾收集器(shenandoah gc)
- 最适合:需要低延迟 gc 的中型到大型堆的应用程序。
- 机制:执行并发垃圾收集和压缩,通过与应用程序线程一起运行大多数操作来最大限度地减少暂停时间。
- 主要特征:通过在应用程序运行时压缩内存来专注于低暂停时间(通常为几毫秒)。
代码示例:对象如何变成垃圾
这是一个简单的示例,演示对象如何变得无法访问并有资格进行垃圾回收:
public class garbagecollectiondemo { public static void main(string[] args) { // object 1 is created person person1 = new person("alice"); // object 2 is created person person2 = new person("bob"); // reference of person1 is reassigned person1 = person2; // object "alice" is now unreachable // both person1 and person2 point to "bob" system.out.println(person1.name); // output: bob // at this point, "alice" object is eligible for garbage collection } } class person { string name; person(string name) { this.name = name; } }
登录后复制
说明:
- 当 person1 重新分配给 person2 时,对象“alice”变得不可访问。
- 垃圾收集器现在可以回收“alice”占用的内存。
如何触发垃圾收集
虽然您无法强制进行垃圾回收,但您可以使用以下方式向 jvm 建议:
system.gc();
登录后复制
为什么要避免显式调用?
- 垃圾收集是自动进行的,并由 jvm 进行优化。
- 调用 system.gc() 可能会破坏垃圾收集器的效率。
常见的垃圾收集问题
- 内存泄漏
当对象保持不必要的引用时发生,从而阻止垃圾收集。
示例:
import java.util.arraylist; import java.util.list; public class memoryleakexample { static list<object> list = new arraylist<>(); public static void main(string[] args) { for (int i = 0; i < 10000; i++) { list.add(new object()); // keeps adding objects to the static list } } }
登录后复制
这里,列表保留了对所有对象的引用,使它们不符合gc。
- 内存不足错误
堆已满且无法回收内存时发生。
解决方案:
使用 jvm 选项增加堆大小:
java -xmx1024m myapp
登录后复制
垃圾收集的最佳实践
- 避免不必要的对象创建:
- 尽可能重用对象。
- 使用轻量级对象来存储短期数据。
- 取消未使用的引用:
- 当不再需要引用时将其设置为 null。
myObject = null;
登录后复制
- 明智地使用集合:
- 从列表或地图等集合中删除未使用的元素。
- 监控垃圾收集:
- 使用 jconsole 或 visualvm 等工具来监控 gc 活动。
- 选择合适的垃圾收集器:
- 使用 jvm 选项根据应用程序需求定制 gc(例如,对于 g1 收集器,-xx: useg1gc)。
要点
- 自动内存管理: 垃圾收集是 java 中的一种内置机制,可以简化内存管理。
- 世代模型: gc 通过不同方式处理短寿命和长寿命对象。
- 选择正确的 gc 算法: 将垃圾收集器与您的应用程序对吞吐量或低延迟的需求相匹配。
- 监控和优化: 定期分析堆使用情况和垃圾收集指标,以防止与内存相关的问题。
通过有效地理解和利用 java 的垃圾收集,您可以确保您的应用程序高效运行,并且不会出现内存泄漏和性能瓶颈。
以上就是了解 Java 中的垃圾收集:什么、为什么以及如何的详细内容,更多请关注其它相关文章!