当前位置: 首页 > 编程笔记 >

浅谈Java垃圾回收的实现过程

薛烈
2023-03-14
本文向大家介绍浅谈Java垃圾回收的实现过程,包括了浅谈Java垃圾回收的实现过程的使用技巧和注意事项,需要的朋友参考一下

教程是为了理解基本的Java垃圾回收以及它是如何工作的。这是垃圾回收教程系列的第二部分。希望你已经读过了第一部分:《简单介绍Java垃圾回收机制》。

Java垃圾回收是一项自动化的过程,用来管理程序所使用的运行时内存。通过这一自动化过程,JVM解除了程序员在程序中分配和释放内存资源的开销。

启动Java垃圾回收

作为一个自动的过程,程序员不需要在代码中显示地启动垃圾回收过程。System.gc()和Runtime.gc()用来请求JVM启动垃圾回收。

虽然这个请求机制提供给程序员一个启动GC过程的机会,但是启动由JVM负责。JVM可以拒绝这个请求,所以并不保证这些调用都将执行垃圾回收。启动时机的选择由JVM决定,并且取决于堆内存中Eden区是否可用。JVM将这个选择留给了Java规范的实现,不同实现具体使用的算法不尽相同。

毋庸置疑,我们知道垃圾回收过程是不能被强制执行的。我刚刚发现了一个调用System.gc()有意义的场景。通过这篇文章了解一下适合调用System.gc()这种极端情况。

Java垃圾回收过程

垃圾回收是一种回收无用内存空间并使其对未来实例可用的过程。

Eden区:当一个实例被创建了,首先会被存储在堆内存年轻代的Eden区中。

注意:如果你不能理解这些词汇,我建议你阅读这篇垃圾回收介绍,这篇教程详细地介绍了内存模型、JVM架构以及这些术语。

Survivor区(S0和S1):作为年轻代GC(MinorGC)周期的一部分,存活的对象(仍然被引用的)从Eden区被移动到Survivor区的S0中。类似的,垃圾回收器会扫描S0然后将存活的实例移动到S1中。

(译注:此处不应该是Eden和S0中存活的都移到S1么,为什么会先移到S0再从S0移到S1?)

死亡的实例(不再被引用)被标记为垃圾回收。根据垃圾回收器(有四种常用的垃圾回收器,将在下一教程中介绍它们)选择的不同,要么被标记的实例都会不停地从内存中移除,要么回收过程会在一个单独的进程中完成。

老年代:老年代(Oldortenuredgeneration)是堆内存中的第二块逻辑区。当垃圾回收器执行MinorGC周期时,在S1Survivor区中的存活实例将会被晋升到老年代,而未被引用的对象被标记为回收。

老年代GC(MajorGC):相对于Java垃圾回收过程,老年代是实例生命周期的最后阶段。MajorGC扫描老年代的垃圾回收过程。如果实例不再被引用,那么它们会被标记为回收,否则它们会继续留在老年代中。

内存碎片:一旦实例从堆内存中被删除,其位置就会变空并且可用于未来实例的分配。这些空出的空间将会使整个内存区域碎片化。为了实例的快速分配,需要进行碎片整理。基于垃圾回收器的不同选择,回收的内存区域要么被不停地被整理,要么在一个单独的GC进程中完成。

垃圾回收中实例的终结

在释放一个实例和回收内存空间之前,Java垃圾回收器会调用实例各自的finalize()方法,从而该实例有机会释放所持有的资源。虽然可以保证finalize()会在回收内存空间之前被调用,但是没有指定的顺序和时间。多个实例间的顺序是无法被预知,甚至可能会并行发生。程序不应该预先调整实例之间的顺序并使用finalize()方法回收资源。

任何在finalize过程中未被捕获的异常会自动被忽略,然后该实例的finalize过程被取消。

JVM规范中并没有讨论关于弱引用的垃圾回收机制,也没有很明确的要求。具体的实现都由实现方决定。

垃圾回收是由一个守护线程完成的。

对象什么时候符合垃圾回收的条件?

所有实例都没有活动线程访问。

没有被其他任何实例访问的循环引用实例。

Java中有不同的引用类型。判断实例是否符合垃圾收集的条件都依赖于它的引用类型。

引用类型 垃圾收集
强引用(Strong Reference) 不符合垃圾收集
软引用(Soft Reference) 垃圾收集可能会执行,但会作为最后的选择
弱引用(Weak Reference) 符合垃圾收集
虚引用(Phantom Reference) 符合垃圾收集

在编译过程中作为一种优化技术,Java 编译器能选择给实例赋 null 值,从而标记实例为可回收。

class Animal {
  public static void main(String[] args) {
    Animal lion = new Animal();
    System.out.println("Main is completed.");
  }
 
  protected void finalize() {
    System.out.println("Rest in Peace!");
  }
}

在上面的类中,lion对象在实例化行后从未被使用过。因此Java编译器作为一种优化措施可以直接在实例化行后赋值lion=null。因此,即使在SOP输出之前,finalize函数也能够打印出'RestinPeace!'。我们不能证明这确定会发生,因为它依赖JVM的实现方式和运行时使用的内存。然而,我们还能学习到一点:如果编译器看到该实例在未来再也不会被引用,能够选择并提早释放实例空间。

关于对象什么时候符合垃圾回收有一个更好的例子。实例的所有属性能被存储在寄存器中,随后寄存器将被访问并读取内容。无一例外,这些值将被写回到实例中。虽然这些值在将来能被使用,这个实例仍然能被标记为符合垃圾回收。这是一个很经典的例子,不是吗?

当被赋值为null时,这是很简单的一个符合垃圾回收的示例。当然,复杂的情况可以像上面的几点。这是由JVM实现者所做的选择。目的是留下尽可能小的内存占用,加快响应速度,提高吞吐量。为了实现这一目标,JVM的实现者可以选择一个更好的方案或算法在垃圾回收过程中回收内存空间。

当finalize()方法被调用时,JVM会释放该线程上的所有同步锁。

GCScope示例程序

Class GCScope {
	GCScope t;
	static int i = 1;
	public static void main(String args[]) {
		GCScope t1 = new GCScope();
		GCScope t2 = new GCScope();
		GCScope t3 = new GCScope();
		// No Object Is Eligible for GC
		t1.t = t2;
		// No Object Is Eligible for GC
		t2.t = t3;
		// No Object Is Eligible for GC
		t3.t = t1;
		// No Object Is Eligible for GC
		t1 = null;
		// No Object Is Eligible for GC (t3.t still has a reference to t1)
		t2 = null;
		// No Object Is Eligible for GC (t3.t.t still has a reference to t2)
		t3 = null;
		// All the 3 Object Is Eligible for GC (None of them have a reference.
		// only the variable t of the objects are referring each other in a
		// rounded fashion forming the Island of objects with out any external
		// reference)
	}
	protected void finalize() {
		System.out.println("Garbage collected from object" + i);
		i++;
	}
	class GCScope {
		GCScope t;
		static int i = 1;
		public static void main(String args[]) {
			GCScope t1 = new GCScope();
			GCScope t2 = new GCScope();
			GCScope t3 = new GCScope();
			// 没有对象符合GC
			t1.t = t2;
			// 没有对象符合GC
			t2.t = t3;
			// 没有对象符合GC
			t3.t = t1;
			// 没有对象符合GC
			t1 = null;
			// 没有对象符合GC (t3.t 仍然有一个到 t1 的引用)
			t2 = null;
			// 没有对象符合GC (t3.t.t 仍然有一个到 t2 的引用)
			t3 = null;
			// 所有三个对象都符合GC (它们中没有一个拥有引用。
			// 只有各对象的变量 t 还指向了彼此,
			// 形成了一个由对象组成的环形的岛,而没有任何外部的引用。)
		}
		protected void finalize() {
			System.out.println("Garbage collected from object" + i);
			i++;
		}

GC OutOfMemoryError 的示例程序

GC并不保证内存溢出问题的安全性,粗心写下的代码会导致 OutOfMemoryError。

import java.util.LinkedList;
import java.util.List;
public class GC {
	public static void main(String[] main) {
		List l = new LinkedList();
		// Enter infinite loop which will add a String to the list: l on each
		// iteration.
		do {
			l.add(new String("Hello, World"));
		}
		while (true);
	}
}

输出:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  at java.util.LinkedList.linkLast(LinkedList.java:142)
  at java.util.LinkedList.add(LinkedList.java:338)
  at com.javapapers.java.GCScope.main(GCScope.java:12)

总结

以上就是本文关于浅谈Java垃圾回收的实现过程的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

 类似资料:
  • 本文向大家介绍浅谈jvm中的垃圾回收策略,包括了浅谈jvm中的垃圾回收策略的使用技巧和注意事项,需要的朋友参考一下 java和C#中的内存的分配和释放都是由虚拟机自动管理的,此前我已经介绍了CLR中GC的对象回收方式,是基于代的内存回收策略,其实在java中,JVM的对象回收策略也是基于分代的思想。这样做的目的就是为了提高垃圾 回收的性能,避免对堆中的所有对象进行检查时所带来的程序的响应的延迟,因

  • 本文向大家介绍浅析JVM垃圾回收的过程,包括了浅析JVM垃圾回收的过程的使用技巧和注意事项,需要的朋友参考一下 JVM垃圾回收的算法很多,但是不管是哪种算法,在进行GC时大致的流程都是差不多的,主要有以下3个过程: 1. 枚举根节点 这个过程主要是找到所有的GC Roots对象,这些对象一般发生在JVM虚拟机栈栈帧、常量池中的静态对象、方法区中静态类属性引用、本地方法栈中引用的对象。这个过程会发生

  • 主要内容:1 什么是Java 垃圾回收,2 Java 垃圾回收的优势,3 如何取消对象引用,4 finalize()方法,5 gc()方法,6 Java 垃圾回收的例子1 什么是Java 垃圾回收 在Java中,垃圾意味着未引用的对象。 垃圾回收是自动回收运行时未使用的内存的过程。换句话说,这是销毁未使用对象的一种方法。 我们在C语言中使用free() 函数,在C ++中使用delete()。但是,在Java中它是自动执行的。因此,java提供了更好的内存管理。 2 Java 垃圾回收的优势 它

  • 问题内容: 我想知道Java中发生的垃圾回收。它真的能够处理所有未使用的对象并释放最大可能的内存吗? 我还想知道Java垃圾收集与另一种语言(例如C#)相比如何?然后,如何自动垃圾收集与从像C这样的语言中进行手动收集相比又能达到更好的效果呢? 问题答案: 是的,这就是垃圾收集的重点。 有许多不同形式的垃圾收集。如果不增强算法,最简单的形式即引用计数就无法处理某些类型的垃圾(循环引用)。 Java(

  • 主要内容:1.ZGC 概述,2.内存多重映射,3.染色指针,4.内存布局,5.读屏障,6.GC 过程,7.垃圾收集算法,8.总结1.ZGC 概述 ZGC(Z Garbage Collector) 是一款性能比 G1 更加优秀的垃圾收集器。ZGC 第一次出现是在 JDK 11 中以实验性的特性引入,这也是 JDK 11 中最大的亮点。在 JDK 15 中 ZGC 不再是实验功能,可以正式投入生产使用了,使用 –XX:+UseZGC 可以启用 ZGC。 ZGC 有 3 个重要特性: 暂停时间不会超过

  • 垃圾回收 我们对生产中花了很多时间来调整垃圾回收。垃圾回收的关注点与Java大致相似,尽管一些惯用的Scala代码比起惯用的Java代码会容易产生更多(短暂的)垃圾——函数式风格的副产品。Hotspot的分代垃圾收集通常使这不成问题,因为短暂的(short-lived)垃圾在大多情形下会被有效的释放掉。 在谈GC调优话题前,先看看这个Attila的报告,它阐述了我们在GC方面的一些经验。 Scal

  • 对于开发者来说,JavaScript 的内存管理是自动的、无形的。我们创建的原始值、对象、函数……这一切都会占用内存。 当我们不再需要某个东西时会发生什么?JavaScript 引擎如何发现它并清理它? 可达性(Reachability) JavaScript 中主要的内存管理概念是 可达性。 简而言之,“可达”值是那些以某种方式可访问或可用的值。它们一定是存储在内存中的。 这里列出固有的可达值的

  • 垃圾收集,引用计数,显式分配 和所有的现代语言一样,OCaml提供垃圾收集器,所以你不用像C/C++一样显式地分配和释放内存。 JWZ在他的文章 "Java sucks" rant(Java蛋疼(怒)!): 第一个好家伙是Java没有 free()。其他的都没有所谓了。这几乎掩盖了所有的缺点,不管有多糟糕, 这个有点让后续文档基本都没有意义了,但是...(译注:但是啥大家自己看吧) OCaml的垃