垃圾回收前言

1.为什么需要垃圾回收

之前的文章讲过java的内存模型。java最主要的两大区域就是堆和栈,而这两个区域大小不是无限大的,我们可以在启动的时候通过xmx xms xmn 这些参数来指定大小。当我们在程序里面不停地创建对象,运行方法,新开线程就会不断地要求JVM给我们分配对应的内存,如果我们一直不去收集那些没用的对象,就会把内存撑爆,导致程序崩溃。但是我们再写代码的时候并没有刻意的指明,我们的程序还可以稳健的运行,这是因为JVM帮我们实现了垃圾回收。如果你要是学C语言的话就得手动去回收垃圾,好在这些JVM已经帮我们实现。

2.如何判断哪些对象可以回收?

垃圾回收的主要区域就是堆区域和方法区。当然也有一部分垃圾回收器不清理方法区。垃圾回收器的英文简写为GC。
现在主流的有两种方法:

1.引用计数法

引用计数法的核心就是这个对象被引用一次,计数加一,被释放的时候引用减一。当计数为0的时候就表明这个对象是可回收的。
但是这有一个问题就是遇到循环引用的时候会出现问题,比如下面这段代码

Object a = new Object();
Object b = new Object();
a=b;
b=a;

那这两个对象永远也不会被回收,因为这四段代码执行完,两个对象的计数都为2,即使把a和b赋值为null,那么两个对象的计数还是为1。导致对象一直不会被回收。所以这种方法一般已经很少使用了。

2.可达性分析法

这也是现在大多数垃圾收集器使用的判断对象是否可用的方法,这个方法是从根节点寻找所有的引用,从而绘制出一条引用引用链。那么不在这条引用链里面的就判断为不可用的。根节点称为GC root。那么有哪些节点可以作为GC root呢。
1.方法区的常量池的引用
2.方法区静态变量的引用
3.虚拟机栈里面的引用
4.本地方法栈的引用

3.Stop The World

JVM在枚举根节点的时候是需要停止所有的工作线程才能开始枚举根节点的,因为要保证在枚举根节点的时候,必须保证引用不再发生变化,否则就永远无法正确的枚举根节点。所以在进行枚举根节点的时候需要停止所有的工作线程,这也就是Stop The World的由来。

4.枚举根节点的过程

现在的GC都是准确GC,就是在编译和类加载的时候就使用一个称为OOP Map的数据结构来保存那个对象的什么偏移量上是什么样的引用类型,以及寄存器和栈上哪那些位置有引用变量。但是问题是程序运行的时候,由大量的指令执行,如果每个命令都记录下哪个位置是哪个引用的话,那么需要记录的数据量也过大。在GC内部也确实没有在所有指令执行的时候都记录
下引用的位置。GC只会在特定的位置记录下引用的位置,然后再执行GC的时候就在程序运行到这个未知的时候执行,这个位置称为安全点。

5.安全点和安全域

安全点不能太少,这样gc的时候需要等待很长时间。也不能太多,这样会增加程序运行时的符合。哪些位置可以作为安全点呢?一般都是指令复用的位置。比如程序里面的方法调用和循环等。这样只有在各个线程都执行到了安全点的时候JVM才会执行GC操作。那么如何保证线程运行到安全点的时候线程会停止运行呢?有两种方式,第一种是***抢先式中断***,这种不需要工作线程的配合。当JVM需要GC的时候会先把所有的工作线层挂起,然后检测如果线程还没有执行到安全点的时候,就会恢复线程等他跑到安全点。现在一般的GC都不采用这种方式。而采用的是另一种方式,主动式中断。这种是需要工作线程配合的,就是简单设置一个标识,当程序运行的时候不断去轮询这个标识。如果有这个标识,那么就把当前线程阻塞,等待GC。
但是这样还有一个问题就是如果有的工作线程在轮询之前已经处于休眠或者阻塞状态的话,那么它是没办法轮询到这个标识的,当然GC不会去等待CPU去调度这个线程之后再去执行GC操作。这就引出了另一个概念。安全域。就是把安全点扩大化,指定一块区域,当线程处于这个区域内垃圾收集器都可以执行GC操作。当线程要离开安全区域的时候,会询问JVM垃圾回收是否完成或者根节点是否枚举完成,如果完成后那么才可以离开。

猜你喜欢

转载自blog.csdn.net/qq_30055391/article/details/84898297