转自:https://blog.csdn.net/tjiyu/article/details/53982412

1-1、为什么需要了解垃圾回收

目前内存的动态分配与内存回收技术已经相当成熟,但为什么还需要去了解内存分配与GC呢?

1、当需要排查各种内存溢出、内存泄漏问题时;

2、当垃圾收集成为系统达到更高并发量的瓶颈时;

我们就需要对这些"自动化"技术实话必要的监控和调节;

1-2、垃圾回收需要了解什么

思考GC完成的3件事:

1、哪些内存需要回收?即如何判断对象已经死亡;

2、什么时候回收?即GC发生在什么时候?需要了解GC策略,与垃圾回收器实现有关;

3、如何回收?即需要了解垃圾回收算法,及算法的实现--垃圾回收器;

第一点就是本文下面的主题,这是垃圾回收的基础,如:可达性分析算法是后面垃圾回收算法的基础,而判断哪些对象可以回收是垃圾回收的首要任务。

2、判断对象可以回收

垃圾收集器对堆进行回收前,首先要确定堆中的对象哪些还"存活",哪些已经"死去";

下面先来了解两种判断对象不再被引用的算法,再来谈谈对象的引用,最后来看如何真正宣告一个对象死亡。

2-1、引用计数算法(Recference Counting)

1、算法基本思路

给对象添加一个引用计数器,每当有一个地方引用它,计数器加1;

当引用失效,计数器值减1;

任何时刻计数器值为0,则认为对象是不再被使用的;

2、优点

实现简单,判定高效,可以很好解决大部分场景的问题,也有一些著名的应用案例;

3、缺点

(A)、很难解决对象之间相互循环引用的问题

如:

ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;

当两个对象不再被访问时,因为相互引用对方,导致引用计数不为0;

更复杂的循环数据结构,如图(《编译原理》7-18):

B)、并且开销较大,频繁且大量的引用变化,带来大量的额外运算;

主流的JVM都没有选用引用计数算法来管理内存;

2-2、可达性分析算法(Reachability Analysis)

也称为传递跟踪算法;

主流的调用程序语言(Java、C#等)在主流的实现中,都是通过可达性分析来判定对象是否存活的。

1、算法基本思路

通过一系列"GC Roots"对象作为起始点,开始向下搜索;

搜索所走过和路径称为引用链(Reference Chain);

当一个对象到GC Roots没有任何引用链相连时(从GC Roots到这个对象不可达),则证明该对象是不可用的;

2、GC Roots对象

Java中,GC Roots对象包括:

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

(2)、方法区中类静态属性引用的对象;

(3)、方法区中常量引用的对象;

(4)、本地方法栈中JNI(Native方法)引用的对象;

主要在执行上下文中和全局性的引用;

3、优点

更加精确和严谨,可以分析出循环数据结构相互引用的情况;

4、缺点

实现比较复杂;

需要分析大量数据,消耗大量时间;

分析过程需要GC停顿(引用关系不能发生变化),即停顿所有Java执行线程(称为"Stop The World",是垃圾回收重点关注的问题);

后面会针对HotSpot虚拟机实现的可达性分析算法进行介绍,看看是它如何解决这些缺点的。

2-3、再谈对象引用

在《Java对象在Java虚拟机中的引用访问方式》曾详细介绍过对象的引用问题,这与对象回收算法有很大关系,下面再来了解下。

java程序通过reference类型数据操作堆上的具体对象;

1、JVM层面的引用

reference类型是引用类型(Reference Types)的一种;

JVM规范规定reference类型来表示对某个对象的引用,可以想象成类似于一个指向对象的指针;

对象的操作、传递和检查都通过引用它的reference类型的数据进行操作

2、Java语言层面的引用

(i)、JDK1.2前的引用定义

如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用;

这种定义太过狭隘,无法描述更多信息;

(ii)、JDK1.2后,对引用概念进行了扩充,将引用分为:

(1)、强引用(Strong Reference)

程序代码普遍存在的,类似"Object obj=new Object()";

只要强引用还存在,GC永远不会回收被引用的对象;

(2)、软引用(Soft Reference)

用来描述还有用但并非必需的对象;

直到内存空间不够时(抛出OutOfMemoryError之前),才会被垃圾回收;

最常用于实现对内存敏感的缓存;

SoftReference类实现;

(3)、弱引用(Weak Reference)

用来描述非必需对象;

只能生存到下一次垃圾回收之前,无论内存是否足够;

WeakReference类实现;

(4)、虚引用(Phantom Reference)

也称为幽灵引用或幻影引用;

完全不会对其生存时间构成影响;

唯一目的就是能在这个对象被回收时收到一个系统通知;

PhantomRenference类实现;

更多请参考JDK相关API说明;

对于软引用,可以使用命令行选项"-XX:SoftRefLRUPolicyMSPerMB = <N>"来控制清除速率;

更多请参考:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/considerations.html#sthref65

2-4、判断对象生存还是死亡

要真正宣告一个对象死亡,至少要经历两次标记过程。

1、第一次标记

在可达性分析后发现到GC Roots没有任何引用链相连时,被第一次标记;

并且进行一次筛选:此对象是否必要执行finalize()方法;

(A)、没有必要执行

没有必要执行的情况:

(1)、对象没有覆盖finalize()方法;

(2)、finalize()方法已经被JVM调用过;

这两种情况就可以认为对象已死,可以回收;

(B)、有必要执行

对有必要执行finalize()方法的对象,被放入F-Queue队列中;

稍后在JVM自动建立、低优先级的Finalizer线程(可能多个线程)中触发这个方法;

2、第二次标记

GC将对F-Queue队列中的对象进行第二次小规模标记;

finalize()方法是对象逃脱死亡的最后一次机会:

(A)、如果对象在其finalize()方法中重新与引用链上任何一个对象建立关联,第二次标记时会将其移出"即将回收"的集合;

(B)、如果对象没有,也可以认为对象已死,可以回收了;

一个对象的finalize()方法只会被系统自动调用一次,经过finalize()方法逃脱死亡的对象,第二次不会再调用;

2-5、finalize()方法

上面已经说到finalize()方法与垃圾回收第二次标记相关,下面了解下在Java语言层面有哪些需要注意的。

finalize()是Object类的一个方法,是Java刚诞生时为了使C/C++程序员容易接受它所做出的一个妥协,但不要当作类似C/C++的析构函数;

因为它执行的时间不确定,甚至是否被执行也不确定(Java程序的不正常退出),而且运行代价高昂,无法保证各个对象的调用顺序(甚至有不同线程中调用);

如果需要"释放资源",可以定义显式的终止方法,并在"try-catch-finally"的finally{}块中保证及时调用,如File相关类的close()方法;

此外,finalize()方法主要有两种用途:

1、充当"安全网"

当显式的终止方法没有调用时,在finalize()方法中发现后发出警告;

但要考虑是否值得付出这样的代价;

如FileInputStream、FileOutputStream、Timer和Connection类中都有这种应用;

2、与对象的本地对等体有关

本地对等体:普通对象调用本地方法(JNI)委托的本地对象;

本地对等体不会被GC回收;

如果本地对等体不拥有关键资源,finalize()方法里可以回收它(如C/C++中malloc(),需要调用free());

如果有关键资源,必须显式的终止方法;

一般情况下,应尽量避免使用它,甚至可以忘掉它。

更多请参考:

《How to Handle Java Finalization's Memory-Retention Issues》:http://www.devx.com/Java/Article/30192

《Effective Java》第二版 第2章 第7条:避免使用终结方法;

《Thinking in Java》第四版 5.5 清理:终结处理和垃圾回收;

《Java语言规范》12.6 类实例的终结;

JVM 基础:回收哪些内存/对象 引用计数算法 可达性分析算法 finalize()方法 HotSpot实现分析的更多相关文章

  1. java中垃圾回收机制中的引用计数法和可达性分析法(最详细)

    首先,我这是抄写过来的,写得真的很好很好,是我看过关于GC方面讲解最清楚明白的一篇.原文地址是:https://www.zhihu.com/question/21539353

  2. JVM中垃圾回收机制如何判断是否死亡?详解引用计数法和可达性分析 !

    因为热爱,所以坚持. 文章下方有本文参考电子书和视频的下载地址哦~ 这节我们主要讲垃圾收集的一些基本概念,先了解垃圾收集是什么.然后触发条件是什么.最后虚拟机如何判断对象是否死亡. 一.前言   我们 ...

  3. JVM的分区+查看GC对象是否存活+3种GC算法+7种垃圾收集器+如何减少GC次数

    一.JVM的分区:   1.程序计数器(私有) 程序计数器是一块较小的内存分区,你可以把它看做当前线程所执行的字节码的指示器. 在虚拟机的概念模型里,字节码解释器工作时,就是通过改变计数器的值来选择下 ...

  4. JVM垃圾回收机制之对象回收算法

    前言 在前面的文章中,介绍了JVM内存模型分为:堆区.虚拟机栈.方法区.本地方法区和程序计数器,其中堆区是JVM中最大的一块内存区域,在Java中的所有对象实例都保存在此区域,它能被所有线程共享. 在 ...

  5. [jvm]垃圾回收与内存分配策略

    一.垃圾回收算法 概述 JVM中,当创建的对象不再被使用的时候,此时我们认为他是无用的“垃圾”:在现代主流的商用jvm中,都是通过可达性分析来判断对象是否存活的.这个算法的基本思想是通过一系列“GCR ...

  6. JVM基础知识1--JAVA内存区域与内存溢出异常

    1,运行时数据区域 根据JAVA虚拟机规范的规定:JAVA虚拟机所管理的内存将会包括以下几个运行时数据区域 程序计数器(Program Counter Register)是一块较小的内存空间,它的作用 ...

  7. Jvm垃圾回收堆内存变化过程

    当Eden区域满时,触发minor GC,垃圾收集器把Eden区域中的不可达对象标记出来.第一次执行minor GC时Survivor 1与Survivor 2均为空: Eden中的不可达对象占用的内 ...

  8. java基础学习总结六(对象与类、类的属性与方法)

    一:面向过程与面向对象的区别 举例:一个人开门的动作,可以分解为开门,人进去,关门. 面向过程:人作为执行者,1:开门  2:进入   3:关门 面向对象:人作为指挥者,将开门,关门的动作都封装到门上 ...

  9. JVM 垃圾回收GC Roots Tracing

    1.跟搜索算法: JVM中对内存进行回收时,需要判断对象是否仍在使用中,可以通过GC Roots Tracing辨别. 定义: 通过一系列名为”GCRoots”的对象作为起始点,从这个节点向下搜索,搜 ...

随机推荐

  1. .NetCore下使用Prometheus实现系统监控和警报 (五)进阶自定义收集指标 之 Counter

    Prometheus下面定了四种类型的收集方式,下面我们主要来来说下Counter的使用 Nuget导入Prometheus.AspNetCore包 下面先来看下我的Prometheus配置,这里我没 ...

  2. HDU 1851 (N个BASH博弈子游戏)

    题意:n堆石子,分别有M1,M2,·······,Mn个石子,各堆分别最多取L1,L2,·····Ln个石头,两个人分别取,一次只能从一堆中取,取走最后一个石子的人获胜.后选的人获胜输出Yes,否则输 ...

  3. Ckeditor一种很方便的文本编辑器

    ckeditor官网:http://ckeditor.com/ 这里介绍ckeditor的其中一个的用法,自己做小项目练手非常的适合,上手非常的快. 首先去官网下载这个东西,链接:http://pan ...

  4. [转] python安装numpy和pandas

    最近要对一系列数据做同比比较,需要用到numpy和pandas来计算,不过使用python安装numpy和pandas因为linux环境没有外网遇到了很多问题就记下来了.首要条件,python版本必须 ...

  5. Codeforces 247D Mike and Fish

    Mike and Fish 我们可以把这个模型转换一下就变成有两类点,一类是X轴, 一类是Y轴, 每个点相当于对应的点之间建一条边, 如果这条边变红两点同时+1, 变蓝两点同时-1. 我们能发现这个图 ...

  6. Mysql数据库改名

    转自:https://www.cnblogs.com/leffss/p/7832100.html #!/bin/bash #作者:fafu_li #时间:2015.08.10 #mysql数据库改名, ...

  7. Evolution ZOJ2853

    矩阵太大导致爆栈 解决方法:矩阵不要开在main函数里面  全部在外面处理  main函数只能处理简单的赋值和加减 损失的要自减!! 最后为  sum+=t[i]*ans.m[i][n-1];   得 ...

  8. 【Java】 剑指offer(22) 链表中倒数第k个结点

    正文 本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集   题目 输入一个链表,输出该链表中倒数第k个结点.为了符合大多数人的 ...

  9. Selenium3 + Python3自动化测试系列二——selenium元素定位

    一.selenium元素定位 Selenium对网页的控制是基于各种前端元素的,在使用过程中,对于元素的定位是基础,只有准去抓取到对应元素 才能进行后续的自动化控制,我在这里将对selenium8种元 ...

  10. MySQL 5.7基于GTID复制的常见问题和修复步骤(二)

    [问题二] 有一个集群(MySQL5.7.23)切换后复制slave报1236,其实是不小心在slave上执行了事务导致 Got fatal error 1236 from master when r ...