Java虚拟机二、垃圾回收与内存分配(1)概述

垃圾回收概述

1.垃圾回收

  • 垃圾回收,或称垃圾收集(Garbage Collection,GC)是指自动管理回收不再被引用的内存数据。
  • Lisp语言首次使用了动态内存分配和垃圾收集技术,可以实现垃圾回收的一个基本要求是语言是类型安全的,现在使用的包括Java、Perl、ML等。
1.1了解垃圾回收的必要性?
  • 1、当需要排查各种内存溢出、内存泄漏问题时;
  • 2、当垃圾收集成为系统达到更高并发量的瓶颈时;
1.2垃圾回收到底干了哈?

1. 哪些内存需要回收?即如何判断对象已经死亡;
2. 什么时候回收?即GC发生在什么时候?需要了解GC策略,与垃圾回收器实现有关;
3. 如何回收?即需要了解垃圾回收算法,及算法的实现–垃圾回收器;

2.如何判断一个对象是不是可以回收?

2.1引用计数法
  • 1、思路:
    给对象添加一个引用计数器,每当有一个地方引用它,计数器加1;
    当引用失效,计数器值减1;
    任何时刻计数器值为0,则认为对象是不再被使用的;

  • 2、优点
    实现简单,判定高效,可以很好解决大部分场景的问题;

  • 3、缺点

  1. 很难解决对象之间相互循环引用的问题
  2. 并且开销较大,频繁且大量的引用变化,带来大量的额外运算;
    主流的JVM都没有选用引用计数算法来管理内存;
2.2 可达性分析方法(主流的实现)
  • 1、思路:
    通过一系列"GC Roots"对象作为起始点,开始向下搜索;
    搜索所走过和路径称为引用链(Reference Chain);
    当一个对象到GC Roots没有任何引用链相连时(从GC Roots到这个对象不可达),则证明该对象是不可用的;

在这里插入图片描述

  • 2、GC ROOTs对象

1.虚拟机栈(栈帧中本地变量表)中引用的对象;
2. 方法区中类静态属性引用的对象;
3. 方法区中常量引用的对象;
4. 本地方法栈中JNI(Native方法)引用的对象;

  • 3、优点

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

  • 4、缺点

  1. 实现比较复杂;
  2. 需要分析大量数据,消耗大量时间;
  3. 分析过程需要GC停顿(引用关系不能发生变化),即停顿所有Java执行线程(称为"Stop The World",是垃圾回收重点关注的问题);

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

2.3对象引用
  • 引用的狭义的定义(jdk1.2之前)
    如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用;
  • 引用的分类(jdk1.2之后)
    1.强引用(Strong Reference)
    程序代码普遍存在的,类似"Object obj=new Object()";
    只要强引用还存在,GC永远不会回收被引用的对象;
  1. 软引用(Soft Reference)

    用来描述还有用但并非必需的对象;
    直到内存空间不够时(抛出OutOfMemoryError之前),才会被垃圾回收;
    最常用于实现对内存敏感的缓存;
    SoftReference类实现;

  2. 弱引用(Weak Reference)

    用来描述非必需对象;
    只能生存到下一次垃圾回收之前,无论内存是否足够
    WeakReference类实现;

  3. 虚引用(Phantom Reference)

    也称为幽灵引用或幻影引用;
    完全不会对其生存时间构成影响;
    唯一目的就是能在这个对象被回收时收到一个系统通知;
    PhantomRenference类实现;

2.4判断对象生存或者是死亡的过程

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

  1. 第一次标记

    在可达性分析后发现到GC Roots没有任何引用链相连时,被第一次标记;
    并且进行一次筛选:此对象是否必要执行finalize()方法:

    1. 没有必要执行

      没有必要执行的情况:

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

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

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

    1. 有必要执行

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

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

  1. 第二次标记

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

finalize() 方法

finalize()方法是对象逃脱死亡的最后一次机会
(A)如果对象在其finalize()方法中重新与引用链上任何一个对象建立关联,第二次标记时会将其移出"即将回收"的集合;
(B、如果对象没有,也可以认为对象已死,可以回收了;
一个对象的finalize()方法只会被系统自动调用一次,经过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());
    如果有关键资源,必须显式的终止方法;

一般情况下,应尽量避免使用它,甚至可以忘掉它。(前面这么多白看了。。。。)

可达性分析算法的实现(以hotpot虚拟机为例)

1. 可达性分析所带来的问题
  1. 消耗大量时间

    从前面可达性分析知道,GC Roots主要在全局性的引用(常量或静态属性)和执行上下文中(栈帧中的本地变量表);
    要在这些大量的数据中,逐个检查引用,会消耗很多时间;

  2. GC停顿

    可达性分析期间需要保证整个执行系统的一致性对象的引用关系不能发生变化
    导致GC进行时必须停顿所有Java执行线程(称为"Stop The World");
    (几乎不会发生停顿的CMS收集器中,枚举根节点时也是必须要停顿的)

    Stop The World: 是JVM在后台自动发起和自动完成的;
    在用户不可见的情况下,把用户正常的工作线程全部停掉;

2. 枚举根节点(查找GC Roots)
  • 目前主流JVM都是准确式GC,可以直接得知哪些地方存放着对象引用,所以执行系统停顿下来后,并不需要全部、逐个检查完全局性的和执行上下文中的引用位置;
  • 在HotSpot中,是使用一组称为OopMap的数据结构来达到这个目的的;
    在类加载时,计算对象内什么偏移量上是什么类型的数据;
    在JIT编译时,也会记录栈和寄存器中的哪些位置是引用;
    这样GC扫描时就可以直接得知这些信息;
3.安全点
  • 安全点是干哈的?
    OopMap数据结构可以有助于快而准地完成GC Roots的枚举,但有一个问题:
    指令导致对象间的引用关系发生变化,如果为每一条指令都生成一个OopMap,需要的空间成本太高
  • 解决方法:
    并非为每一条指令都生成OopMap,只在特定的位置(安全点)记录这些信息。即:程序并非在所有的地方都会停顿下来开始GC,只有在安全点彩可以
  • 安全点的选取:
    选择标准:是否具备让程序长时间执行的特征
    "长时间执行"最明显的特征就是指令序列复用,如:方法调用、循环跳转、循环的末尾、异常跳转等;
    只有具有这些功能的指令才会产生Safepoint;
* 安全点的停顿

即:如何让所有的线程都跑到最近的安全点上在停顿

    1. 抢先式中断(现在几乎没有JVM实现采用这种方式)
      不需要线程主动配合,实现如下:
      (1)在GC发生时,首先中断所有线程;
      (2)如果发现不在Safepoint上的线程,就恢复让其运行到Safepoint上;
    1. 主动式中断(Voluntary Suspension)
      (1)在GC发生时,不直接操作线程中断,而是仅简单设置一个标志
      (2)让各线程执行时主动去轮询这个标志,发现中断标志为真时就自己中断挂起;
      而轮询标志的地方和Safepoint是重合的;
4. 安全区域
  • 出现的原因:
    程序在没有占用CPU时(sleep或是block), 无法运行到安全点上挂起

  • 啥是安全区域
    指一段代码片段中,引用关系不会发生变化;在这个区域中的任意地方开始GC都是安全的;

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

    1. 线程执行进入Safe Region,首先标识自己已经进入Safe Region;
    2. 线程被唤醒离开Safe Region时,其需要检查系统是否已经完成根节点枚举(或整个GC);
      如果已经完成,就继续执行;
      否则必须等待,直到收到可以安全离开Safe Region的信号通知;这样就不会影响标记结果;

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值