diff --git a/jvm/深入理解java虚拟机.md b/jvm/深入理解java虚拟机.md index 4176ad9..86aa4dc 100644 --- a/jvm/深入理解java虚拟机.md +++ b/jvm/深入理解java虚拟机.md @@ -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的场景都需要虚拟机具备类卸载功能,已保证永久代不会溢出。 + + + + +