“深入理解JVM:垃圾收集器与内存分配策略”的版本间差异

来自Wikioe
跳到导航 跳到搜索
第12行: 第12行:
 
 
== 对象已死? ==
== 对象已死? ==
3.2.1 引用计数算法
=== 引用计数算法(Reference Counting)===
3.2.2 可达性分析算法
引用计数算法:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。
3.2.3 再谈引用
* 优点:原理简单,判定效率高;
3.2.4 生存还是死亡?
* 缺点:不能解决'''对象之间相互循环引用'''的问题;
3.2.5 回收方法区
*: <syntaxhighlight lang="java">
/**
* testGC()方法执行后,objA和objB会不会被GC呢?
* @author zzm
*/
public class ReferenceCountingGC {
  public Object instance = null;
  private static final int _1MB = 1024 * 1024;
 
  /**
  * 这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否有回收过
  */
  private byte[] bigSize = new byte[2 * _1MB];
 
  public static void testGC() {
      ReferenceCountingGC objA = new ReferenceCountingGC();
      ReferenceCountingGC objB = new ReferenceCountingGC();
      objA.instance = objB;
      objB.instance = objA;
     
      objA = null;
      objB = null;
     
      // 假设在这行发生GC,objA和objB是否能被回收?
      System.gc();
  }
}
</syntaxhighlight>
 
=== 可达性分析算法(Reachability Analysis)===
* 当前主流的商用程序语言(Java、C#,上溯至前面提到的古老的Lisp)的内存管理子系统,都是通过可达性分析(Reachability Analysis)算法来判定对象是否存活的。
 
可达性分析算法:通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。
 
: [[File:利用可达性分析算法判定对象是否可回收.jpg|600px]]
 
 
Java中可作为“GC Roots”的对象:【看起来能从它往下找的对象】
* '''在虚拟机栈(栈帧中的本地变量表)中引用的对象''';
*: 譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
* '''在方法区中类静态属性引用的对象''';
*: 譬如Java类的引用类型静态变量。
* '''在方法区中常量引用的对象''';
*: 譬如字符串常量池(String Table)里的引用。
* '''在本地方法栈中JNI(即通常所说的Native方法)引用的对象'''。
* Java虚拟机内部的引用;【?】
*: 如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
* 所有被同步锁(synchronized关键字)持有的对象。【???】
* 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。【?】
 
=== 再谈引用(强、软、弱、虚)===
判定对象是否存活都和“引用”离不开关系。JDK 1.2版之后,Java将引用分为强、软、弱、虚四种:
# 强引用(Strongly Reference):是指在程序代码之中普遍存在的引用赋值,即类似“Objectobj = new Object()”这种引用关系。
#: 只要强引用关系还存在,就永远不会回收掉被引用的对象。
# 软引用(Soft Reference):JDK 1.2版之后提供了“SoftReference”类来实现软引用。
#: 在系统将要'''发生内存溢出异常前''',会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
# 弱引用(Weak Reference):在JDK 1.2版之后提供了“WeakReference”类来实现弱引用。
#: 被弱引用关联的对象只能生存到下一次垃圾收集发生为止。
# 虚引用(Phantom Reference):也称为“幽灵引用”或者“幻影引用”,在JDK 1.2版之后提供了“PhantomReference”类来实现虚引用。
#: 虚引用的对于对象的生存时间没有影响,也无法通过虚引用来取得一个对象实例。(唯一作用是在对象被收集器回收时收到一个系统通知)
 
=== 生存还是死亡? ===
真正宣告一个对象死亡,至少要经历两次标记过程:
# 标记:对象在进行可达性分析后发现没有与GC Roots相连接的引用链,将会被第一次标记;
# 筛选(对标记的对象):筛选的条件是此对象是否有必要执行“finalize()”方法;
## “没有必要执行”:【对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过】直接回收;
## “有必要执行”:将该对象放如“F-Queue”队列中,稍后由“Finalizer”(虚拟机自动建立的低优先级)线程去调用对象的“finalize()”方法;
##: 并不承诺一定会等待对象的“finalize()”方法运行结束(防止等待造成回收系统崩溃);
# 标记(对F-Queue中的对象):被标记的对象会被回收;
 
 
* finalize()方法中:如果对象重新与引用链上的对象建立了关联【如把自己(this关键字)赋值给某个类变量或者对象的成员变量】,那么在第二次标记时它将被移出“即将回收”的集合;
* finalize()方法的不确定性大:不确定是否一定被调用,不确定何时被调用;所以应尽量避免使用;
 
 
一次对象自我拯救的演示:
<syntaxhighlight lang="java">
/**
* 此代码演示了两点:
* 1.对象可以在被GC时自我拯救。
* 2.这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次
* @author zzm
*/
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;
public void isAlive() {
System.out.println("yes, i am still alive :)");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed!");
FinalizeEscapeGC.SAVE_HOOK = this;
}
public static void main(String[] args) throws Throwable {
SAVE_HOOK = new FinalizeEscapeGC();
//对象第一次成功拯救自己
SAVE_HOOK = null;
System.gc();
// 因为Finalizer方法优先级很低,暂停0.5秒,以等待它
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("no, i am dead :(");
}
// 下面这段代码与上面的完全相同,但是这次自救却失败了
// (finalize已被执行过,筛选后直接被回收)
SAVE_HOOK = null;
System.gc();
// 因为Finalizer方法优先级很低,暂停0.5秒,以等待它
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("no, i am dead :(");
}
}
}
</syntaxhighlight>
运行结果:
<syntaxhighlight lang="java">
finalize method executed!
yes, i am still alive :)
no, i am dead :(
</syntaxhighlight>
 
=== 回收方法区 ===
方法区的垃圾收集主要回收两部分内容:
# 废弃常量:没有被对象或对象属性引用的常量(与回收Java堆中的对象类似);
# 不再使用的类:需要同时满足下面三个条件:
## 该类所有的实例都已经被回收;
##: 也就是Java堆中不存在该类及其任何派生子类的实例。
## 加载该类的类加载器已经被回收;
##: (这个条件除非是经过精心设计的可替换类加载器的场景,如OSGi、JSP的重加载等,否则通常是很难达成的。)
## 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
#* Java虚拟机被允许对满足上述三个条件的无用类进行回收,这里说的仅仅是“被允许”,而并不是和对象一样,没有引用了就必然会回收。
 
 
<pre>
关于是否要对类型进行回收,HotSpot虚拟机提供了“-Xnoclassgc”参数进行控制,还可以使用“-verbose:class”以及“-XX:+TraceClass-Loading”、“-XX:+TraceClassUnLoading”查看类加载和卸载信息,其中“-verbose:class”和“-XX:+TraceClassLoading”可以在Product版的虚拟机中使用,“-XX:+TraceClassUnLoading”参数需要FastDebug版的虚拟机支持。
</pre>
 
* 在大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及OSGi这类频繁自定义类加载器的场景中,通常都需要Java虚拟机具备类型卸载的能力,以保证不会对方法区造成过大的内存压力。
【???】


== 垃圾收集算法 ==
== 垃圾收集算法 ==

2020年10月24日 (六) 03:05的版本


概述

垃圾收集(Garbage Collection,GC),并非Java语言的伴生产物。


垃圾收集需要完成的三件事情:

  1. 哪些内存需要回收?
  2. 什么时候回收?
  3. 如何回收?

 

对象已死?

引用计数算法(Reference Counting)

引用计数算法:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。

  • 优点:原理简单,判定效率高;
  • 缺点:不能解决对象之间相互循环引用的问题;
    /**
    * testGC()方法执行后,objA和objB会不会被GC呢?
    * @author zzm
    */
    public class ReferenceCountingGC {
       public Object instance = null;
       private static final int _1MB = 1024 * 1024;
       
       /**
       * 这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否有回收过
       */
       private byte[] bigSize = new byte[2 * _1MB];
       
       public static void testGC() {
          ReferenceCountingGC objA = new ReferenceCountingGC();
          ReferenceCountingGC objB = new ReferenceCountingGC();
          objA.instance = objB;
          objB.instance = objA;
          
          objA = null;
          objB = null;
          
          // 假设在这行发生GC,objA和objB是否能被回收?
          System.gc();
       }
    }
    

可达性分析算法(Reachability Analysis)

  • 当前主流的商用程序语言(Java、C#,上溯至前面提到的古老的Lisp)的内存管理子系统,都是通过可达性分析(Reachability Analysis)算法来判定对象是否存活的。

可达性分析算法:通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。

利用可达性分析算法判定对象是否可回收.jpg


Java中可作为“GC Roots”的对象:【看起来能从它往下找的对象】

  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象
    譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
  • 在方法区中类静态属性引用的对象
    譬如Java类的引用类型静态变量。
  • 在方法区中常量引用的对象
    譬如字符串常量池(String Table)里的引用。
  • 在本地方法栈中JNI(即通常所说的Native方法)引用的对象
  • Java虚拟机内部的引用;【?】
    如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
  • 所有被同步锁(synchronized关键字)持有的对象。【???】
  • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。【?】

再谈引用(强、软、弱、虚)

判定对象是否存活都和“引用”离不开关系。JDK 1.2版之后,Java将引用分为强、软、弱、虚四种:

  1. 强引用(Strongly Reference):是指在程序代码之中普遍存在的引用赋值,即类似“Objectobj = new Object()”这种引用关系。
    只要强引用关系还存在,就永远不会回收掉被引用的对象。
  2. 软引用(Soft Reference):JDK 1.2版之后提供了“SoftReference”类来实现软引用。
    在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
  3. 弱引用(Weak Reference):在JDK 1.2版之后提供了“WeakReference”类来实现弱引用。
    被弱引用关联的对象只能生存到下一次垃圾收集发生为止。
  4. 虚引用(Phantom Reference):也称为“幽灵引用”或者“幻影引用”,在JDK 1.2版之后提供了“PhantomReference”类来实现虚引用。
    虚引用的对于对象的生存时间没有影响,也无法通过虚引用来取得一个对象实例。(唯一作用是在对象被收集器回收时收到一个系统通知)

生存还是死亡?

真正宣告一个对象死亡,至少要经历两次标记过程:

  1. 标记:对象在进行可达性分析后发现没有与GC Roots相连接的引用链,将会被第一次标记;
  2. 筛选(对标记的对象):筛选的条件是此对象是否有必要执行“finalize()”方法;
    1. “没有必要执行”:【对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过】直接回收;
    2. “有必要执行”:将该对象放如“F-Queue”队列中,稍后由“Finalizer”(虚拟机自动建立的低优先级)线程去调用对象的“finalize()”方法;
      并不承诺一定会等待对象的“finalize()”方法运行结束(防止等待造成回收系统崩溃);
  3. 标记(对F-Queue中的对象):被标记的对象会被回收;


  • finalize()方法中:如果对象重新与引用链上的对象建立了关联【如把自己(this关键字)赋值给某个类变量或者对象的成员变量】,那么在第二次标记时它将被移出“即将回收”的集合;
  • finalize()方法的不确定性大:不确定是否一定被调用,不确定何时被调用;所以应尽量避免使用;


一次对象自我拯救的演示:

/**
* 此代码演示了两点:
* 1.对象可以在被GC时自我拯救。
* 2.这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次
* @author zzm
*/
public class FinalizeEscapeGC {
	
	public static FinalizeEscapeGC SAVE_HOOK = null;
	
	public void isAlive() {
		System.out.println("yes, i am still alive :)");
	}
	
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println("finalize method executed!");
		FinalizeEscapeGC.SAVE_HOOK = this;
	}
	
	public static void main(String[] args) throws Throwable {
		SAVE_HOOK = new FinalizeEscapeGC();
		
		//对象第一次成功拯救自己
		SAVE_HOOK = null;
		System.gc();
		// 因为Finalizer方法优先级很低,暂停0.5秒,以等待它
		Thread.sleep(500);
		if (SAVE_HOOK != null) {
			SAVE_HOOK.isAlive();
		} else {
			System.out.println("no, i am dead :(");
		}
		
		// 下面这段代码与上面的完全相同,但是这次自救却失败了
		// (finalize已被执行过,筛选后直接被回收)
		SAVE_HOOK = null;
		System.gc();
		// 因为Finalizer方法优先级很低,暂停0.5秒,以等待它
		Thread.sleep(500);
		if (SAVE_HOOK != null) {
			SAVE_HOOK.isAlive();
		} else {
			System.out.println("no, i am dead :(");
		}
	}
}

运行结果:

finalize method executed!
yes, i am still alive :)
no, i am dead :(

回收方法区

方法区的垃圾收集主要回收两部分内容:

  1. 废弃常量:没有被对象或对象属性引用的常量(与回收Java堆中的对象类似);
  2. 不再使用的类:需要同时满足下面三个条件:
    1. 该类所有的实例都已经被回收;
      也就是Java堆中不存在该类及其任何派生子类的实例。
    2. 加载该类的类加载器已经被回收;
      (这个条件除非是经过精心设计的可替换类加载器的场景,如OSGi、JSP的重加载等,否则通常是很难达成的。)
    3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
    • Java虚拟机被允许对满足上述三个条件的无用类进行回收,这里说的仅仅是“被允许”,而并不是和对象一样,没有引用了就必然会回收。


关于是否要对类型进行回收,HotSpot虚拟机提供了“-Xnoclassgc”参数进行控制,还可以使用“-verbose:class”以及“-XX:+TraceClass-Loading”、“-XX:+TraceClassUnLoading”查看类加载和卸载信息,其中“-verbose:class”和“-XX:+TraceClassLoading”可以在Product版的虚拟机中使用,“-XX:+TraceClassUnLoading”参数需要FastDebug版的虚拟机支持。
  • 在大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及OSGi这类频繁自定义类加载器的场景中,通常都需要Java虚拟机具备类型卸载的能力,以保证不会对方法区造成过大的内存压力。

【???】

垃圾收集算法

3.3.1 分代收集理论 3.3.2 标记-清除算法 3.3.3 标记-复制算法 3.3.4 标记-整理算法

HotSpot的算法细节实现

3.4.1 根节点枚举 3.4.2 安全点 3.4.3 安全区域 3.4.4 记忆集与卡表 3.4.5 写屏障

经典垃圾收集器

3.5.1 Serial收集器 3.5.2 ParNew收集器 3.5.3 Parallel Scavenge收集器 3.5.4 Serial Old收集器 3.5.5 Parallel Old收集器 3.5.6 CMS收集器 3.5.7 Garbage First收集器

低延迟垃圾收集器

3.6.1 Shenandoah收集器 3.6.2 ZGC收集器

选择合适的垃圾收集器

3.7.1 Epsilon收集器 3.7.2 收集器的权衡 3.7.3 虚拟机及垃圾收集器日志 3.7.4 垃圾收集器参数总结

内存分配与回收策略

 大对象优先在Eden分配 3.8.2 大对象直接进入老年代 3.8.3 长期存活的对象将进入老年代 3.8.4 动态对象年龄判定 3.8.5 空间分配担保