目录
概述
垃圾收集(Garbage Collection,下文简称GC)诞生于1960年麻省理工学院,应用于古老的Lisp语言,远比java的历史久远, 不要把GC看成是伴随java而生的产物 。
C和C++对内存的使用和回收都是手动的,程序员手动分配内存,在程序结束时手动回收内存,如此操作增加了程序员开发的工作量,降低了开发效率;同时手动分配内存会容易出现【忘记回收】和【多次回收】的问题。
Java采用了自动回收内存的策略,编程简单,开发效率高,不会出现手动回收的问题,同样采用自动回收的还有C#,Python,Go等高级编程语言。
什么是垃圾?
java中什么是垃圾呢?很简单,已经死了的对象就是垃圾。
如何判断一个对象已经死了呢?
如上图所示有两种方法可以判断对象是否已死,1,引用计数法;2,可达性分析算法
1.1 引用计数法
给对象添加一个引用计数器,当有地方引用该对象时,计数器值会+1;当引用失效时,计数器值会-1;任何时刻计数器值为0的对象,就是死了,就是垃圾了。
引用计数算法(Reference Counting)原理简单,判定效率高,大多数情况下是一个不错的算法。但引用计数法有很多情况不能保证正确工作。譬如:对象之间相互循环引用,如下图:
1.Dog corgi = new Dog();
2.Cat siam = new Cat();
// 1,2两行代码执行后,main方法栈中会生成两个变量corgi,siam,分别指向堆中new的Dog对象和Cat对象,
// 此时Dog对象和Cat对象的引用计数器值都为1。
3.corgi.cat = siam;
4.siam.dog = corgi;
// 3,4两行代码执行后,Dog对象的cat属性指向Cat对象,Cat对象的dog属性指向Dog对象,
// 此时Dog对象和Cat对象的引用计数器值都为2。
5.corgi = null;
6.siam = null;
// 5,6两行代码执行后,栈中corgi变量和siam变量指向堆中的对象的引用(1)(2)两条引用箭头消失,
// 此时Dog对象和Cat对象的引用计数器值都为1。
main方法执行完后,main方法的栈帧会被弹栈,main方法创建的对象应该是垃圾的被回收,但是Dog对象和Cat对象的引用计数器值仍然为1,不为0,导致不能被垃圾收集器回收,成为浮动垃圾,造成内存泄露。
鉴于引用计数算法有一些例外情况导致不能正确完成工作,主流的Java虚拟机里面都没有选用引用计数算法来管理内存,而是用可达性分析算法。
1.2 可达性分析算法
1.2.1 可达性分析算法
通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,即从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的,就是死了,就是垃圾了。
如上图所示,object1,2,3,4通过引用链可以回溯到GC Root根对象,即从GC Roots到这个对象可达,所以object1,2,3,4对象都存活;object5,6,7虽然彼此有引用链,但是不能回溯到GC Root根对象,即从GC Roots到这个对象不可达,object5,6,7死了,就是垃圾对象了;
1.2.2 什么是GC Roots对象呢?
熟悉java虚拟运行时内存构造的同学都知道上边这张图,GC Roots对象蕴含在Java虚拟机栈,Java本地方法栈,方法区中。
1,Java虚拟机栈
虚拟机栈里每个方法栈帧的使用的参数,局部变量,临时变量。
2,方法区
方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
方法区中基本数据类型,异常类,系统类加载器等对应的Class对象。
3,本地方法栈
本地方法栈中JNI(即通常所说的Native方法)引用的对象。
上述1,2,3提到的都是GC Roots对象,和以上对象有引用链关系的对象,都是根可达对象,也就是存活的对象,堆内存其他的对象都是已死的对象,可以被垃圾收集器回收。
书籍:深入理解Java虚拟机:JVM高级特性…佳实践(第3版)周志明