对象究竟是如何从堆中移除的?
How exactly are objects removed from the heap?
我正在学习 Java 和所有不同世代的垃圾收集。我现在知道,当一个对象在运行之后没有被标记时,它就被清除了。不过我很好奇:那个清扫过程到底是什么?
我的理解是它从堆中移除死对象并且堆存储在 RAM 中,但是移除究竟是如何工作的? OS 是否公开了从 RAM 中删除特定块中的数据的方法?
我猜它的作用相当于 C 中的 free() 函数,所以我想这是一个关于该函数究竟做了什么的问题。
JVM 具有用于 GC 系统的可插入架构。因此,JVM 实际执行的操作完全取决于您插入的 GC 引擎。在 SO post 中解释所有内容的细节有点多,但基本要点:
JVM 很少调用 free 或 malloc。 JVM 是它自己的小系统,它 mallocs 一个 boatload 然后将保留它,通常永远(java 主要是为服务器设计的,并假设它得到的任何东西都是为它保留的,并且不会被其他应用程序需要基础。如果不在服务器上,它会尝试很好地发挥作用,但这不是 GC 的优化目标) - 如果您的 VM 在某个时候创建了 2GB 的东西,后来几乎所有这些东西都可以被 GC,它将是* ,但 VM 保留了从 OS 获得的 2GB。理论是:嘿,我不得不在我生命的早期处理价值 2GB 的对象,我是一名服务器,很可能我会被要求做一份工作,导致我不得不尽快再次处理 2GB,而不是稍后,和 free/malloc 需要时间,所以为什么要费心呢?我只为自己保留 2GB 的页面,因为我知道可以免费使用它来写入堆内容。
GC 倾向于在 'pages' 中工作 - 每个新创建的对象都在页面上的下一个可用字节串处。一旦整个页面已满,它会记住哪些对象离开了 'eden'(如果你在单个方法的范围内创建一个对象,并且该对象永远不会分配给任何地方的任何字段,那么方法现在已经完成了,也就是说对象也一定完成了。一般对于这个'first generation'(对象创建后的第一次,页面已满和垃圾回收是相关的),JVM 以相反的方式工作:它知道哪些对象尚未完成并将这些对象复制到新页面,全部在该新页面的 'the front' 处,然后只考虑整个页面现在是空的。在从这个意义上说,java 可以以零成本快速收集垃圾,并且 JVM 优于基于 malloc 和 free 的 C 代码,因为这个。这也意味着你应该做很多的垃圾。最好有一个通常不可变的类型,你可以不断地创建新的实例,而不是拥有一个寿命更长的实例。包含寿命短的垃圾通常是免费的。
对于下一代(随着对象的存在,它们会不断被推高 'generation',连续的几代将被单独放置并且在更长的时间内未被收集。要点是:一个对象存活的时间越长,降低它现在符合条件的机会。存活时间长的对象(不符合收集条件)往往继续不符合条件。除了第一代,GC 倾向于以 'positive mark and sweep' 方式工作,从所有 'alive' 对象开始,并制作一个扩展的对象图,您可以通过它们到达这些对象,从而使这些对象也 'alive'。但是一旦识别出垃圾,原理是一样的:新建一个页面,将非垃圾的复制过来,然后将旧页面标记为空闲,而不用零覆盖,因为不需要(java不允许指针运算,因此没有代码看到页面中未覆盖的字节的风险。
如果您知道过去的硬盘碎片整理程序是如何工作的 - 就是这样。
但请记住:这是一个过于简化的视图,它从各种 GC impl 中挑选了一些细节。 Java VM 具有可插拔内存架构,GC 的实际工作方式存在广泛差异。此处命名的技术(copy-alive-out-then-reuse-page、free-collect-fast-garbage、generational garbage collection 和 mark-and-sweep)并未被 JVM 可用的每个 GC 系统普遍使用。
*) 快速垃圾,如果你的 GC 是分代的,内置了 'eden' 生成系统,通常总是接近连续应用,但是一旦你从伊甸园一代继续前进,不要假设那些垃圾今天会被收集起来。如果 JVM 有足够的剩余堆,那么此时绝对没有理由在收集上花费周期。符合 GC 条件的东西在堆中停留 DAYS 是完全可行的,因为无论如何服务器大部分时间都处于空闲状态,并且生成的所有对象都是快速垃圾并且不会超过 eden gen。这是为什么将 OS 的 VM 占用内存指示的输出或 VM 自己的内存可用性报告视为特别有用的原因是不合适的,也是为什么您应该这样做的原因之一绝对不要写终结器。假设无论您需要终结器做什么,都不会及时清理垃圾。
我正在学习 Java 和所有不同世代的垃圾收集。我现在知道,当一个对象在运行之后没有被标记时,它就被清除了。不过我很好奇:那个清扫过程到底是什么?
我的理解是它从堆中移除死对象并且堆存储在 RAM 中,但是移除究竟是如何工作的? OS 是否公开了从 RAM 中删除特定块中的数据的方法?
我猜它的作用相当于 C 中的 free() 函数,所以我想这是一个关于该函数究竟做了什么的问题。
JVM 具有用于 GC 系统的可插入架构。因此,JVM 实际执行的操作完全取决于您插入的 GC 引擎。在 SO post 中解释所有内容的细节有点多,但基本要点:
JVM 很少调用 free 或 malloc。 JVM 是它自己的小系统,它 mallocs 一个 boatload 然后将保留它,通常永远(java 主要是为服务器设计的,并假设它得到的任何东西都是为它保留的,并且不会被其他应用程序需要基础。如果不在服务器上,它会尝试很好地发挥作用,但这不是 GC 的优化目标) - 如果您的 VM 在某个时候创建了 2GB 的东西,后来几乎所有这些东西都可以被 GC,它将是* ,但 VM 保留了从 OS 获得的 2GB。理论是:嘿,我不得不在我生命的早期处理价值 2GB 的对象,我是一名服务器,很可能我会被要求做一份工作,导致我不得不尽快再次处理 2GB,而不是稍后,和 free/malloc 需要时间,所以为什么要费心呢?我只为自己保留 2GB 的页面,因为我知道可以免费使用它来写入堆内容。
GC 倾向于在 'pages' 中工作 - 每个新创建的对象都在页面上的下一个可用字节串处。一旦整个页面已满,它会记住哪些对象离开了 'eden'(如果你在单个方法的范围内创建一个对象,并且该对象永远不会分配给任何地方的任何字段,那么方法现在已经完成了,也就是说对象也一定完成了。一般对于这个'first generation'(对象创建后的第一次,页面已满和垃圾回收是相关的),JVM 以相反的方式工作:它知道哪些对象尚未完成并将这些对象复制到新页面,全部在该新页面的 'the front' 处,然后只考虑整个页面现在是空的。在从这个意义上说,java 可以以零成本快速收集垃圾,并且 JVM 优于基于 malloc 和 free 的 C 代码,因为这个。这也意味着你应该做很多的垃圾。最好有一个通常不可变的类型,你可以不断地创建新的实例,而不是拥有一个寿命更长的实例。包含寿命短的垃圾通常是免费的。
对于下一代(随着对象的存在,它们会不断被推高 'generation',连续的几代将被单独放置并且在更长的时间内未被收集。要点是:一个对象存活的时间越长,降低它现在符合条件的机会。存活时间长的对象(不符合收集条件)往往继续不符合条件。除了第一代,GC 倾向于以 'positive mark and sweep' 方式工作,从所有 'alive' 对象开始,并制作一个扩展的对象图,您可以通过它们到达这些对象,从而使这些对象也 'alive'。但是一旦识别出垃圾,原理是一样的:新建一个页面,将非垃圾的复制过来,然后将旧页面标记为空闲,而不用零覆盖,因为不需要(java不允许指针运算,因此没有代码看到页面中未覆盖的字节的风险。
如果您知道过去的硬盘碎片整理程序是如何工作的 - 就是这样。
但请记住:这是一个过于简化的视图,它从各种 GC impl 中挑选了一些细节。 Java VM 具有可插拔内存架构,GC 的实际工作方式存在广泛差异。此处命名的技术(copy-alive-out-then-reuse-page、free-collect-fast-garbage、generational garbage collection 和 mark-and-sweep)并未被 JVM 可用的每个 GC 系统普遍使用。
*) 快速垃圾,如果你的 GC 是分代的,内置了 'eden' 生成系统,通常总是接近连续应用,但是一旦你从伊甸园一代继续前进,不要假设那些垃圾今天会被收集起来。如果 JVM 有足够的剩余堆,那么此时绝对没有理由在收集上花费周期。符合 GC 条件的东西在堆中停留 DAYS 是完全可行的,因为无论如何服务器大部分时间都处于空闲状态,并且生成的所有对象都是快速垃圾并且不会超过 eden gen。这是为什么将 OS 的 VM 占用内存指示的输出或 VM 自己的内存可用性报告视为特别有用的原因是不合适的,也是为什么您应该这样做的原因之一绝对不要写终结器。假设无论您需要终结器做什么,都不会及时清理垃圾。