1、什么是垃圾回收
JVM内存结构包括五大块:虚拟机栈、本地方法栈、程序计数器、方法区、堆。其中虚拟机栈、本地方法栈、程序计数器属于线程私有的,它们随着线程生而生,随着线程死而死。当它们死的时候内存自然就被回收了。而堆、方法区属于所有线程公有的。只要程序还在机器少跑没关掉,堆和方法区就要一直存在。所以不能其他线程没死,这两个就死了。当是内存也是有限的,当堆和方法区的内存不够用的时候也就对它们的内存中的垃圾进行清理,以便有足够的空间让新的信息进来。这和我们的电脑一样,内存不够了,就开360清垃圾了。
2、怎么判断哪些是垃圾
1、引用计数算法
当对象被创建时,就给该对象添加一个引用计数器,每当有一个地方引用了它,计数器就+1,当有地方引用失效时,计数器就-1。若计数器值为0时,表示没有对象引用它了,这时就通知垃圾回收器回收它们。
- 优点:算法实现简单,效率高
- 缺点:无法解决循环引用问题
2、可达性分析算法
现在主流的语言(java,c)都是通过可达性分析来判定对象是否存活的。
可达性分析的思路是:通过一系列的GC Roots
的对象作为起始点,从这些节点开始向下搜索,所走过的路径称为引用链。当一个对象没有在任何的引用链下的时候(或者说从GC Roots往下找找不到对象),证明该对象是不可用的,则它们会被判定为可回收对象。
在Java语言中,可作为GC Roots的对象包括下面几种:
- 虚拟机栈中引用的对象(栈帧中的本地变量表);
- 方法区中类静态属性引用的对象;
- 方法区中常量引用的对象;
- 本地方法栈中JNI(Native方法)引用的对象。
继续说刚才那些被判定为可回收对象的对象,它们真的已经死亡了吗?答案是没有。
要判定一个对象是否已经死亡,需要经过两次标记过程:
第一次:如果对象在进行可达性分析后,发现它没有在GC Roots
引用链中,则它将会被第一次标记,并且进行筛选。筛选的条件是该对象是否有必要执行finalize()方法。当对象没有重写finalize()方法
,或者finalize()
方法已经被执行过后(finalize()方法只能被执行一次),则判定没有必有执行,直接回收。
第二次:对象被筛选后判定为有必要执行finalize()方法,那么这个对象将会被放置在一个叫做F-QUEUE
的队列中(自救队列),并在稍后由虚拟机自动创建一个低优先级的Finalizer
的线程去触发该对象的finalize()方法。finalize()方法是对象自救的最后一次方法,如果对象在finalize()方法中重新与引用链上的任意对象建立关联,则对象自救成功,在第二次标记时移出“即将回收的集合”。如果自救失败,则该对象被回收。
3、回收方法区
方法区中也是有垃圾回收的。Java虚拟机规范中不要求虚拟机在方法去实现垃圾回收,这是因为在方法区中实现垃圾回收性价比低,效率低。
方法区的垃圾回收内容包含两部分:废弃常量和无用的类。回收常量和回收Java堆中的对象非常相似,都是判断其他地方有没有引用这个常量。“无用的类”判定条件就比较苛刻。满足三个条件才能算是无用的类:
- 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。
- 加载该类的ClassLoader已经被回收。
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
当然并不是满足3个条件的类就要一定要回收。HotSpot虚拟机提供多种参数供使用者选择。(与类回收有关)在大量使用反射,动态代理,动态生生JSP这类频繁自定义ClassLoader场景需要虚拟机具备类卸载功能,保证永久代不会发生溢出
3、引用
不管是引用计数算法还是可达性分析算法,判定对象是否存活都与引用有关。在JDK1.2后,java对引用的概念进行时扩充,将引用分为强引用,软引用,弱引用,虚引用,这4种引用强度依次逐渐减弱。
3.1、强引用
强引用在程序种普遍存在,比如 String a = new String(abc)
。只要强引用还在,垃圾回收器永远不会回收掉被引用的对象。
3.2、软引用
软引用用来描述一些有用但非必要存在的对象。软引用关联的对象,在内存即将溢出前,垃圾回收器会将这些对象回收。
3.3、弱引用
弱引用也用来描述非必须对象,强度比软引用弱。弱引用关联的对象只能生存到下一次垃圾回收发生前。当垃圾回收发生时,不管内存够不够,弱引用关联的对象都会被回收。
3.4、虚引用
虚引用是强度最弱的引用。虚引用对对象的生存时间没有影响。为一个对象设置一个虚引用关联唯一目的就是在该对象被收集器回收前可以收到一个系统的通知。