doc: 阅读jvm文档

This commit is contained in:
asahi
2025-11-27 22:33:24 +08:00
parent 89232ffb86
commit 640560d264

View File

@@ -20,6 +20,14 @@
- [实例数据](#实例数据)
- [对齐填充](#对齐填充)
- [对象的访问定位](#对象的访问定位)
- [垃圾收集器和内存分配策略](#垃圾收集器和内存分配策略)
- [对象是否应当被回收](#对象是否应当被回收)
- [引用计数算法](#引用计数算法)
- [可达性分析算法](#可达性分析算法)
- [引用类型](#引用类型)
- [垃圾回收细节](#垃圾回收细节)
- [回收方法区](#回收方法区)
- [判断类是否可回收](#判断类是否可回收)
# 深入理解java虚拟机
@@ -168,3 +176,76 @@ java程序需要通过虚拟机栈上的reference来操作java堆中具体的对
在Hotspot实现中采用的是直接指针访问方案。
## 垃圾收集器和内存分配策略
在上一章节中提到的java运行时数据区域中java虚拟机栈、本地方法栈、程序计数器三个区域是线程私有的生命周期和线程一致无需考虑垃圾回收问题在方法结束/线程终止时内存会随之回收。
但是,`方法区``java堆内存`这两个区域则不同,内存的分配和回收都是动态的,垃圾收集器主要关注这部分内存。
### 对象是否应当被回收
堆中几乎存放着所有的java对象垃圾收集器在对对内存进行回收前需要确定哪些对象可以被回收哪些对象不能被回收。
#### 引用计数算法
该算法会为对象添加一个引用计数器每当有一个地方引用该对象时计数器加一当引用失效时计数器则减一技术器为0代表该对象不再被引用。
但是java虚拟机并没有采用引用计数算法来管理内存主要原因是其难以解决对象之间循环引用的问题。
#### 可达性分析算法
在主流的商用程序语言JAVA/C#等)的主流实现中,都通过可达性分析来判定对象是否可以被回收的。该算法核心是`根据一系列被称为GC Roots的对象来作为起点从这些节点开始向下搜索搜索时所走过的路径被称为引用链当一个对象GC Roots没有任何的引用链相连接时即从GC Roots到该对象不可达时证明该对象是不可用的可以被回收`
在Java语言中GC Roots包含如下内容
- 虚拟机栈中所引用的对象
- 方法区中静态属性应用的对象
- 方法区中常量所引用的对象
- 本地方法栈中JNI所引用的对象
### 引用类型
在JDK 1.2之后Java对引用的概念进行了扩充引入了如下的引用类型概念
- 强引用Strong Reference
- 软引用Soft Reference
- 弱引用Weak Reference
- 虚引用Phantom Reference
上述四种引用类型,引用强度逐级递减:
- `强引用`:对于强引用类型,只要强引用还存在,垃圾收集器永远不会回收掉被引用对象
- `软引用`:软引用用于描述一些有用但非必须的对象。对于软引用关联着的对象,在系统即将要发生内存溢出的异常之前,会将这些引用列进回收范围之内进行二次回收。如果此次回收之后还没有足够的内存,才会抛出内存溢出异常。
- 即在抛出OOM之前SoftReference会被清空如果清空后内存仍然不够就会实际抛出OOM
- `弱引用`:弱引用用于描述非必须对象,但其强度比软引用更弱一些。被弱引用关联的对象只能生存到下一次垃圾收集发生之前。
- 在垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象
- `虚引用`:虚引用是最弱的引用关系,一个对象是有虚引用存在,完全不会对其生存时间造成影响,也无法通过虚引用获得一个对象实例。
- 为对象设置虚引用关联的唯一目的是能在该对象被收集器回收时收到一个系统通知
### 垃圾回收细节
即使在可达性分析算法中被判定为不可达,对象也并非一定会被回收。要真正宣告对象的死亡,必须经历两次标记过程:
- 如果在可达性分析后发现GC Roots到该对象不可达其将会被第一次标记并进行一次筛选筛选条件是对该对象是否有必要执行finalize方法
- 如果当前对象没有覆盖finalize方法或是finalize方法已经被虚拟机调用过则虚拟机将其视为没有必要执行
- 如果当前对象被判定为需要执行finalize方法那么该对象将会被放置到F-QUEUE的队列中并且稍后由一个`由虚拟机自动建立的、优先级较低的Finalizer线程`去执行。该执行只是触发该方法,并不承诺会等待其执行结束。
- finalize方法是对象逃脱死亡命运的最后一次机会稍后GC将会对F-QUEUE中的对象进行第二次小规模的标记如果对象需要在finalize中避免自己被回收只需将自身和引用链上任一对象建立关联即可例如将自身赋值给引用链上任一对象的成员变量或类变量那么在第二次标记时其将被移除即使回收的集合如果此时对象仍未逃脱其会被真正回收
即对象被GC第一次标记为不可达后只能通过finalize方法来避免自己被回收并且`finalize方法只会被调用一次`。需要覆盖finalize逻辑在finalize方法中重新令自身变得可达此时才能避免被垃圾回收。
> 如果一个对象已经调用过一次finalize并且成功变为可达。那么其再次变为不可达后将不会有再次调用finalize的机会这次对象将会被回收。
通常不推荐使用finalize方法。
### 回收方法区
Java虚拟机规范并不强制要求对方法区进行垃圾回收并且在方法区进行垃圾回收的性价比较低
- 在堆中尤其在新生代中常规应用进行一次垃圾收集一般可以回收70%~95%的空间,但方法区垃圾回收的效率远低于此
方法区的垃圾回收主要回收两部分内容:
- 废弃常量
- 无用类
例如在回收字面量时如果一个字符串“abc"已进入常量池但是当前系统没有任何一个String对象引用常量池中“abc”常量也没有其他地方引用该字面量如果此时发生垃圾回收该常量将会被系统清理出常量池。常量池中其他方法、接口、字段、类的符号引用也与此类似。
##### 判断类是否可回收
判断类是否可回收的条件比较苛刻,必须同时满足如下条件:
- 该类所有实例已经被回收java堆中不存在任何该类的实例
- 加载该类的classloader已经被回收
- 该类对应的java.lang.Class对象没有在其他任何地方被引用无法在其他任何地方通过反射访问该类的方法
在大量使用反射、动态代理、Cglib、动态生成jsp、动态生成osgi等频繁自定义ClassLoader的场景都需要虚拟机具备类卸载功能已保证永久代不会溢出。