Java内存区域和垃圾收集器

Java内存区域和垃圾收集器,java,garbage-collection,Java,Garbage Collection,我昨天读到了关于垃圾收集的文章,但我不理解其中的一些概念。我读过,对于次要收集,通常使用复制技术将可到达的对象移动到幸存者空间,而对于主要收集,通常使用标记和扫描 我不明白的第一件事是垃圾收集器从gc根开始,沿着图移动以检测活动实例,但它如何知道哪个对象是年轻的,哪个对象是旧的?它如何知道对象所在的内存区域 第二件事是,如果我们只进行小的收集,gc如何知道年轻一代中的对象不是由老一代中的对象引用的,还是由方法区域中的静态引用引用的 最后一件事是,在标记和扫掠之后,有时会进行压实。gc如何知道对移

我昨天读到了关于垃圾收集的文章,但我不理解其中的一些概念。我读过,对于次要收集,通常使用复制技术将可到达的对象移动到幸存者空间,而对于主要收集,通常使用标记和扫描

我不明白的第一件事是垃圾收集器从gc根开始,沿着图移动以检测活动实例,但它如何知道哪个对象是年轻的,哪个对象是旧的?它如何知道对象所在的内存区域

第二件事是,如果我们只进行小的收集,gc如何知道年轻一代中的对象不是由老一代中的对象引用的,还是由方法区域中的静态引用引用的

最后一件事是,在标记和扫掠之后,有时会进行压实。gc如何知道对移动对象的哪些引用必须更新?如果我们有一个程序,有数千个线程,使用巨大的帧堆栈和千兆字节的堆?它是否有一些内部结构,以地图或其他形式包含这些信息

谢谢

  • 每次GC检查一个对象是否可以被收集时,它都会在该对象中增加一个计数器,告诉它该对象被GC访问了多少次。这是判断何时需要将对象从年轻一代移动到终身一代的最简单方法。但要知道对象所在的区域,可以使用对象的地址来计算它。(例如,JVM只会说0-100之间的内存地址是年轻一代,101+是终身一代。)
  • 即使在较小的收集过程中,也会遍历整个对象图以检查引用。至少理论上是这样,在实践中会有一些优化。但总的想法是这样的
  • 因为第一步是构建一个引用图,所以在压缩阶段仍然可以获得该信息。但是,压缩千兆字节大小的堆需要很长时间

  • 答案取决于GC算法,因此您将在答案中发现一些变化

    GC如何知道哪个对象是年轻的,哪个对象是旧的?它如何知道对象所在的内存区域

    对象的内存地址将回答这个问题,因为每个区域的位置趋于固定。调整大小是可能的,但只有当JVM暂停所有工作线程时才会发生。还请记住,G1收集器没有代,因此答案会因所使用的算法而异

    gc如何知道年轻一代中的对象是不是被旧一代中的对象引用,还是被方法区域中的静态引用引用

    一些GC算法将对堆中的所有对象执行完全扫描。其他人则依赖于这样的观察:从老代到年轻代的ref数量相对较低,因此JVM会跟踪它们,并将它们用作根来进行标记。其机制通常是一个记分卡系统,这就是为什么一些GC算法具有非常大的祖先空间,会减慢年轻空间的GC的速度。因为每一个物体的每一张记分卡都必须被检查,看它是否有一个指向年轻一代的指针

    最后一件事是,在标记和扫掠之后,有时会进行压实。gc如何知道对移动对象的哪些引用必须更新

    同样,答案也会有所不同,因为JVM可以改变这些细节。有些算法使用双间接寻址,因此指针很容易定位和更新。这涉及到为每个对象存储一个大索引。然而,当GC没有运行时,这会减慢用户代码的速度,因为运行的代码必须不断查找对象的实际位置,所以一些GC算法确实会跟踪引用


    Azul使用了一种非常聪明的机制,使内存页无效,并存储在陷阱处理程序代码中访问的重定向映射。因此,它只需要存储已移动对象的地址。而其他GC算法在扫描活动对象时跟踪信息。毕竟,我们不需要死堆的信息。

    YoungGen/OldGen和PermGen是Java堆所在的三个区域。JVM非常熟悉每种语言的边界

  • 在收集垃圾时,GC首先决定运行哪个空间,然后标识该空间中每个对象的GC根。对象是年轻的还是旧的由它在堆中所处的空间指定。GC也会保持每个对象的状态,就像每个对象存活了多少GC循环一样,这使GC可以估计将对象从年轻移动到老年的时间是否合适

  • 对象图决定年轻一代对象是否由老一代引用引用

  • 如果压缩对象,则也会更新参照。当对象从Young>Old Gen移动时也会发生这种情况


  • 谢谢@Geek!还有一件事,GC是如何执行小收集的?它是否也适用于采摘GC根?我知道在小集合中,你们不想触碰老一代中的那个些,所以GC如何决定应该在小集合中处理哪些GC根呢?谢谢Chris!我可以请你检查一下我在极客答案评论中的问题吗?@alobodzk如果我们选择一个算法,我们可以更具体。所以,让我们来看看CMS,Oracle Hotspot的并发标记和扫描的实现。他们年轻的GC使用通常的根,即线程、本机调用等。对于老一代到年轻一代的部分,它们有一个大的位数组,与老一代中的每个内存地址对齐。每个位表示,可能是指向年轻一代的指针(但可能是误报)。年轻一代扫描了这一点,并从中扎根。这些被称为“卡片表”。一些关于卡片表的详细信息可以在这里阅读:谢谢!你提到