JVM 运行时的数据区域
-
程序计数器(Program Counter Register)
- 一块比较小的内存空间,是当前线程所执行的字节码的行号指示器
- 为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,即线程私有
- 若正在执行的是一个 Java 方法,这个计数器记录的是当前正在执行的虚拟机字节码的地址
- 若正在执行的是 Native 方法,这个计数器则为空(Undefined)
- 唯一一个在 Java 虚拟机规范中没有规定任何 OOM 情况的区域
-
Java 虚拟机栈(Java Virtual Machine Stacks)
- 生命周期与线程相同,也是线程私有内存
- 用于描述Java 方法执行的内存模型: 每个 Java 方法执行时会创建一个栈帧,
用于存储局部变量表,操作数栈,动态链接和方法出口等信息
- 若线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError;
若虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存,将抛出 OutOfMemoryError
-
本地方法栈(Native Method Stack)
- Java 虚拟机栈为 Java 方法(字节码)服务,而本地方法栈为 Native 方法服务
- Sun HotSpot 虚拟机把本地方法栈和虚拟机栈合二为一
-
Java 堆(Java Heap)
- 一般来说,是 Java 虚拟机锁管理的内存中最大的一块,被所有线程共享
- 几乎所有的对象实例都在这里分配内存
- 是垃圾收集器管理的主要区域,也称作GC 堆(Garbage Collected Heap)
- 若在堆中没有内存完成实例分配,也无法再扩展时,将抛出 OutOfMemoryError
-
方法区(Method Area)
- 所有线程共享的内存区域
- 存储已被虚拟机加载的类信息、常量、静态变量和即时编译器编译后的代码等数据
- HotSpot 虚拟机使用永久代(Permanent Generation)来实现方法区,
像管理 Java 堆一样管理这部分内存
- 永久代有
-XX:MaxPremSize
作为上限
对象存活&死去
Java 中的四种引用—强软弱虚
- 强引用(Strong Reference): 最常见的引用,只要强引用还在,就永远不会被回收
- 软引用(Soft Reference): 系统将要发生内存溢出异常之前,将会把软引用中的对象进行回收,
若这次回收仍没有释放足够的内存,才会抛出内存溢出异常
- 弱引用(Weak Reference): 只被弱引用关联的对象只能生存到下一次垃圾回收之前,即当垃圾收集器工作时,总会回收掉只被弱引用关联的对象
- 虚引用(Phantom Reference): 最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个对象实例.为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知
垃圾回收与 finalize
真正宣告一个对象死亡,至少要经过两次标记过程(代码示例):
- 在可达性分析算法中不可达的对象,会被第一次标记为并且进行一次筛选:
筛选条件是是否有必要执行 finalize 方法(对象没有覆盖了 finalize 方法或者对象的这个方法已经被虚拟机执行过,将视为没有必要执行)
- 如果被判定为有必要执行 finalize()方法,那么对象会放置在 F-Queue 队列中,由一个低优先级的
Finalizer 线程去执行它.稍后的 GC 将对 F-Queue 中的对象进行第二次标记,如果此时对象又和引用链上的其他对象进行了关联(比如把 this 赋值给某个类变量),那么它将被移除出即将回收的集合,否则将被真正回收
方法区中的垃圾回收
垃圾回收算法
-
标记-清除算法(Mark-Sweep)
- 基本思路: 首先标记出所有需要回收的对象,在标记完成后统一回收
- 主要不足: 一是效率问题,标记和清除两个过程效率都不高;另一个是空间问题,
标记清除后会产生大量不连续的内存碎片
-
复制算法(Copy)
- 基本思路: 将内存按容量划分为大小相等的两块,每次只使用其中的一块;当这一块的内存用完了,就将还存活着的对象复制到另一块上面,然后把已经使用过的那块内存一次清理掉.
- 优点: 内存分配时不用考虑内存碎片的问题
- 缺点: 内存利用率较低,只利用了一半;对象存活率较高时复制操作太多
- 现在的商业虚拟机都采用这种算法来回收新生代
-
标记-整理算法(Mark-Compact)
- 基本思路: 标记过程和就”标记-清除”算法一样,然后让所有存活的对象都向一端移动,然后清理掉边界以外的内存
- 优点: 没有内存碎片
- 适用于老年代
垃圾收集器
GC 类型
- 新生代 GC(MinorGC / YoungGC): 发生在新生代的垃圾收集动作,非常频繁,速度较快
- 老年代 GC(MajorGC / FullGC): 发生在老年代的 GC,一般会伴随至少一次的 MinorGC,
速度一般会比 MinorGC 慢十倍以上
内存分配与回收策略
- 对象优先在新生代 Eden 分配,没有足够空间时发起一次Minor GC
- 大对象直接进入老年代
-
长期存活的对象将进入老年代
- 每
熬过
一次 MinorGC 的对象年龄增加一岁
- 达到一定年龄(MaxTenuringThreshold,默认为 15)的对象晋升到老年代
-
动态对象年龄判定
- 如果在 Survivor 空间中相同年龄的所有对象大小总和大于 Survivor 空间的一半,
则年龄大于或者等于该年龄的对象就可以直接进入老年代,无需等到 MaxTenuringThreshold
-
空间分配担保
- 每次发生 MinorGC 之前,会检查老年代最大可用连续空间是否大于新生代的所有空间
- 如果成立,则可以确保 MinorGC 是安全的
- 如果不成立,则检查 HandlePromotionFailure(是否允许担保失败)
- HandlePromotionFailure 为 true,继续检查最大的可用连续空间是否大于历次晋升到老年代对象的平均大小
- 如果小于,或者 HandlePromotionFailure 为 false,则改为进行 FullGC
Question
-
GC 是什么?为什么要有 GC
-
什么时候会导致垃圾回收
-
GC 是怎么样运行的
-
新老以及永久区是什么
-
GC 有几种方式?怎么配置
-
什么时候一个对象会被 GC? 如何判断一个对象是否存活
-
System.gc() Runtime.gc()会做什么事情? 能保证 GC 执行吗
-
垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?
-
Minor GC 、Major GC、Young GC 与 Full GC 分别在什么时候发生
-
垃圾回收算法的实现原理
-
如果对象的引用被置为 null,垃圾收集器是否会立即释放对象占用的内存?
-
垃圾回收的最佳做法是什么
-
GC 收集器有哪些
-
垃圾回收器的基本原理是什么?
-
串行(serial)收集器和吞吐量(throughput)收集器的区别是什么
-
Serial 与 Parallel GC 之间的不同之处
-
CMS 收集器 与 G1 收集器的特点与区别
-
CMS 垃圾回收器的工作过程
-
JVM 中一次完整的 GC 流程是怎样的? 对象如何晋升到老年代
-
吞吐量优先和响应优先的垃圾收集器选择
-
举个实际的场景,选择一个 GC 策略
-
JVM 的永久代中会发生垃圾回收吗
-
标记清除、标记整理、复制算法的原理与特点?分别用在什么地方
-
如果让你优化收集方法,有什么思路