java垃圾回收(一)——什么是垃圾

    最近主要时间都放在知识图谱构建中,但是还是需要给自己充电。想在近段时间好好把JVM的垃圾回收好好看一下,学习然后输出,是我新找到的有效学习方法,希望你看了之后能有收获。

    垃圾回收(常称做GC——Garbage Collection)诞生于1960年 MIT 的 Lisp 语言,垃圾回收机制也已经用在了很多编程语言中,比如java、python、C#等。我们这里主要说java中的垃圾回收。

    在JVM中,程序计数器、虚拟机栈、本地方法栈都是随线程生而生,随线程灭而灭;栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理;常说的垃圾回收主要集中在堆和方法区,这部分内存是随着程序运行动态分配的。

    既然要回收垃圾,那么我们首先需要知道的就是,什么样的对象是垃圾。一般有两种方式:

引用计数

    每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,当引用计数变为0的时候,这个对象就可以回收了。但是这个方法无法解决对象循环引用的问题。

    // 对象循环引用示例

    Object objectA = new Object();
    Object objectB = new Object();

    objectA.instance = objectB;
    objectB.instance = objectA;

    objectA = null;
    objectB = null;

    假设我们有上面的代码。程序启动后,objectA和objectB两个对象被创建并在堆中分配内存,它们都相互持有对方的引用,但是除了它们相互持有的引用之外,再无别的引用。而实际上,引用已经被置空,这两个对象不可能再被访问了,但是因为它们相互引用着对方,导致它们的引用计数都不为0,因此引用计数算法无法通知GC回收它们,造成了内存的浪费。如下图:对象之间的引用形成一个有环图。

image

可达性分析

    或者叫根搜索算法,在主流的JVM中,都是使用的这种方法来判断对象是否存活的。这个算法的思路很简单,它把内存中的每一个对象都看作一个结点,然后定义了一些可以作为根结点的对象,我们称之为“GC Roots”。果一个对象中有另一个对象的引用,那么就认这个对象有一条指向另一个对象的边。

image

    像上面这张图,JVM会起一个线程从所有的GC Roots开始往下遍历,当遍历完之后如果发现有一些对象不可到达,那么就认为这些对象已经没有用了,需要被回收。(这里多说一句,我们的JVM一起动,就至少有两个线程启动,一个是垃圾回收线程,一个是我们自己的主线程。

    那么现在问题就变成了——什么样的对象可以当作GC Roots?共有四种对象可以作为GC Roots。

虚拟机栈中的引用的对象

    们在程序中正常创建一个对象,对象会在堆上开辟一块空间,同时会将这块空间的地址作为引用保存到虚拟机栈中,如果对象生命周期结束了,那么引用就会从虚拟机栈中出栈,因此如果在虚拟机栈中有引用,就说明这个对象还是有用的,这种对象可以作为GC Roots。

全局的静态的对象

    也就是使用了static关键字定义了的对象,这种对象的引用保存在共有的方法区中,因为虚拟机栈是线程私有的,如果保存在栈里,就不叫全局了,很显然,这种对象是要作为GC Roots的。

常量引用

    就是使用了static final关键字,由于这种引用初始化之后不会修改,所以方法区常量池里的引用的对象也作为GC Roots。

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

    有时候单纯的java代码不能满足我们的需求,就可能需要调用C或C++代码(java本身就是用C和C++写的嘛),因此会使用native方法,JVM内存中专门有一块本地方法栈,用来保存这些对象的引用,所以本地方法栈中引用的对象也会被作为GC Roots。

猜你喜欢

转载自blog.csdn.net/heuguangxu/article/details/80171834