概述
GC => 垃圾回收 = 回收可用空间 + 压缩内存
内存管理
- 手动内存管理 => C | C++
- 自动内存管理 => 几乎所有的现代编程语言
自动内存管理
引用计数 Reference Counting
内存被引用了一次,引用计数为1
引用计数管理内存:清除引用计数为 0 的内存,之后被清除的内存引用的内存引用计数减 1
引用计数的致命缺点:没有办法处理循环引用
标记清除 Mark and Sweep
从一个不是垃圾的根节点开始,能够访问到的所有的对象都不是垃圾,其余都是垃圾
从一组预先定义好的根节点开始,把不是垃圾的都打上标记,之后把没有标记都清除掉
标记清除算法 => Java | JavaScript | Python
内存碎片化
长期工作的内存会出现碎片化 => 总可用空间仍然足够,但是无法分配大对象
引用的类型
- 强引用 => Strong Reference => Developer 用到的都是强引用 => GC 的时候回收的都是强引用
- 软引用 => Soft Reference => 内存不够时会回收它们 =>
SoftReference<Integer> soft = new SoftReference<>(1);
- 弱引用 => Weak Reference => 一 GC 就回收它们 =>
WeakReference<Integer> weak = new WeakReference<>(1);
- 影子引用 => Phantom Reference => 只能拿到影子,拿不到引用本身
Java GC
GC 的本质就是从 GC 开始沿着引用链去找到所有的可以达到的对象,这个过程叫可达性分析
GC Roots
- 活的线程 => java.lang.Thread
- 类的静态成员
- 线程方法栈所引用的对象
- JNI 引用的对象 => JNI == Java Native Interface => Java 通过一些接口来调用 C 的代码,Java 的内存管理不管理这部分,所以将其引用的对象都算作 GC Root
- 分代 GC 时其他代的对象
GC 发生的什么
每时每刻堆都会发生很多改变,可能有新的对象产生,有旧的对象消失,在此过程中可达性分析如何处理?
- STW => Stop The World => 把当前 JVM 中所有运行的线程都停下来,等待标记结束,在执行线程 => 一般以毫秒计
- 清除垃圾
- 压紧(compact)内存 | 拷贝(一边回收一边拷贝到另外一块内存,拷贝相比压紧更快一点,但是占用了两倍的空间)
对象的分代假设
研究表明:绝大多数对象的生命周期都很短
内存分代 => HotSpot
- 年轻代 => Young Generation
- 老年代 => Old Generation | Tenured
- 永久代 => Permanent Generation
Young Generation
- Eden => 伊甸园 => 可以分为多个 Thread Local Allocation Buffer => TLAB => 当 new 一个新的对象时会进入伊甸园,如果对象特别特别大,会直接进入老年代
- Survivor (S0 | S1 | From & To) => Java 虚拟机并没有定义如何去实现 & 命名,这里都是 HotSpot 实现细节 => 当伊甸园满了之后执行一次年轻代 GC,将剩余对象存放到 Survivor 中,一共有两个 Survivor,两个 Survivor 一个是有对象的,一个是空的,当伊甸园满了的时候,进行 GC,将伊甸园和 Survivor 的剩余对象放置在另一个 Survivor 中,之前 Survivor 空了 => 超过 15 次 GC Cycles 的对象会被放置在 Tenured 中 => 有可能没有到 15 次的对象也被提升到了 Tenured,可能的原因是该对象过大,Survivor 中放不下 => 过早的提升对于程序的健康是一个损害,在 GC 的日志中可以查看到 => 解决方式:将 Young Generation 空间调大
伊甸园和 Survivor 的大小是可配置的 => Search Key: jvm eden survivor ratio => -XX:SurvivorRatio
以及超过多少次 GC Cycles 会被提升至 Tenured 中也是可配置的 => Search Key: jvm young promote tenured threshold => MaxTenuringThreshold=0
Tenured
- 老年代相对较大
- 存储足够年老的对象
- 发生 GC 的频率较低 => 清除垃圾 + 压紧内存
永久代 & 元空间
Java 8 之前:永久代
- 是堆的一部分
- 存储类数据、字符串常量
- OOM => Out of Memory => Permgen Space => 产生原因大致两个 => 自定义 Classloader + 动态字节码生成
Java 8+:元空间
- 不是堆的一部分
- 除非特别指定,否则没有上限
- 设置
-XX:MaxMetaspaceSize
限制,此时可能出现 OOM:metaspace
GC 种类
- Minor GC/Young GC
- 总是发生在 Eden 满的时候
- 会发生 STW
- Major GC vs Full GC
- 这两个概念没有明确的定义
- Major GC => 清除老年代
- Full GC => 清除整个堆 => 发生条件:程序中有一个对象哪里都放不下 => 不应该发生,如果发生,表明程序处于不健康的状态 => 发生 Full GC 的过程要耗费 100ms 左右,取决于堆的大小,会引起程序严重性的下降
GC 算法
GC AlgorithmJVM options
-XX:+UseXXX
-
-X
=> 证明这个参数通用,不仅 HotSpot 可以使用,其他虚拟机也可以使用 -
-XX
=> HotSpot 专属 -
+
=> 开启 -
-
=> 关闭 -
UseXXX
=> 相关功能
G1
G1 => Garbage First => 优先收集垃圾 => 设计的主要目的:提供一个可以预测的停顿时间 => Example: 1s 内 GC 的时间不能超过 5ms
知识点
- 在 Java 中所有的对象只能通过地址来访问,不算 Native + JNI
- 时间概念
- SSD => 100微秒
- HDD => 1 ~ 10 ms
- HTTP => 100 - 500 ms => 大于 500 ms 人类就能感知到了
- GC => < 100 ms
- 多线程加锁会导致性能下降
-
Thread State TERMINATED 是需要 GC 的线程,其余的是活的
Thread State
网友评论