为什么非静态字段不能充当GC根?

2024-02-17

据我所知,静态字段(以及线程、局部变量和方法参数、JNI 引用)充当 GC 根。

我无法提供可以证实这一点的链接,但我已经阅读了很多相关文章。

为什么非静态字段不能充当GC根?


首先,我们需要确保我们对于跟踪垃圾收集算法在标记阶段的作用达成共识。

在任何给定时刻,跟踪 GC 都有许多已知处于活动状态的对象,即当前正在运行的程序可以访问它们。标记短语的主要步骤涉及跟踪这些对象的非静态字段以找到更多对象,并且这些新对象现在也将被认为是活动的。递归地重复该步骤,直到遍历现有的存活对象没有找到新的存活对象为止。内存中所有未证明存活的对象都被视为死亡。 (GC 然后进入下一个阶段,称为扫描阶段。对于这个答案,我们不关心该阶段。)

现在仅此还不足以执行算法。一开始,算法没有已知存在的对象,因此它无法开始跟踪任何人的非静态字段。我们需要指定一组从一开始就被认为是活动的对象。我们公理地选择这些对象,因为它们不是来自算法的前一步——它们来自外部。具体来说,它们来自语言的语义。这些对象称为根。

在像 Java 这样的语言中,有两组对象是明确的 GC 根。仍然在作用域内的局部变量可以访问的任何内容显然都是可以访问的(在其方法内,该方法仍然没有返回),因此它是活动的,因此它是根。通过类的静态字段可访问的任何内容显然也可以(从任何地方)访问,因此它是活动的,因此它是根。

但如果非静态字段也被视为根,会发生什么?

假设你实例化一个ArrayList<E>。在内部,该对象有一个非静态字段,指向Object[](表示列表存储的支持数组)。在某个时刻,GC 循环开始。在标记阶段,Object[]被标记为活动的,因为它被指向ArrayList<E>私有非静态字段。这ArrayList<E>没有被任何东西指向,所以它不能被认为是活着的。因此,在这个循环中,ArrayList<E>当支持物被破坏时Object[]幸存下来。当然,在下一个周期,Object[]也会死亡,因为任何根都无法访问它。但为什么要分两个周期进行呢?如果ArrayList<E>在第一个周期就死了,如果Object[]仅由死对象使用,不应该Object[]也算是死在同一个举动,节省时间和空间?

这就是重点。如果我们想要获得最大效率(在跟踪 GC 的上下文中),我们需要在单个 GC 中删除尽可能多的死对象。

为此,非静态字段应仅在封闭对象(包含该字段的对象)时使对象保持活动状态已被证明还活着。相比之下,根是我们公理地称为“活着”的对象(无需证明)以便启动算法的标记阶段。将后一类限制在不破坏正在运行的程序的最低限度符合我们的最佳利益。

例如,假设您有以下代码:

class Foo {
    Bar bar = new Bar();

    public static void main(String[] args) {
        Foo foo = new Foo();
        System.gc();
    }

    public void test() {
        Integer a = 1;
        bar.counter++; //access to the non-static field
    }
}

class Bar {
    int counter = 0;
}
  • 当垃圾收集开始时,我们得到一个根,即局部变量Foo foo。就是这样,这是我们唯一的根。
  • 我们沿着根找到 的实例Foo,它被标记为活动的,然后我们尝试找到它的非静态字段。我们找到其中之一,即Bar bar field.
  • 我们按照字段查找实例Bar,它被标记为活动的,然后我们尝试找到它的非静态字段。我们发现它不再包含引用类型的字段,因此 GC 不再需要为该对象操心。
  • 由于在这一轮递归中我们无法找到新的存活对象,因此标记阶段可以结束。

或者:

class Foo {
    Bar bar = new Bar();

    public static void main(String[] args) {
        Foo foo = new Foo();
        foo.test();
    }

    public void test() {
        Integer a = 1;
        bar.counter++; //access to the non-static field
        System.gc();
    }
}

class Bar {
    int counter = 0;
}
  • 当垃圾收集开始时,局部变量Integer a是一个根并且Foo this引用(所有非静态方法获取的隐式引用)也是根。局部变量Foo foo from main也是一个根,因为main还没有超出范围。
  • 我们沿着根找到 的实例Integer和实例Foo(我们两次找到其中一个对象,但这对算法来说并不重要),它们被标记为活动的,然后我们尝试跟踪它们的非静态字段。比如说下面的例子Integer没有更多的类实例字段。的实例Foo给我们一个Bar field.
  • 我们沿着字段查找实例Bar,它被标记为活动的,然后我们尝试找到它的非静态字段。我们发现它不再包含引用类型的字段,因此 GC 不再需要为该对象操心。
  • 由于在这一轮递归中我们无法找到新的存活对象,因此标记阶段可以结束。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么非静态字段不能充当GC根? 的相关文章

  • Java - 因内存不足错误而关闭

    关于如何最好地处理这个问题 我听到了非常矛盾的事情 并且陷入了以下困境 OOME 会导致一个线程崩溃 但不会导致整个应用程序崩溃 我需要关闭整个应用程序 但不能 因为线程没有剩余内存 我一直认为最佳实践是让它们离开 这样 JVM 就会死掉
  • 如何使用Spring WebClient进行同步调用?

    Spring Framework in 休息模板 https docs spring io spring framework docs current javadoc api org springframework web client R
  • 带有 Android 支持库 v7 的 Maven Android 插件

    我使用 maven android plugin 构建我的 android 应用程序 它依赖于 android 支持库 v4 和 v7 由于我没有找到如何从developer android com下载整个sdk 因此我无法使用maven
  • 使用 WebDriver 单击新打开的选项卡中的链接

    有人可以在这种情况下帮助我吗 场景是 有一个网页 我仅在新选项卡中打开所有指定的链接 现在我尝试单击新打开的选项卡中的任何一个链接 在下面尝试过 但它仅单击主 第一个选项卡中的一个链接 而不是在新选项卡中 new Actions drive
  • 当路径的点超出视野时,Android Canvas 不会绘制路径

    我在绘制路径时遇到了 Android Canvas 的一些问题 我的情况是 我有一个相对布局工作 如地图视图 不使用 google api 或类似的东西 我必须在该视图上绘制一条路径 canvas drawPath polyPath bor
  • 如何强制jar使用(或jar运行的jvm)utf-8而不是系统的默认编码

    我的Windows默认编码是GBK 而我的Eclipse完全是utf 8编码 因此 在我的 Eclipse 中运行良好的应用程序崩溃了 因为导出为 jar 文件时这些单词变得不可读 我必须在 bat 文件中写入以下行才能运行该应用程序 st
  • 如何使用 JAVA 代码以编程方式捕获线程转储?

    我想通过 java 代码生成线程转储 我尝试使用 ThreadMXBean 为此 但我没有以正确的格式获得线程转储 因为我们正在使用jstack命令 请任何人提供一些帮助 他们是否有其他方式获取线程转储 使用任何其他 API 我想要的线程转
  • Android蓝牙java.io.IOException:bt套接字已关闭,读取返回:-1

    我正在尝试编写一个代码 仅连接到运行 Android 5 0 KitKat 的设备上的 目前 唯一配对的设备 无论我尝试了多少方法 我仍然会收到此错误 这是我尝试过的最后一个代码 它似乎完成了我看到人们报告为成功的所有事情 有人能指出我做错
  • 在 MongoDB 和 Apache Solr 之间同步数据的简单方法

    我最近开始使用 MongoDB 和 Apache Solr 我使用 MongoDB 作为数据存储 并且希望 Apache Solr 为我的数据创建索引 以实现应用程序中的搜索功能 经过一些研究 我发现 基本上有两种方法可以在 MongoDB
  • Java:从集合中获取第一项

    如果我有一个集合 例如Collection
  • 在 Java 中通过 XSLT 分解 XML

    我需要转换具有嵌套 分层 表单结构的大型 XML 文件
  • Java、Spring:使用 Mockito 测试 DAO 的 DataAccessException

    我正在尝试增加测试覆盖率 所以我想知道 您将如何测试 DAO 中抛出的 DataAccessExceptions 例如在一个简单的 findAll 方法中 该方法仅返回数据源中的所有数据 就我而言 我使用 Spring JdbcTempla
  • 用于缓存的 Servlet 过滤器

    我正在创建一个用于缓存的 servlet 过滤器 这个想法是将响应主体缓存到memcached 响应正文由以下方式生成 结果是一个字符串 response getWriter print result 我的问题是 由于响应正文将不加修改地放
  • 如何从日期中删除毫秒、秒、分钟和小时[重复]

    这个问题在这里已经有答案了 我遇到了一个问题 我想比较两个日期 然而 我只想比较年 月 日 这就是我能想到的 private Date trim Date date Calendar calendar Calendar getInstanc
  • IntelliJ 组织导入

    IntelliJ 是否具有类似于 Eclipse 中的组织导入功能 我拥有的是一个 Java 文件 其中多个类缺少导入 例子 package com test public class Foo public Map map public J
  • 如何从 Ant 启动聚合 jetty-server JAR?

    背景 免责声明 I have veryJava 经验很少 我们之前在 Ant 构建期间使用了 Jetty 6 的包装版本来处理按需静态内容 JS CSS 图像 HTML 因此我们可以使用 PhantomJS 针对 HTTP 托管环境运行单元
  • 禁用 Android 菜单组

    我尝试使用以下代码禁用菜单组 但它不起作用 菜单项仍然启用 你能告诉我出了什么问题吗 资源 菜单 menu xml menu menu
  • 替换文件中的字符串

    我正在寻找一种方法来替换文件中的字符串而不将整个文件读入内存 通常我会使用 Reader 和 Writer 即如下所示 public static void replace String oldstring String newstring
  • 何时在 hibernate 中使用 DiscriminatorValue 注解

    在 hibernate 中使用 DiscriminatorValue 注释的最佳场景是什么以及何时 这两个链接最能帮助我理解继承概念 http docs oracle com javaee 6 tutorial doc bnbqn html
  • 检查应用程序是否在 Android Market 上可用

    给定 Android 应用程序 ID 包名称 如何以编程方式检查该应用程序是否在 Android Market 上可用 例如 com rovio angrybirds 可用 而 com random app ibuilt 不可用 我计划从

随机推荐