1、Java虚拟机垃圾回收

垃圾回收,或称垃圾收集(Garbage Collection,GC)是指自动管理回收不再被引用的内存数据。

在1960年诞生于MIT的Lisp语言首次使用了动态内存分配和垃圾收集技术,可以实现垃圾回收的一个基本要求是语言是类型安全的,现在使用的包括Java、Perl、ML等。

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)、很难解决对象之间相互循环引用的问题

如:

  1. ReferenceCountingGC objA = new ReferenceCountingGC();
  2. ReferenceCountingGC objB = new ReferenceCountingGC();
  3. objA.instance = objB;
  4. objB.instance = objA;
  5. objA = null;
  6. 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 类实例的终结;

3、HotSpot虚拟机中对象可达性分析的实现

前面对可达性分析算法进行介绍,并看到了它在判断对象存活与死亡的作用,下面看看是HotSpot虚拟机是如何实现可达性分析算法,如何解决相关缺点的。

3-1、可达性分析的问题

1、消耗大量时间

从前面可达性分析知道,GC Roots主要在全局性的引用(常量或静态属性)和执行上下文中(栈帧中的本地变量表);

要在这些大量的数据中,逐个检查引用,会消耗很多时间;

2、GC停顿

可达性分析期间需要保证整个执行系统的一致性,对象的引用关系不能发生变化;

导致GC进行时必须停顿所有Java执行线程(称为"Stop The World");

(几乎不会发生停顿的CMS收集器中,枚举根节点时也是必须要停顿的)

Stop The World:

是JVM在后台自动发起和自动完成的;

在用户不可见的情况下,把用户正常的工作线程全部停掉;

3-2、枚举根节点

枚举根节点也就是查找GC Roots;

目前主流JVM都是准确式GC,可以直接得知哪些地方存放着对象引用,所以执行系统停顿下来后,并不需要全部、逐个检查完全局性的和执行上下文中的引用位置;

在HotSpot中,是使用一组称为OopMap的数据结构来达到这个目的的;

在类加载时,计算对象内什么偏移量上是什么类型的数据;

在JIT编译时,也会记录栈和寄存器中的哪些位置是引用;

这样GC扫描时就可以直接得知这些信息;

3-3、安全点

1、安全点是什么,为什么需要安全点

HotSpot在OopMap的帮助下可以快速且准确的完成GC Roots枚举,但是这有一个问题:

运行中,非常多的指令都会导致引用关系变化;

如果为这些指令都生成对应的OopMap,需要的空间成本太高;

问题解决:

只在特定的位置记录OopMap引用关系,这些位置称为安全点(Safepoint);

即程序执行时并非所有地方都能停顿下来开始GC;

2、安全点的选定

不能太少,否则GC等待时间太长;也不能太多,否则GC过于频繁,增大运行时负荷;

所以,基本上是以程序"是否具有让程序长时间执行的特征"为标准选定;

"长时间执行"最明显的特征就是指令序列复用,如:方法调用、循环跳转、循环的末尾、异常跳转等;

只有具有这些功能的指令才会产生Safepoint;

3、如何在安全点上停顿

对于Safepoint,如何在GC发生时让所有线程(不包括JNI线程)运行到其所在最近的Safepoint上再停顿下来?

主要有两种方案可选:

(A)、抢先式中断(Preemptive Suspension)

不需要线程主动配合,实现如下:

(1)、在GC发生时,首先中断所有线程;

(2)、如果发现不在Safepoint上的线程,就恢复让其运行到Safepoint上;

现在几乎没有JVM实现采用这种方式;

(B)、主动式中断(Voluntary Suspension)

(1)、在GC发生时,不直接操作线程中断,而是仅简单设置一个标志;

(2)、让各线程执行时主动去轮询这个标志,发现中断标志为真时就自己中断挂起;

而轮询标志的地方和Safepoint是重合的;

在JIT执行方式下:test指令是HotSpot生成的轮询指令;

一条test汇编指令便完成Safepoint轮询和触发线程中断;

3-4、安全区域

1、为什么需要安全区域

对于上面的Safepoint还有一个问题:

程序不执行时没有CPU时间(Sleep或Blocked状态),无法运行到Safepoint上再中断挂起;

这就需要安全区域来解决;

2、什么是安全区域(Safe Region)

指一段代码片段中,引用关系不会发生变化;

在这个区域中的任意地方开始GC都是安全的;

3、如何用安全区域解决问题

安全区域解决问题的思路:

(1)、线程执行进入Safe Region,首先标识自己已经进入Safe Region;

(2)、线程被唤醒离开Safe Region时,其需要检查系统是否已经完成根节点枚举(或整个GC);

如果已经完成,就继续执行;

否则必须等待,直到收到可以安全离开Safe Region的信号通知;

这样就不会影响标记结果;

虽然HotSpot虚拟机中采用了这些方法来解决对象可达性分析的问题,但只是大大减少了这些问题影响,并不能完全解决,如GC停顿"Stop The World"是垃圾回收重点关注的问题,后面介绍垃圾回收器时应注意:低GC停顿是其一个关注。

原博地址:https://blog.csdn.net/tjiyu/article/details/53982412

Java虚拟机垃圾回收:基础点(转载)的更多相关文章

  1. Java虚拟机垃圾回收(三): 7种垃圾收集器(转载)

    1.垃圾收集器概述 垃圾收集器是垃圾回收算法(标记-清除算法.复制算法.标记-整理算法.火车算法)的具体实现,不同商家.不同版本的JVM所提供的垃圾收集器可能会有很在差别,本文主要介绍HotSpot虚 ...

  2. Java虚拟机垃圾回收(二) :垃圾回收算法(转载)

    1.标记-清除算法 标记-清除(Mark-Sweep)算法是一种基础的收集算法. 1.算法思路 "标记-清除"算法,分为两个阶段: (A).标记 首先标记出所有需要回收的对象: 标 ...

  3. Java虚拟机垃圾回收(三) 7种垃圾收集器

    Java虚拟机垃圾回收(三) 7种垃圾收集器 主要特点 应用场景 设置参数 基本运行原理 在<Java虚拟机垃圾回收(一) 基础>中了解到如何判断对象是存活还是已经死亡?在<Java ...

  4. Java虚拟机垃圾回收:内存分配与回收策略 方法区垃圾回收 以及 JVM垃圾回收的调优方法

    在<Java对象在Java虚拟机中的创建过程>了解到对象创建的内存分配,在<Java内存区域 JVM运行时数据区>中了解到各数据区有些什么特点.以及相关参数的调整,在<J ...

  5. ☕【JVM技术指南】「JVM总结笔记」Java虚拟机垃圾回收认知和调优的"思南(司南)"【下部】

    承接上文 (完结撒花1-52系列)[JVM技术指南]「JVM总结笔记」Java虚拟机垃圾回收认知和调优的"思南(司南)"[上部] 并行收集器 并行收集器(也称为吞吐量收集器)是类似 ...

  6. 了解java虚拟机—垃圾回收算法(5)

    引用计数器法(Reference Counting) 引用计数器的实现很简单,对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器减1.只要对象A的引用计数器的 ...

  7. JAVA虚拟机垃圾回收算法原理

    除了释放不再被引用的对象外,垃圾收集器还要处理堆碎块.新的对象分配了空间,不再被引用的对象被释放,所以堆内存的空闲位置介于活动的对象之间.请求分配新对象时可能不得不增大堆空间的大小,虽然可以使用的总空 ...

  8. Java虚拟机垃圾回收机制

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

  9. Java虚拟机----垃圾回收与内存分配

    一.垃圾回收的对象: 在Java的运行时数据区中,程序计数器和虚拟机栈.本地方法栈是随着线程的生灭而生灭,栈当中栈帧的大小在编译的时候已知,在方法结束之后栈帧出栈,这部分的垃圾回收是明确的,因此需要讨 ...

随机推荐

  1. 经典算法详解(1)斐波那契数列的n项

    斐波那契数列是一个常识性的知识,它指的是这样的一个数列,它的第一项是1,第二项是1,后面每一项都是它前面两项的和,如:1,1,2,3,5,8,13,21,34,55,89,144,233…… 说明:由 ...

  2. maven module

    通过将一个maven项目拆分成多个module,会引入一定的项目复杂度,但随着后期项目代码的逐渐增多,最直观的感受是,每次build代码,不必build整个项目,可节省很多时间. 如果各个module ...

  3. 用DebuggerDisplay在Visual Studio的调试器中定制类的显示方式

    博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:用DebuggerDisplay在Visual Studio的调试器中定制类的显示方式.

  4. java基础--提示对话框的使用

    java基础--提示对话框的使用 2019-03-17-00:35:50-----云林原创 一.显示信息对话框:使用“JOptionPane.showMessageDialog”显示:   图标 对话 ...

  5. oracle UDT 有关数据字典的研究

    1.数据及类型准备 创建了一个自定义类型 create or replace type addr_type as object( street varchar2(30); city varchar2( ...

  6. CSP学习之ASN.1编码(一)

    一.定义: 是定义抽象数据类型的标准. 是用于描述数据的表示.编码.传输.解码的灵活记法. 它提供一套正式.无歧义和精确的规则,以描述独立于特定计算机硬件的对象结构. 标准的ASN.1编码规则有其基本 ...

  7. shiro 配置导图

    1 web.xml配置:shiro filter必须放在其他filter之前 <filter> <filter-name>shiroFilter</filter-name ...

  8. 数据分析核心包——pandas

    一.pandas简介 pandas是一个强大的Python数据分析的工具包,是基于NumPy构建的. 1.pandas的主要功能 (1)具备对其功能的数据结构DataFrame.Series (2)集 ...

  9. js实现螺旋纹理特效

    效果如下       实现代码如下: <!doctype html> <html> <head> <meta charset="UTF-8" ...

  10. 【转】SNR , Eb/N0 , Es/N0区别与联系

    原文地址:http://www.360doc.com/content/16/0505/23/532901_556620735.shtml 通信方向在做仿真时经常用到信噪比这个参数,而对于不同形式的信号 ...