java 垃圾收集(Garbage Collection)之 哪些内存需要回收

Posted BBinChina

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java 垃圾收集(Garbage Collection)之 哪些内存需要回收相关的知识,希望对你有一定的参考价值。

Garbage Collection : 垃圾收集,简称GC

在使用java的时候,我们将内存交由虚拟机管理,而不需开发人员过度关注,但并不意味着我们可以不学习什么是垃圾回收和内存分配。因为当需要排查各种内存溢出、内存泄漏问题时,垃圾回收成为系统达到更高并发量的瓶颈时(gc需要占用工作线程),我们需要能进行监控和调节。

Java的内存运行区数据中,程序计数器、虚拟机栈、本地方法栈这3个区域随线程而生亡,其栈帧分配多少内存基本上是在类结构确定下来时就已知的,所以这3个区域的内存分配和回收都具备确定性,而不需要关注如何回收:当方法结束或者线程结束时,内存自然就回收了

那么哪个区域的内存需要考虑呢:java堆方法区
比如:一个接口的多个实现类需要的内存可能不同,一个方法所执行的不同条件分支所需要的内存也可能不一样,只有在运行期才能知道创建哪些对象,创建多少个对象。这部分内存的分配和回收是动态的,我们将讨论java这部分内存的分配和回收

如果让你设计垃圾回收,可以有哪些方法

引用计数算法

在使用c++的智能指针share_ptr时,其采用一个计数器来记录对象的引用情况,当新增引用时,计数器+1,引用失效时,计数器就-1.当计数器值为0时,对象可以被自动化释放。

采用引用计数算法比较简单,但随着的问题在于解决循环引用的问题,而在Java中,大多数的JVM并没有选用引用计数算法,主要在于JVM整体不像c++的share_ptr这样简化。

可达性分析算法

这个算法的思路是通过一系列称为"GC Roots"的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则表明该对象不可能再被使用。


在Java体系中,固定可作为GC Roots对象包括以下几种:
1、在虚拟机栈(栈帧中的本地变量表)中引用的对象
2、在方法区中类静态属性引用的对象
3、在方法区中常量引用的对象
4、在本地方法栈中JNI(Native方法)引用的对象
5、Java虚拟机内部的引用
6、所有被同步锁(synchronized)持有的对象
7、反映JVM内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等

了解哪些是GC Roots对象,我们再回头看什么是引用?哎?引用不就是 数据中存储的数值代表的另外一块内存的起始地址么?这有啥好解释的?

Java 引用

强引用(Strongly Reference):程序中普遍存在的引用赋值,只要强引用在,对象就永不会回收,如 Object A = new Object();

软引用(Soft Reference):类SoftReference用于实现软应用,描述还有用,但非必须的对象,在内存溢出异常前才列入回收对象范围进行回收。

弱引用(Weak Reference):类WeakReference实现弱引用,描述非必须对象,在gc时不管内存是否足够都进行回收。

虚引用(Phantom Reference):类PhantomReference实现虚引用,为对象设置虚引用的唯一目的是在该对象回收时,收到一个系统通知。

Java哪些对象将被回收

通过可达性分析算法,JVM判定不可达的对象列入回收范围,但并不是立马进行回收执行,还得再经历一次标记,即对象是否需被回收经历两次标记:
1、可达性分析,当没有引用链时,进行一次标记
2、该对象执行了finalize()方法或者没必要执行finalize()方法时,进行一次标记

什么叫没必要执行:
当对象没有覆盖finalize()方法,或者finalize()方法已经被JVM调用过,称这两种情况为"没有必要执行"
而如果要执行finalize()方法,也并非立马进行标记,而是将对象放置F-Queue队列之中,并由JVM的Finalizer线程去执行队列中对象的finalize()方法,该线程并不会等待finalize()方法执行完成,避免对象Finalize()方法异常导致回收子系统崩溃等情况。在执行finalize()方法时,可以将this(即自己)赋值给某个类变量或者对象的成员变量,让当前对象重回调用链,那么可以让自己避免被标记,从而自我拯救。
需要注意的是对象的finalize()方法只能被系统自动调用一次

/**
 * 1、对象可以在被GC时自救
 * 2、自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次
 */
public class FinalizeEscapeGC {
    private static FinalizeEscapeGC SAVE_HOOK = null;

    public void isAlive() {
        System.out.println("im fine");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method executed!");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }

    public static void main(String[] args) throws InterruptedException {
        SAVE_HOOK = new FinalizeEscapeGC();

        SAVE_HOOK = null;
        System.gc();

        //Finalizer方法优先级很低,先暂停
        Thread.sleep(500);
        if(SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("gg");
        }

        //再一次gc,finalize不会再被调用
        SAVE_HOOK = null;
        System.gc();

        Thread.sleep(500);
        if(SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("gg");
        }
    }
}

以上代码便是利用finalize()方法来自我拯救,避免被回收, 一般场景下,也不会有使用finalize()方法的时候。

回收方法区

上文说到,回收的内容除了Java堆外,还有方法区的内容:放弃的常量和不再使用的类型。
而判断是否废弃的条件:
1、该类的所有实例都已经被回收,即Java堆中不存在该类以及任何派生子类的实例。
2、加载该类的类加载器已经被回收。
3、该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

以上是关于java 垃圾收集(Garbage Collection)之 哪些内存需要回收的主要内容,如果未能解决你的问题,请参考以下文章

java垃圾回收Garbage Collection(垃圾收集算法)

Garbage First(G1)垃圾收集器

[JVM 相关] Java 新型垃圾回收器(Garbage First,G1)

java 垃圾收集(Garbage Collection)之 哪些内存需要回收

垃圾收集(Garbage Collection)

详解 JVM Garbage First(G1) 垃圾收集器