作为Java开发人员,我们对垃圾收集的概念并不陌生。我们的应用程序一直在产生垃圾,这些垃圾被CMS、G1、Azul C4 和其他类型的收集器精心清理。基本上,我们的应用程序生来就是为了给这个世界带来价值,但是,没有什么是完美的——包括我们在Java堆中留下垃圾的应用程序。

然而,故事并没有以Java堆结束。事实上,它只是从那里开始。让我们以一个基本的Java应用程序为例,该应用程序使用PostgreSQL等关系数据库和固态驱动器 (SSD) 作为存储设备。从这里,我们将探索我们的应用程序如何在Java运行时边界之外生成垃圾。

用Dead Tuples填充 PostgreSQL

当你的Java应用程序对 PostgreSQL数据库执行DELETE或UPDATE语句时,不会立即删除已删除的记录,也不会在其位置更新现有记录。相反,删除的记录被标记为Dead Tuples并将保留在存储中。更新的记录实际上是PostgreSQL通过复制先前版本的记录并更新请求的列来插入的全新记录。该更新记录的先前版本被视为已删除,并且与DELETE操作一样,被标记Dead Tuples。

数据库引擎在其存储中保留旧版本的已删除和更新记录是有充分理由的。对于初学者,你的应用程序可以针对PostgreSQL并行运行一堆事务。其中一些交易确实比其他交易更早开始。但是,如果一个事务删除了一条记录,而该记录可能对一些较早开始的事务仍然感兴趣,那么该记录需要保存在数据库中(至少直到所有较早开始的事务完成的时间点)。这就是 PostgreSQL实现MVCC(多版本并发协议)的方式。

很明显,PostgreSQL不能也不想永远保留Dead Tuples。这就是为什么数据库有自己的垃圾收集过程,称为清理。有两种类型的VACUUM——普通的和完整的。普通的VACUUM与你的应用程序工作负载并行工作,不会阻止你的查询。这种类型的清理将Dead Tuples占用的空间标记为空闲,使其可用于你的应用稍后将添加到同一个表中的新数据。普通的VACUUM不会将空间返回给操作系统,以便它可以被其他表或 第三方应用程序重用(在某些极端情况下,当页面仅包含Dead Tuples并且页面位于表的末尾时)。

相比之下,完整的VACUUM确实将可用空间回收给操作系统,但它会阻止应用程序工作负载。你可以将其视为Java的“stop-the-world”垃圾收集暂停。只有在PostgreSQL中,这样的暂停才能持续数小时(或数天)。因此,数据库管理员尽最大努力防止完全VACUUM发生。

在SSD中生成陈旧数据

如果你认为垃圾收集只是为了软件,那就错了!一些硬件设备也需要执行垃圾收集例程。SSD一直在做垃圾收集!

  每当你的Java应用程序删除或更新磁盘上的任何数据时——通过上面讨论的PostgreSQL或直接通过Java文件 API——应用程序就会在SSD上生成垃圾。

SSD将数据存储在页面中(通常大小在4KB和16KB之间),后者按块分组。虽然你的数据可以在页面级别写入或读取,但陈旧(已删除)的数据只能在块级别擦除。擦除需要比读/写操作更多的电压,并且很难在不影响相邻单元的情况下将电压定位在页面级别。

因此,如果你的Java应用程序更新了文件,那么事实上,更新的段将被写入可能位于不同块中的空页面。带有旧数据的段将被标记为过时,稍后将被垃圾收集。首先,SSD中的垃圾收集器遍历具有陈旧数据的页面块,并将好的数据移动到其他块(类似于Java的G1收集器中的压缩阶段)。其次,收集器擦除只剩下陈旧数据的块,并使这些块可用于未来的数据。

好奇SSD制造商如何防止或尽量减少“stop-the-world”暂停的次数? 有一个SSD过度配置的概念,即每台设备都有一个额外的空间供你的应用程序使用。该空间是一种安全缓冲区,允许应用程序继续写入或修改数据,同时垃圾收集器同时擦除陈旧数据。

总结

严肃地说,垃圾收集是一种广泛使用的技术,其使用范围远远超出Java生态系统。如果实施得当,垃圾收集可以在不影响性能的情况下简化软件和硬件的架构。Java、PostgreSQL和SSD都是成功利用gar的产品的好例子。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部