JVM(九) - 垃圾回收机制

各语言内存操作对比:

 

语言

申请内存

释放内存

C

malloc

free

C++

new

delete

Java

new

自动释放

Java语言的自动内存管理设计最终可以归结为自动化地解决了两个问题:

  1. 给对象分配内存,可查看JVM内存模型、Java对象创建等文章;
  2. 回收分配给对象的内存。

即对象内存的分配和回收。

了解JVM是垃圾回收机制,如何有效防止内存泄露、保证内存的有效使用,需要思考三个方向的问题:

  • 什么对象的内存需要回收?(什么是垃圾?怎么判断对象是垃圾?)
  • 用什么方式回收?(垃圾回收的算法?)
  • 有什么垃圾收集器?

一、对象回收判断

进行垃圾回收的第一步:什么是垃圾? 没有引用指向的一个对象或多个对象(循环引用)。

定位垃圾的方法有两种:引用计数法可达性分析

1、对像的引用

Java中将数据类型分为两大类:基本类型和引用类型。如果reference类型数据中存储的数值为另一个块内存的起始地址,就称这块内存代表一个引用。在JDK1.2开始对引用的概念进行了扩充,分为强、软、弱、虚四种引用,且强度依次逐渐降低。详细见Java-四种引用类型。

Person p = new Person();

// 等号后面的 new Person(); 是真正的实例对象,其内容存储在Java堆内存中
// 等号前面的 p 只是一个引用标识符,保存在虚拟机栈内存中,它存储的只是一个地址,是 new Person() 在堆内存中的起始位置,因此 p 就是一个引用。
// 等号是一种强引用方式

2、引用计数法(Reference Count已淘汰 )

引用计数法是垃圾收集器中的早期策略,通过判断对象的引用数量来决定对象是否可以被回收。该方法为堆中每个对象添加一个计数器,有地方引用了此对象则该对象的计数器加1,如果引用失效了则计数器减1,引用计数为0的对象实例则可以被当作垃圾收集

优点:实现简单,判定效率也很高;

缺点:很难解决对象之间循环引用的问题;互相引用导致他们的引用计数都不为0,最终不能回收他们。

3、根可达性分析算法(Root Searching)

通过判断对象的引用链是否可达GCRoot来决定对象是否可以被回收。从离散数学中的图论引入,把所有的引用关系当作一张关系图谱,从被称为"GCRoot"的对象作为起始点,沿着引用链(Reference Chain,即引用路径)向下搜索,如果一个对象没有任何引用链连接到GCRoot节点,则证明此对象是不可用的/不可达,则此对象可被回收。

JAVA中可以作为GCRoot对象/根对象包括以下几种:

  • 虚拟机栈(栈帧中的局部变量表)中引用的对象;
  • 方法区中类静态变量引用的对象;
  • 方法区中运行常量池引用的对象;
  • 本地方法栈中Native方法引用的对象;

如图,引用链连接上GCroot的Object1、2、3、4都是被使用的对象,但是Object5、6、7却不能通过任何方式连接上根节点,因此判定Object5、6、7为可回收的节点。

4、对象死亡和自我拯救

可达性分析之后,不可达的对象一定会被垃圾收集器回收吗?不一定。

在可达性分析后发现不可达的对象会被进行一次标记,然后进行筛选,筛选的条件是判断该对象有没有必要执行finalize()方法;

  • 回收情况:如果对象没有重写finalize()方法或者对象的finalize方法已经被虚拟机调用过一次了,则都将视为"没有必要执行",垃圾回收器直接回收对象。
  • 自救情况:如果对有重写finalize()方法且对象的finalize方法没有被虚拟机调用过,则将该对象判定"有必要执行",那么虚拟机会把这个对象放置在一个F-Queue的队列中,然后由一个专门的Finalizer线程去执行这个对象的finalize()方法;在这个方法中可以进行对象的"自我拯救",即重新与引用链上的任何一个对象建立关联就可以了,如把this赋值给某个类的变量、或者对象的成员变量;但在又被第二次GC标记时它将被移除"即将回收"的集合

1、对象无finalize方法:直接去除引用链,一次GC时便直接回收实例对象。

// JVM执行参数 -Xmx20m -XX:+PrintGCDetails
public class FinalizeTest {
    static class M {
        // 10M大小
        private byte[] bytes = new byte[10 * 1024 * 1024];
    }

    public static void main(String[] args) throws Exception {
        M m = new M();
        m = null;
        // new M()被去掉引用,且M中没有自救方法finalize,所以在GC时会直接回收
        System.gc();
        // GC相关线程优先级低,主线程等待一下
        Thread.sleep(1000);
    }
}
// 日志
/* 年轻代PSYoungGen的总内存大小为6144k=6M,所以10M的new M()对象会直接被分配在老年代ParOldGen*/
[GC (System.gc()) [PSYoungGen: 1749K->480K(6144K)] 11989K->10765K(19968K), 0.0018203 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
// FGC情况下,ParOldGen老年代从10285K->471K
[Full GC (System.gc()) [PSYoungGen: 480K->0K(6144K)] [ParOldGen: 10285K->471K(13824K)] 10765K->471K(19968K), [Metaspace: 3087K->3087K(1056768K)], 0.0046541 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
/* 可知new M()有10M大小,从整个堆上各代的使用内存情况,可知new M()已经不在堆上面,也可以通过jmap来查看实例对象存活*/
/* 后面的三个内存地址值指的是:起始地址,已使用空间的结束地址,对应区整个内存空间的结束地址 */
Heap
 PSYoungGen      total 6144K, used 1255K [0x00000007bf980000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 5632K, 22% used [0x00000007bf980000,0x00000007bfab9f10,0x00000007bff00000)
  from space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
  to   space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
 ParOldGen       total 13824K, used 471K [0x00000007bec00000, 0x00000007bf980000, 0x00000007bf980000)
  object space 13824K, 3% used [0x00000007bec00000,0x00000007bec75e70,0x00000007bf980000)
 Metaspace       used 3613K, capacity 4536K, committed 4864K, reserved 1056768K
  class space    used 399K, capacity 428K, committed 512K, reserved 1048576K

Process finished with exit code 0

2、对象有finalize方法,但不进行自我拯救:直接去除引用链,第一次GC时对象放入F-Queue的队列且进行finalize方法的拯救,不会被回收,但finalize中并未拯救,所以第二次GC时对象会被回收

public class FinalizeTest {
    static class M {
        // 10M大小
        private byte[] bytes = new byte[10 * 1024 * 1024];

        @Override
        protected void finalize() {
            System.out.println("m的finalize执行了,但并不拯救");
        }
    }

    public static void main(String[] args) throws Exception {
        M m = new M();
        m = null;
        System.out.println("第一次GC-----------");
        // 因为M的存在finalize,所以被放入F-Queue的队列中,拯救线程执行finalize方法,所以10M的new M()对象本次不被回收
        System.gc();
        // GC相关线程优先级低,主线程等待一下
        Thread.sleep(1000);

        // 第二次GC,
        System.out.println("第二次GC-----------");
        // 因为M的finalize已经执行了,但并未拯救,所以10M的new M()对象被回收了
        System.gc();
        Thread.sleep(1000);
    }
}
// 日志
第一次GC-----------
[GC (System.gc()) [PSYoungGen: 1862K->512K(6144K)] 12102K->10797K(19968K), 0.0022455 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
/* 第一次FGC情况下,ParOldGen老年代从10285K->10712K,说明并未回收10M的new M()对象*/
[Full GC (System.gc()) [PSYoungGen: 512K->0K(6144K)] [ParOldGen: 10285K->10712K(13824K)] 10797K->10712K(19968K), [Metaspace: 3101K->3101K(1056768K)], 0.0040101 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
m的finalize执行了,
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值