JVM阶段(4)-回收策略
1.概述
回忆一下,运行时数据区哪些部分会产生OOM异常,如果无休止的创建对象,或者类信息,或者往运行时常量池塞数据,都可能导致OOM。为了避免这种情况,就需要去考虑底层的垃圾收集。
那么,哪些内存需要回收。
什么时候来回收。
如何回收。
这三个问题,其实就是垃圾回收器的本质。当然,先从哪些内存需要回收说起。
其实主要就是堆和方法区,其它的都是线程私有,线程挂,直接无。
2.回收策略
关于回收策略,其实大多说的都是堆这一块,哪些对象需要回收。这就用到了引用计数法,以及可达性分析算法。
2.1 引用计数法
引用计数法,首先声明,这个算法Java之中是不用的。可以想想,底层支撑的数据结构,双向链表,注定了两个对象肯定是持有各自的引用的,要用这种算法无法回收。
引用计数法,可以理解为每个对象都有一个引用数,reference counting,当这个数为0时,代表了任何对象都没有引用该对象,它没用了,所以回收。
反向证明下,java内部使用的不是引用计数法。
package com.bo.jvmstudy.thirdchapter;
/**
* @Auther: zeroB
* @Date: 2022/8/31 20:46
* @Description: 证明GC不是引用计数法
* -Xmx20m -Xms20m -XX:+PrintGCDetails
*/
public class ReferenceCountingTest {
//内部类设置个1M空间,明显点
static class Obj{
private Integer[] arr = new Integer[1024*1024];
public Obj obj;
}
public static void main(String[] args) {
Obj obj1 = new Obj();
Obj obj2 = new Obj();
obj1.obj = obj2;
obj2.obj = obj1;
obj1 = null;
obj2 = null;
/*
,[GC (System.gc()) [PSYoungGen: 2170K->504K(6144K)] 10362K->8948K(19968K), 0.0009356 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 504K->0K(6144K)] [ParOldGen: 8444K->710K(13824K)] 8948K->710K(19968K), [Metaspace: 2958K->2958K(1056768K)], 0.0057077 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
*/
//根据日志
System.gc();
}
}
看一下打印出的GC日志:
[GC (System.gc()) [PSYoungGen: 2918K->488K(6144K)] 11110K->9263K(19968K), 0.0009433 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 488K->0K(6144K)] [ParOldGen: 8775K->1008K(13824K)] 9263K->1008K(19968K), [Metaspace: 3356K->3356K(1056768K)], 0.0050389 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 6144K, used 56K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
eden space 5632K, 1% used [0x00000000ff980000,0x00000000ff98e2b8,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 13824K, used 1008K [0x00000000fec00000, 0x00000000ff980000, 0x00000000ff980000)
object space 13824K, 7% used [0x00000000fec00000,0x00000000fecfc110,0x00000000ff980000)
Metaspace used 3363K, capacity 4500K, committed 4864K, reserved 1056768K
class space used 366K, capacity 388K, committed 512K, reserved 1048576K
看样子是执行了两次GC,分析一下各个的含义:
[PSYoungGen: 2918K->488K(6144K)] 11110K->9263K(19968K), 0.0009433 secs]
PSYoungGen,从young这个词就可以看出来,这个是新生代。
6144K,当年新生代可用空间为6144K。即一个survior+Eden的区域大小。
2918K->488K,代表了原先由新生代有2918K的内存占用,GC后剩下的488K。回收了2M,没毛病。
19968K,代表的是整体的堆内存空间,新生代默认是整体堆空间的1/3,这里大致看起来确实是。
11110K->9263K,代表了堆整体从11110K回收到了9263K。
后面的时间就是消耗时间了。
后面的这个FullGC应该是我程序结束后,整体的一个FullGc.整体也可以看出来:
PSYoungGen:新生代
ParOldGen:老年代
Metaspace:元空间
垃圾回收回收的也主要是这几个区域。不过我分配了20M内存,如果换算的话,应该是20480K,但实际使用的只有19968K空间。其它的500多K的空间呢?
因为初学,猜测是另一个不被使用的survior区域。但是,从大小来说,20480/3/10,我这面计算下682.6666666666667,比500要大了不少。而且元空间并不占堆内存。后期回来再看吧。
言归正传,从2918K->488K(6144K)看出来,我们的两个对象还是被回收了。这里证明了引用计数法在java中其实并没有使用。
2.2 可达性分析算法
可达性分析算法,则是从定义的一些根对象(GCRoot)开始,搜索这些根对象直接或者间接所持有的对象,来形成一个个引用链。不在这些引用链上的对象,则代表没有其它对象持有,可以被回收。
那么,可以用作GC ROOT节点有哪些?
在虚拟机栈中(局部变量表)所引用的对象。
在方法区中静态属性所引用的对象。
在方法区中常量所引用的对象。
在本地方法栈JNI中引用的对象。
java虚拟机内部的引用,比如基础数据据类型对应的Class对象,常驻异常对象,以及系统类加载器。
被同步锁所持有的对象。
反应Java虚拟机内部情况的JMXBean,JVMTI注册的回调,本地代码缓存等。(这个没有懂)。
其实在测试OOM导出的hprof文件,也可以看到GCROOT这一系列对象,当然,暂时没有深究,后期学完了统一串一下。