Java虚拟机垃圾回收机制知识点总结

  • 时间:
  • 3071人关注

下面小编就为大家带来一篇老生常谈Java虚拟机垃圾回收机制(必看篇)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧,另外这篇文章主要知识点是关于Java、虚拟机、垃圾回收机制、Java虚拟机的内容,如果大家想对相关知识点有系统深入的学习,可以参阅以下电子资料:

在Java虚拟机中,对象和数组的内存都是在堆中分配的,垃圾收集器主要回收的内存就是再堆内存中。如果在Java程序运行过程中,动态创建的对象或者数组没有及时得到回收,持续积累,最终堆内存就会被占满,导致OOM。

JVM提供了一种垃圾回收机制,简称GC机制。通过GC机制,能够在运行过程中将堆中的垃圾对象不断回收,从而保证程序的正常运行。

垃圾对象的判定

我们都知道,所谓“垃圾”对象,就是指我们在程序的运行过程中不再有用的对象,即不再存活的对象。那么怎么来判断堆中的对象是“垃圾”、不再存活的对象呢?

引用计数法

每个对象都有一个引用计数的属性,用来保存该对象被引用的次数。当引用次数为0时,就意味着该对象没有被引用了,也就不会在使用这个对象了,可以判定为垃圾对象。但是,这种方式有一个很大的Bug,就是无法解决对象间相互引用或者循环引用的问题:当两个对象相互引用,他们两个和其他任何对象也没有引用关系,它俩的引用次数都不为0,因此不会被回收,但实际上这两个对象已经不再有用了。

可达性分析(根搜索法)

为了避免使用引用计数法带来的问题,Java采用了可达性分析法来判断垃圾对象。

这种方式可以将所有对象的引用关系想象成一棵树,从树的根节点GC Root遍历所有引用的对象,树的节点就为可达对象,其他没有处于节点的对象则为不可达对象。

老生常谈Java虚拟机垃圾回收机制(必看篇)

那么什么样的对象可以作为GC的根节点呢?

虚拟机栈(帧栈中的本地变量表)中引用的对象

方法区中静态属性引用的对象

方法区中常量引用的对象

本地方法栈中JNI引用的对象

引用状态

垃圾回收机制,不管采用是引用计数法,还是可达性分析法,都与对象的引用有关,Java中存在四种引用状态:

强引用 - 我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,就表示它处于可达状态,垃圾回收器绝不会回收它,即便系统内存非常紧张,Java虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会回收被强引用所引用的对象。因此,强引用是造成Java内存泄露的主要原因之一。

软引用 - 一个对象只具有软引用,如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

弱引用 - 一个对象只具有弱引用,那就类似于是可有可无的。弱引用和软引用很像,但弱引用的引用级别更低。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

虚引用 - 一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收的活动,我们平常一般不会使用。

垃圾回收算法

通过可达性分析算法能够判定哪些对象是需要回收的了,那么回收具体需要怎样去执行呢?

标记-清除算法

首先需要标记可以回收的对象内存,然后在对回收的内存进行清除。

老生常谈Java虚拟机垃圾回收机制(必看篇)

标记-清除算法(回收前)

老生常谈Java虚拟机垃圾回收机制(必看篇)

标记-清除算法(回收后)

但是这样的话,随着程序的运行,会不断分配释放内存,在堆中会产生很多的不连续的空闲内存区,即内存碎片。这样即使有足够多的空闲内存,也不一定能分配出足够大的内存,并且可能会造成频繁的GC,影响效率,甚至OOM。

标记-整理算法

和标记-清除算法不同的是,标记-整理算法在标记后不直接清理可回收内存,而是将存活对象都移动到一端,然后清除掉可回收内存。

老生常谈Java虚拟机垃圾回收机制(必看篇)

标记-整理算法(回收前)

老生常谈Java虚拟机垃圾回收机制(必看篇)

标记-整理算法(回收后)

这样做的好处就是不会产生内存碎片。

复制算法

复制算法需要先将内存分为两块,先在其中一块内存上分配内存,当这块内存被分配完后,则执行垃圾回收,然后把存活对象全部复制到另一块内存上,第一块内存则全部清空。

老生常谈Java虚拟机垃圾回收机制(必看篇)

复制算法(回收前)

老生常谈Java虚拟机垃圾回收机制(必看篇)

复制算法(回收后)

这种算法不会产生内存碎片,但是相当于只能使用一半的内存空间。同时,复制算法和存活对象的数量有关,如果存活对象的数量多,那么复制算法的效率会大大降低。

分代收集算法

在Java虚拟机中,对象的生命周期有长有短,大部分对象的生命周期很短,只有少部分的对象才会在内存中存留较长时间,因此可以依据对象生命周期的长短将它们放在不同的区域。在采用分代收集算法的Java虚拟机堆中,一般分为三个区域,用来分别储存这三类对象:

新生代 - 刚创建的对象,在代码运行时一般都会持续不断地创建新的对象,这些新创建的对象有很多是局部变量,很快就会变成垃圾对象。这些对象被放在一块称为新生代的内存区域。新生代的特点是垃圾对象多,存活对象少。

老年代 - 一些对象很早被创建了,经历了多次GC也没有被回收,而是一直存活下来。这些对象被放在一块称为老年代的区域。老年代的特点是存活对象多,垃圾对象少。

永久代 - 一些伴随虚拟机生命周期永久存在的对象,比如一些静态对象,常量等。这些对象被放在一块称为永久代的区域。永久代的特点是这些对象一般不需要垃圾回收,会在虚拟机运行过程中一直存活。(在Java1.7之前,方法区中存储的是永久代对象,Java1.7方法区的永久代对象移到了堆中,而在Java1.8永久代已经从堆中移除了,这块内存给了元空间。)

分代收集算法也就根据新生代和老年代来进行垃圾回收的。

对于新生代区域,每次GC都会有很多垃圾对象被回收,只有少量存活。因此采用复制回收算法,GC时把剩余很少的存活对象复制过去即可。

在新生代区域中,并不是按照1:1的比例来进行复制回收,而是按照8:1:1的比例分为了Eden、SurvivorA、SurvivorB三个区域。其中Eden意为伊甸园,形容有很多新生对象在里面创建;Survivor区则为幸存者,即经历GC后仍然存活下来的对象。

Eden区对外提供堆内存。当Eden区快要满了,则进行Minor GC(新生代GC),把存活对象放入SurvivorA区,清空Eden区;

Eden区被清空后,继续对外提供堆内存;

当Eden区再次被填满,此时对Eden区和SurvivorA区同时进行Minor GC(新生代GC),把存活对象放入SurvivorB区,此时同时清空Eden区和SurvivorA区;

Eden区继续对外提供堆内存,并重复上述过程,即在 Eden 区填满后,把Eden区和某个Survivor区的存活对象放到另一个Survivor区;

当某个Survivor区被填满,且仍有对象未被复制完毕时,或者某些对象在反复Survive 15次左右时,则把这部分剩余对象放到老年代区域;当老年区也被填满时,进行Major GC(老年代GC),对老年代区域进行垃圾回收。

老年代区域对象一般存活周期较长,每次GC时,存活的对象比较多,因此采用标记-整理算法,GC时移动少量存活对象,不会产生内存碎片。

触发GC的类型

Java虚拟机会把每次触发GC的信息打印出来,可以根据日志来分析触发GC的原因。

GC_FOR_MALLOC:表示是在堆上分配对象时内存不足触发的GC。

GC_CONCURRENT:当我们应用程序的堆内存达到一定量,或者可以理解为快要满的时候,系统会自动触发GC操作来释放内存。

GC_EXPpCIT:表示是应用程序调用System.gc、VMRuntime.gc接口或者收到SIGUSR1信号时触发的GC。

GC_BEFORE_OOM:表示是在准备抛OOM异常之前进行的最后努力而触发的GC。

以上这篇老生常谈Java虚拟机垃圾回收机制(必看篇)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持码农之家。

码农之家
浅析Java垃圾回收机制

4小时52分钟前回答

浅析Java垃圾回收机制

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

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

启动Java垃圾回收

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

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

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

Java垃圾回收过程

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

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

展开阅读

上一篇:Java中BigInteger大数字运算知识点整理

下一篇:Java异常区分和处理的经验方法总结

相关内容

  • 如何用Java实现将容器 Map中的内容保存到数组

    这篇文章主要介绍了Java实现将容器 Map中的内容保存到数组,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

    05-03Java Map内容保存数组

    阅读更多
  • Java数据类型及类型转换的互相转换实例代码

    这篇文章主要介绍了Java 数据类型及类型转换的互相转换实例代码,需要的朋友可以参考下

    05-09java数据类型互相转换

    阅读更多
  • 如何让java程序运行

    04-29java程序运行

    阅读更多
  • java是如何输出的

    04-29java输出的方式

    阅读更多
  • java如何调用接口

    其实对于java调用接口进行获取对方服务器的数据在开发中特别常见,然而一些常用的基础的知识总是掌握不牢,让人容易忘记,写下来闲的时候看看,比回想总会好一些。 总体而言,一些东西知识点一直复制粘贴容易依赖,重要的是会忘记为什么这么写,只有理解到位,或者八九不离十才可以对于随时变化的情况进行分析,如果到家,还可以对别人或自己的进行优化。 如果你在这篇没有找到你想要的,请点击:java如何调用接口方式二 而对于一些知识

    04-27java调用接口的方法

    阅读更多
  • 深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)

    深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)

    大小:253 MBJava虚拟机

    点击下载
  • 深入Java虚拟机:JVM G1GC的算法与实现

    深入Java虚拟机:JVM G1GC的算法与实现

    编辑推荐 结合实用JVM,图解Java垃圾回收机制的关键技术! 90张图表+33段代码,轻松理解G1GC算法原理 HotSpotVM源码剖析,深入探讨G1GC具体实现 1.深入Java虚拟机底层原理,详细解读经典GC算法; 2.理论结合实际,基于HotSpotVM源码探讨具体实现; 3.图文并茂、深入浅出,辅以大量插图和代码细致讲解。 内容简介 本书深入Java虚拟机底层原理,对JVM内存管理中的垃圾回收算法G1GC进行了详细解读。全书分为算法篇和实现篇两大部分:前一部分主要介绍G1GC的算法

    大小:129 MBJava虚拟机

    点击下载
  • 深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)

    深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)

    (1)这本书值得全部Java技术人员读3遍,值得Java技术人员读3遍,值得读3遍! (2)依据JDK12和JDK13EA版本全方位升级,增加內容近50%,并对第二版中模棱两可、缺陷和不正确內容开展了调整 。 (3)计算机图书行业的永远的丰碑,前两版总计包装印刷36次,销售量超出三十万册,俩家网店评价总数超出90000条,內容近乎零恶意差评。 (4)从Java技术性管理体系、全自动代码优化、vm虚拟机实行分系统、程序流程编译器与代码设计、高效率高并发五个层面全

    大小:38 MBJava虚拟机

    点击下载
  • 实战Java虚拟机:JVM故障诊断与性能优化

    实战Java虚拟机:JVM故障诊断与性能优化

    实战Java虚拟机:JVM故障诊断与性能优化 [葛一鸣] 2017年版

    大小:35.76MBJava虚拟机

    点击下载

学习笔记

21小时43分钟前回答

java垃圾回收机制原理知识点详解

说到垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联系起来。在Java中,程序员不需要去关心内存动态分配和垃圾回收的问题,这一切都交给了JVM来处理。 顾名思义,垃圾回收就是释放垃圾占用的空间,那么在Java中,什么样的对象会被认定为垃圾?那么当一些对象被确定为垃圾之后,采用什么样的策略来进行回收(释放空间)?在目前的商业虚拟机中,有哪些典型的垃圾收集器?下面我们就来逐一探讨这些问题。以下是本文的目录大纲: 如何确定某个对象是垃圾? 典型的垃圾收集算法 典型的垃圾收集器 一.如何确定某个对象是垃圾? 在这一小节我们先了解一个最基本的问题:如果……

29小时57分钟前回答

Java垃圾回收之分代收集算法详解

概述 这种算法,根据对象的存活周期的不同将内存划分成几块,新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。可以用抓重点的思路来理解这个算法。 新生代对象朝生夕死,对象数量多,只要重点扫描这个区域,那么就可以大大提高垃圾收集的效率。另外老年代对象存储久,无需经常扫描老年代,避免扫描导致的开销。 新生代 在新生代,每次垃圾收集器都发现有大批对象死去,只有少量存活,采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;可以参看我之前写的Java垃圾回收之复制算法详解 老年代 而老年代中因为对象存活率高、没有额外空间对它进行分……