《理解Java虚拟机》第三章读书笔记:垃圾收集器与内存分配

时间:2022-12-27 15:27:48

1. 概述

GC需要完成的事情
1、哪些内存需要回收
2、什么时候回收
3、如何回收

2. 对象已死

2.1 引用计数算法

给对象中添加一个引用计算器,每当有一个地方引用它时,计数器加一,当引用失效时,计数器减一
。任何时刻都为0的对象就是不可能再被使用了。

2.2 根搜索算法

通过一系列的名为“GC Root”的对象起始点,从这些节点开始向下搜索,搜索所有走过的路径称为引用链。当一个对象到“GC Root”没有任何引用链相连。则证明对象不可用。

GC root对象包括如下
虚拟栈(栈中的引用变量表)中的引用的对象
方法区中的类静态属性引用的对象
方法区的常量引用的对象
本地方法栈中的JNI(native)方法

2.3 再谈引用

强引用:只要强引用存在,垃圾回收器永远不会回收掉被原因的对象
软引用:在系统发将要发生内存溢出溢出之前,将会把这些对象列进回收范围之中进行第二次回收
弱引用:被关联的对象只能生存到下一次垃圾收集发生之前
虚引用:一个对象是否 有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚yiny取得一个对象实例

2.4 存在还是死亡

对象的死亡至少经过两次标记
第一次:对象从根搜索发现没有与GC Roots相连接的引用链,那它会被第一次标记并且进行一次筛选,筛选条件为对象是否有必要执行finalize()。
第二次:对象执行了finalize()方法,那么这个对象会被放置一个名为F-Queue的队列之中。
代码清单:

package com.one.jvm;

/**
* 测试对象只调用一次Finalize()方法
* @author Administrator
*
*/

public class FinalizeEscapeGC {


public static FinalizeEscapeGC escapeGC = null;


@Override
protected void finalize() throws Throwable {
// TODO Auto-generated method stub
super.finalize();
System.out.println("finalize method excuted");
escapeGC = this;
}

public static void main(String[] args) throws InterruptedException {
escapeGC = new FinalizeEscapeGC();

escapeGC = null;
//运行垃圾回收器。
System.gc();
Thread.sleep(600);

if(escapeGC !=null) {
System.out.println("escapeGC Object not null");
} else {
System.out.println("escapeGC Object is null");
}


escapeGC = null;
//运行垃圾回收器。
System.gc();
Thread.sleep(600);

if(escapeGC !=null) {
System.out.println("escapeGC Object not null");
} else {
System.out.println("escapeGC Object is null");
}
}
}

2.5 方法区回收

类需要同时满足下面三个条件才能算无用的类
1、该类所有的实例已经被回收,也就是Java堆中不存在该类的任何实例
2、加载该类的ClassLoder已经被回收
3、该类对应的java.lang.Class对象没有在任何地方被引用,无 法通过任何地方通过反射访问该类的方法

3. 垃圾收集算法

1、标记-清除算法(标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象)
缺点:1、标识与清除是效率不高,2、会产生大量不连续内存碎片
《理解Java虚拟机》第三章读书笔记:垃圾收集器与内存分配

2、复制算法:他将可用的内存按容量划分大小相等的两块,每次只使用其中一块。当这块的内存用完了,就将存活的对象复制到另外一块上面,然后再把已使用过得内存空间一次清理。(新生代比较适合)
《理解Java虚拟机》第三章读书笔记:垃圾收集器与内存分配

3、标记-整理算法:左右存活的对象都往一遍移,然后直接清理掉边界以外的内存(老年代比较适合)
《理解Java虚拟机》第三章读书笔记:垃圾收集器与内存分配

4. 垃圾收集器

垃圾收集器是内存回收的具体实现

5. 内存分配与回收策略

5.1 对象优先在Eden分配

对象在新生代Eden区中分配内存,当Eden去没有足够的空间进行分配时,虚拟机将发起一次Minor GC

package com.one.jvm;

/**
* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* 参数解释
* -Xms20M:设置JVM促使内存为10M。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
* -Xmx20M :设置JVM最大可用内存为20M
* -Xmn10M :设置年轻代大小为10M。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
* -XX:PrintGCDetails,开启详细GC日志模式
* -XX:SurvivorRatio=8:年轻代中Eden区与两个Survivor区的比值(8:1)
* @author Administrator
*
*/

public class EdenAllocation {

private static final int _1MB = 1024*1024;

public static void main(String[] args) {

byte[] eden4 = new byte[2*_1MB];
byte[] eden5 = new byte[2*_1MB];
byte[] eden6 = new byte[2*_1MB];
//新生代垃圾回收
byte[] eden7 = new byte[2*_1MB];
}
}

日志

[GC [PSYoungGen: 6495K->304K(9216K)] 6495K->6448K(19456K), 0.0091599 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC [PSYoungGen: 304K->0K(9216K)] [PSOldGen: 6144K->6328K(10240K)] 6448K->6328K(19456K) [PSPermGen: 2967K->2967K(21248K)], 0.0069367 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 9216K, used 2375K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 29% used [0x00000000ff600000,0x00000000ff851f98,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
PSOldGen total 10240K, used 6328K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 61% used [0x00000000fec00000,0x00000000ff22e140,0x00000000ff600000)
PSPermGen total 21248K, used 2984K [0x00000000f9a00000, 0x00000000faec0000, 0x00000000fec00000)
object space 21248K, 14% used [0x00000000f9a00000,0x00000000f9cea3a0,0x00000000faec0000)

日志参数含义

  • 第一行GC是指发生在新生代的垃圾收集动作,PSYoungGen表示Parallel Scavenge垃圾回收器,
    6495K->304K(9216K):6495K表示新生代垃圾回收前的大小,304K表示新生代垃圾回收后的大小。9216K表示新生代的大小。6495K->6448K(19456K):6495K表示整个堆回收前的大小,6448K表示整个堆回收后的大小,19456K表示整个堆内存分配大小

  • 第二行Full GC指发生在老年代的垃圾收集动作,PSYoungGen表示Parallel
    Scavenge垃圾回收器,304K->0K(9216K):304K表示新生代垃圾回收前的大小,0K表示新生代垃圾回收后的大小。 PSOldGen表示垃圾回收器,6144K->6328K(10240K):6144K表示老年代垃圾回收前的大小,6328K表示老年代垃圾回收后的大小。10240K表示整个老年代的大小。10240K表示整个老年代的大小。6448K->6328K(19456K):6448K表示整个堆回收前的大小,6328K表示整个堆回收的大小,19456K表示整个堆内存

GC内存走向分析

  • 第一次新生代GC的内存走向分析
    6495K->304K,新生代回收的内存大小(6191K=6495K-304K)。发出这样的疑问?6191K到底去向何处。分析一下,
    6495K->6448K,整个堆回收的大小(47K)=6495K -
    6448K;剩下内存去那儿了(6191K-47K=6144K),哈哈,6144K去老年代了(有一段日志:[PSOldGen:
    6144K->6328K(10240K)])
  • 第二次老年代GC的内存走向分析
    304K->0K,在进行老年代GC时,首先把新生代的内存回收完毕。那么会产生一个疑问,清理的304K去向何处?分析一下,6144K->6328K,老年代增加了184K,整个内存堆回收了(6448K->6328K)120K。184K+120K=304K。

5.2 大对象直接进入老年代

所谓的大对象是指,需要大量连续内存空间的java对象,

package com.one.jvm;

/**
* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* 参数解释
* -Xms20M:设置JVM促使内存为10M。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
* -Xmx20M :设置JVM最大可用内存为20M
* -Xmn10M :设置年轻代大小为10M。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
* -XX:PrintGCDetails,开启详细GC日志模式
* -XX:SurvivorRatio=8:年轻代中Eden区与两个Survivor区的比值(8:1)
* @author Administrator
*
*/

public class EdenAllocation {

private static final int _1MB = 1024*1024;

public static void main(String[] args) {

byte[] eden1 = new byte[8*_1MB];

}
}

GC日志:
Heap
PSYoungGen total 9216K, used 515K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 6% used [0x00000000ff600000,0x00000000ff680ee8,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
PSOldGen total 10240K, used 8192K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 80% used [0x00000000fec00000,0x00000000ff400010,0x00000000ff600000)
PSPermGen total 21248K, used 2975K [0x00000000f9a00000, 0x00000000faec0000, 0x00000000fec00000)
object space 21248K, 14% used [0x00000000f9a00000,0x00000000f9ce7e48,0x00000000faec0000)

长期存活的对象进入老年代

动态对象年龄判定

空间分配担保