Java 虚拟机 二

[TOC]

垃圾收集器 与 内存分配策略

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想
进去,墙里面的人却想出来。

一、概述

了解GC和内存分配的原因:

​ 当需要排查各种内存溢出、内存泄露问题时,当垃圾收集成为系统达到更高并发量的瓶颈时候,就需要对这些“自动化”的技术实施必要的监控和调节。

跳转到这里:

二、对象已死吗

在 Java堆 放着Java中几乎所有的对象实例,垃圾收集器在进行 堆回收之前,首先要做的就是确定这些对象中 哪些对象还 “存活”着,哪些已经“死去”(即不可能再被任何途径使用的对象)

1. 引用计数算法

给对象中添加一个引用计数器,每有一个地方引用她时候,计数器就 加 1;引用失效时,计数器就 减 1,,任何时刻计数器 为 0的对象就不可能再被使用

Java没有选用 引用计数算法来管理内存,原因是它很难解决对象之间的互相循环引用问题。

2.可达性分析算法

基本思路:通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径为 引用链(Reference Chain),当一个对象到GC Roots没有任何引用链 相连时,证明此对象不可用

Java中 可以作为 GC Roots对象包括以下几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性 引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈 中JNI(一般说的 Native 方法)引用的对象。

3. 引用是啥玩意,都有啥

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

Java中分为 强引用、软引用、弱引用、虚引用

  • 强引用(Strong Reference)

    在程序代码中普遍存在的,类似 Object obj = new Object()的引用,只要强引用还存在,垃圾收集器不会回收被引用的对象。

  • 软引用(Soft Reference)

    软引用用来描述一些还有用但并非必需的对象,软引用关联的对象,在系统将要发生内存溢出的异常之前会把这些对象列进中进行第二次回收。这次回收没有足够的内存,才会抛出内存溢出异常

  • 弱引用(Weak Reference)

    弱引用用来描述非必需对象的,强调比弱引用更弱,被弱引用关联的对象只能生存到下一次垃圾收集前

  • 虚引用(Phantom Reference)

    ​ 最弱的引用关系。一个对象是否有虚引用的存在不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知

4. 生存还是死亡(标记过程)

可达性分析算法中不可达的对象,也不是非死不可的,这个时候它们暂时处于缓刑阶段,真正的宣告一个对象死亡需要经历两次标记过程

  1. 如果对象在进行可达性分析后发现没有与 GC Roots相连接的引用链,会被第一次标记并且进行一次筛选,筛选的条件是这个对象是不是有必要执行 finalize()方法。

    • 当对象没有覆盖 finalize() 方法
    • finalize() 方法已经被虚拟机调用过

    以上两种情况,虚拟机都当做“没必要执行”

  2. 如果对象被判定有必要执行finalize() 方法,这个对象就被放置在一个叫做 F-Queue的队列中,并且在稍后 一个由虚拟机自动建立的、低优先级的Finalizer 线程 去执行它。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束,这样做的原因是,如果一个对象在finalize()方法中执行缓慢,或者发生了死循环(更极端的情况),将很可能会导致F-Queue队列中其他对象永久处于等待,甚至导致整个内存回收系统崩溃。finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移除出“即将回收”的集合;如果对象这时候还没有逃脱,那基本上它就真的被回收了。

5. 回收方法区

Java虚拟机规范中说过 可以不要求虚拟机在方法区实现垃圾收集,而且在方法区中进行垃圾收集的“性价比”一般比较低。在堆中国,尤其是新生代中,常规应用进行一次垃圾收集一般可以回收70%~95%的空间,永久代的垃圾收集效率远低于这个。

永久代的垃圾收集主要回收两个部分内容: 废弃常量和无用的类

回收废弃常量和回收Java堆中的对象类似。如果没有对象引用 常量池的某个常量,也没有其他地方引用了这个字面量。如果这个时候发生内存回收,而且必要的话,常量就会被系统清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也是类似。

判定无用类的条件:

  • 该类的所有实例都已经被回收,即Java堆中不存在该类的任何实例
  • 加载该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

虚拟机可以对满足上述3个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样,不使用了就必然会回收

HotSpot 虚拟机提供了

-Xnoclassgc参数进行控制

-verbose:class、-XX:+TraceClassLoading、-XX:+TraceClassUnLoading 查看类加载和卸载信息

三. 垃圾手机算法

几种算法的思想

1. 清除-标记算法

算法分为两个阶段:清除、标记两个阶段

  1. 清除

    先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象

  2. 标记

    标记过程

清除-标记算法是个最基础算法,后续的收集算法都是基于这个思路对其不足进行改进而得到的

主要不足:浪费时间和空间

  1. 效率问题,标记和清除两个过程的效率是太
  2. 空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致后面程序运行过程中需要分配较大对象时,不能找到足够内存而不得不提前触发的一次垃圾收集动作

2. 复制算法

为了

3. 标记-整理算法

4. 分代收集算法

​ 根据对象存活周期的不同将内存划分为几块。Java堆分为新生代 和 老年代,可以根据各个年代的特点采用最适当的收集算法

​ 在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就蒜用复制算法,只需要付出少量存活对象的复制成本就可以完成收集

​ 在老年代中 因对象存活率高、没有额外空间进行分配担保,就必须使用“标记-清理”或者“标记-整理”算法 进行回收。

四、HotSpot 的算法实现

1. 枚举根节点

2. 安全点

3. 安全域

五、垃圾收集器

1. Serial 收集器

2. ParNew 收集器

3. Parallel Scavenge 收集器

4. Serial Old收集器

5. Parallel Old 收集器

6. CMS收集器

7. G1收集器

8. 理解 GC日志