您的位置 首页 java

你必须知道的java虚拟机之GC篇——针对堆和方法区的GC

一、垃圾回收的范围

当Lisp还在胚胎时期时,其作者John McCarthy就思考过垃圾收集需要完成的三件事情:

1.哪些内存需要回收

2.什么时候回收

3.如何回收

垃圾回收范围确定

仔细看过本系列的第一章运行时数据区域介绍的同学应该还记得我们的口诀一器一区一堆两栈,我们知道程序计数器、虚拟机栈和本地方法栈都是线程私有的,也就是说线程创建之后,这几个区域就会应运而生,等到线程正常结束,这几个区域也就对应被回收了,所以说这几个区域不是我们这一章所关注的重点, 我们这几个篇幅主要围绕线程共享的方法区和java堆的内存回收。

虽然我们这里的重心是在讨论java堆和方法区的内存回收,但是并不意味着别的区域不会有内存回收的问题,我们在第一章也总结过,除了程序计数器不会发生OOM之外,别的区域都会有OOM的风险。所以同学们要清楚,内存回收是针对内存的,我们这里用堆和方法区这两个线程共享的区域来讲,只是因为这两个比较突出,也最常见。

有点忘记运行时数据区域的同学赶紧恶补一下咯~

二、堆的回收

在堆里面存放着Java世界中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就 是要确定这些对象之中哪些还“存活”着,哪些已经“死去”(“死去”即不可能再被任何途径使用的对象)了。

对象死亡算法

1. 引用计数算法

很好记,字面意思,对象每引用一次计数器加一,每失效一次计数器减一。不知道同学们还记不记得在多线程那个系列里面,我们在讲Synchronize关键字的时候的场景,我们的Synchronize关键字的加锁和释放锁的思路和我们这里要讲的引用计数算法异曲同工。

客观地说,引用计数算法(Reference Counting)虽然占用了一些额外的内存空间来进行计数,但 它的原理简单,判定效率也很高,在大多数情况下它都是一个不错的算法。也有一些比较著名的应用 案例,例如微软COM(Component Object Model)技术、使用ActionScript 3的FlashPlayer、Python语言以及在游戏脚本领域得到许多应用的Squirrel中都使用了引用计数算法进行内存管理。但是,在Java领域,至少主流的Java虚拟机里面都没有选用引用计数算法来管理内存,主要原因是,这个看似简单的算法有很多例外情况要考虑,必须要配合大量额外处理才能保证正确地工作,譬如单纯的引用计数就很难解决对象之间相互循环引用的问题。

2.可达性分析

很多同学会抱怨,这个算法,那个实现,我们学的时候觉得简单,但是隔一段时间问你,突然就想不起来了,所以这边我的一个目的就是让大家更加形象地记住这些点。

可达性分析算法字面意思就是可以到达,所以一个点就是可以到达的节点,到达哪里呢,也就是我们常说的GC Roots集合。再配合下面这张图,相信同学们一下就能明白这个算法的思想了。

你必须知道的java虚拟机之GC篇——针对堆和方法区的GC

了解了这个算法的思想之后引出两个问题

第一个问题:那么哪些节点有资格成为我们的GC root呢?

大致有以下七种情况:

1.在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的 参数、局部变量、临时变量等。

2.在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量

3.在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。

4.在本地方法栈中JNI(即通常所说的Native方法)引用的对象。

5.Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如 NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。

6.所有被同步锁(synchronized关键字)持有的对象。

7.反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

第二个问题:什么样的引用才算是可以到达的?

Java对引用的概念进行了扩充,将引用分为强引用(Strongly Re-ference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱。

强引用 :是指在程序代码之中普遍存在的引用赋值,即类似“Objectobj=new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。

软引用 :是用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内 存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存, 才会抛出内存溢出异常。在JDK 1.2版之后提供了SoftReference类来实现软引用。

弱引用 :是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只 能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只 被弱引用关联的对象。在JDK 1.2版之后提供了WeakReference类来实现弱引用

虚引用 :也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。一个对象是否有虚引用的 存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚 引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2版之后提供 了PhantomReference类来实现虚引用。

三、方法区的回收

方法区于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据 ,通过方法区存放的数据类型我们也大概能猜到方法区回收的主要是不在使用的类和废弃的常量。

常量的回收和对象类似,只要虚拟机中没有一个地方在引用目标常量,那么目标常量就会被移出常量池,同时这部分内存被回收。

类的回收相对来说会复杂一点,需要同时满足三个条件:

1.该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。

2.·加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如 OSGi、JSP的重加载等,否则通常是很难达成的。

3.该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方 法。

由于第二点的存在,所以我们类的卸载其实是很不容易的,究其原因就是如果我们没有自定义类加载器的情况下, 默认的类加载器就是应用类加载器 ,应用类加载器是jvm双亲委派模型中存在的一个加载器,所以这个加载器回收也是很困难的,

在大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及OSGi这类频繁自定义类加载 器的场景中,通常都需要Java虚拟机具备类型卸载的能力,以保证不会对方法区造成过大的内存压 力。

这段话可以这么来理解,因为在大量使用到自定义类加载器的场景下,同一个类可以被多个不同的类加载到方法区中,但是很多时候并不是每一个都是有效的,所以为了避免方法区内存溢出的出现,java虚拟机都会设计类型卸载的能力。


最近真的忙忙忙。。。本章的基本概念希望大家有一个概念,具体的回收操作是怎么来实现的,我们之后会有更为详细的篇章来具体展开讲解~~能坚持到这里的同学都挺厉害的,给自己也点个吧

文章来源:智云一二三科技

文章标题:你必须知道的java虚拟机之GC篇——针对堆和方法区的GC

文章地址:https://www.zhihuclub.com/175662.shtml

关于作者: 智云科技

热门文章

网站地图