编译原理作业10

Surf the Internet and write a short paper to compare GC on Java platform with GC on Microsoft .Net platform.

1.垃圾回收的定义

当一个电脑上的动态内存不再需要时,就应该予以释放,以让出内存,这种内存资源管理,称为垃圾回收(garbage collection),简称 GC。

2. Java的垃圾回收机制

  • Java的内存分配机制

Java的内存由JVM自动管理,也是通过JVM做垃圾回收的。

在Java程序运行过程中,程序计数器、虚拟栈区和本地方法栈会随着线程的结束而被释放,所以这部分的内存分配和回收不需要动态考虑,因为方法结束或者线程结束时,内存自然就跟随着回收了。需要考虑的是堆区,因为我们只有在运行时才能知道程序会创建多少对象,需要多少空间,何时释放不产生影响,垃圾回收机制主要解决的是这部分的问题。

  • 判断需要回收的对象

    1.引用计数法

    给对象添加一引用计数器,被引用一次计数器值就加 1;当引用失效时,计数器值就减 1;计数器为 0 时,对象就是不可能再被使用的,简单高效,缺点是无法解决对象之间相互循环引用的问题。

    2.根搜索算法

    因为引用计数法无法解决对象之间循环引用的问题,所以大多数情况下会使用根搜索算法来实现垃圾回收。

    这种方法通过一系列的称为 “GC Roots” 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的。此算法解决了上述循环引用的问题。

    在Java语言中,可作为 GC Roots 的对象包括下面几种:
    a. 虚拟机栈(栈帧中的本地变量表)中引用的对象。
    b. 方法区中类静态属性引用的对象。
    c. 方法区中常量引用的对象。
    d. 本地方法栈中 JNI(Native方法)引用的对象

    不过由于Java GC管理的是堆区,所以只有堆区内引用的对象作为GC Roots才会被回收。

  • 不同强度的引用

    Java在刚刚出现的时候只有被引用和未被引用两种状态,后来进行了补充,分为4种类型的引用:强引用、软引用、弱引用和虚引用,这 4 种引用强度依次逐渐减弱。如下图

​ 强引用:类似”Object obj=new Object()”这类的引用,垃圾收集器永远不会回收存活的强引用对象。

​ 软引用:如果一个对象只具有软引用,那么它的性质属于可有可无的那种。如果此时内存空间足够,垃圾回收 器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可 以被程序使用。

​ 弱引用:也是用来描述非必需对象的,被弱引用关联的对象 只能生存到下一次垃圾收集发生之前 。当垃圾收集 器工作时,无论内存是否足够,都会回收掉只被弱引用关联的对象。

​ 虚引用是最弱的一种引用关系。无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的 就是能在这个对象被收集器回收时收到一个系统通知。

  • 堆内存的分类

    根据对象的存活率(年龄),Java对内存划分为3种:新生代、老年代、永久代

    新生代主要用来存放新生的对象。一般占据堆空间的1/3;老年代主要存放应用中生命周期长的内存对象,而永久代指的是永久保存区域,主要存放Class和Meta(元数据)的信息。

  • 垃圾回收算法

    1. 标记-清除算法

      首先标记出所有需要回收的对象,然后标记完成后统一回收所有被标记的对象。

      主要的问题一个是效率较低,另外一个是回收后的可用内存不是连续的,会有较多碎片。

    2. 复制算法

      这个方法用来解决效率问题较低的问题,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

      只是代价是只能使用50%的内存。

    3. 标记整理算法

      为了解决复制算法只能利用50%内存的问题,可以使用该算法。标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,“标记-整理”算法的示意图如下:

    4. 分代收集算法

      当前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,根据对象存活周期的不同将内存划分为几块并采用不用的垃圾收集算法。

      一般是把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。

  • 垃圾收集器

    垃圾收集器实现了内存回收算法。主要分为以下几类:

    • Serial收集器
    • ParNew收集器
    • Serial Old 收集器

    • Parallel Old收集器

    • CMS收集器

    • G1收集器

3. Microsoft .Net的垃圾回收机制

微软Microsoft .Net的文档写道:.NET 的垃圾回收器管理应用程序的内存分配和释放。 每当有对象新建时,公共语言运行时都会从托管堆为对象分配内存。 只要托管堆中有地址空间,运行时就会继续为新对象分配空间。 不过,内存并不是无限的。 垃圾回收器最终必须执行垃圾回收来释放一些内存。 垃圾回收器的优化引擎会根据所执行的分配来确定执行回收的最佳时机。 执行回收时,垃圾回收器会在托管堆中检查应用程序不再使用的对象,然后执行必要的操作来回收其内存。

  • 垃圾回收的条件

    当满足以下条件之一时将发生垃圾回收:

    • 系统具有低的物理内存。 这是通过 OS 的内存不足通知或主机指示的内存不足检测出来。
  • 由托管堆上已分配的对象使用的内存超出了可接受的阈值。 随着进程的运行,此阈值会不断地进行调整。

  • 调用 GC.Collect 方法。 几乎在所有情况下,你都不必调用此方法,因为垃圾回收器会持续运行。 此方法主要用于特殊情况和测试。

  • 托管堆

    在垃圾回收器由 CLR 初始化之后,它会分配一段内存用于存储和管理对象。 此内存称为托管堆(与操作系统中的本机堆相对)。每个托管进程都有一个托管堆。 进程中的所有线程都在同一堆上为对象分配内存。

  • 托管堆分代

    为优化垃圾回收器的性能,将托管堆分为三代:第 0 代、第 1 代和第 2 代,因此它可以单独处理长生存期和短生存期对象。 垃圾回收器将新对象存储在第 0 代中。 在应用程序生存期的早期创建的对象如果未被回收,则被升级并存储在第 1 级和第 2 级中。 因为压缩托管堆的一部分要比压缩整个托管堆速度快,所以此方案允许垃圾回收器在每次执行回收时释放特定级别的内存,而不是整个托管堆的内存。

  • 幸存和提升

    垃圾回收中未回收的对象也称为幸存者,并会被提升到下一代:

    • 第 0 代垃圾回收中未被回收的对象将会升级至第 1 代。
    • 第 1 代垃圾回收中未被回收的对象将会升级至第 2 代。
    • 第 2 代垃圾回收中未被回收的对象将仍保留在第 2 代。

    当垃圾回收器检测到某个代中的幸存率很高时,它会增加该代的分配阈值。 下次回收将回收非常大的内存。 CLR 持续在以下两个优先级之间进行平衡:不允许通过延迟垃圾回收,让应用程序的工作集获取太大内存,以及不允许垃圾回收过于频繁地运行。

  • 垃圾回收过程中发生的情况

    垃圾回收分为以下几个阶段:

    • 标记阶段,找到并创建所有活动对象的列表。
    • 重定位阶段,用于更新对将要压缩的对象的引用。
    • 压缩阶段,用于回收由死对象占用的空间,并压缩幸存的对象。 压缩阶段将垃圾回收中幸存下来的对象移至段中时间较早的一端。

    在垃圾回收启动之前,除了触发垃圾回收的线程以外的所有托管线程均会挂起,如下图:

  • 非托管资源

    对于应用程序创建的大多数对象,可以依赖垃圾回收自动执行必要的内存管理任务。 但是,非托管资源需要显式清除。 最常用的非托管资源类型是包装操作系统资源的对象,例如,文件句柄、窗口句柄或网络连接。 虽然垃圾回收器可以跟踪封装非托管资源的托管对象的生存期,但却无法具体了解如何清理资源。所以需要显式清除。

参考

https://www.cnblogs.com/czwbig/p/11127159.html