(1)垃圾回收机制的意义
java 语言中一个显著的特点就是引入了java回收机制,是c++程序员最头疼的内存管理的问题迎刃而解,它使得java程序员在编写程序的时候不在考虑内存管理。由于有个垃圾回收机制,java中的额对象不在有“作用域”的概念,只有对象的引用才有“作用域”。垃圾回收可以有效的防止内存泄露,有效的使用空闲的内存;
说到这,不得不提起内存泄漏(memory leak
)和内存溢出(out of memory)
(2)内存泄漏
和内存溢出
内存泄漏:
是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
内存溢出:
指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。
内存泄露量大到一定程度会导致内存溢出。但是内存溢出不一定是内存泄露引起的。
(3)内存泄漏的分类(按发生方式来分类)
常发性内存泄漏:
发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
偶发性内存泄漏:
发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
一次性内存泄漏:
发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。
隐式内存泄漏:
程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏
(4)内存溢出原因
- 内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
- 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
- 代码中存在死循环或循环产生过多重复的对象实体;
- 使用的第三方软件中的BUG;
- 启动参数内存值设定的过小;
(5)GC
Android触发垃圾回收机制的时机是无法确定的,但是可以执行
System.gc();
主动触发垃圾回收机制。
(6)Android代码中的堆和栈
了解垃圾回收机制,必须了解堆和栈的关系,上面是我用画图工具画的简单图。
首先我画了两个矩形区域,一个代表栈空间(负责存储对象的引用),一个代表堆空间(负责存储对象的实例),c是一个引用,存入栈空间,new Object()是对象,存入堆空间,两者存在引用关系,用一根直线来表示。
重点知识:
因为存在引用关系,所以垃圾回收机制是无法回收这个对象的。
当执行以下代码时,引用将会断掉
c = null;
这时,下次垃圾回收机制触发时,Object的实例会被回收。
由于引用存入栈空间占用空间很小,对象存入堆中占用空间很大,所以将堆中无引用的对象回收是垃圾回收机制的主要工作。
那么,是否只要执行了c = null;
,垃圾回收机制就一定会将对象回收呢?
答案是不一定。我再画一个简单图来说明:
这是一个 简单的树形结构,GC Roots
是垃圾回收机制树形结构的根节点,当对象存在引用时,我们就在GC Roots
和对象之间画一条直线,如果设置为null
a = null;
GC Roots
和a的连线就会断开,如图:
这种情况下,当垃圾回收机制触发时,由于a对象和b对象也存在引用关系,所以对象a不会被回收,这样就造成了内存泄漏。
(7)原理
本来想画个图来说一下垃圾回收机制的原理的,后来发现图画的并不咋滴,然后我查找了很多很多有关垃圾回收机制的文章,最后终于找到一篇和我脑海中的图差不多的,Java 技术之垃圾回收机制,这篇文章写得非常不错,只能用牛逼来形容,我也认真读完了,大家看一下这篇文章的这张图
顶端有一个节点是GC Roots
,这是垃圾回收机制树形结构的根节点,其中object 1、object 2、object 3、object 4和GC Roots
存在直接或者间接的引用,object 5、object 6、object 7和GC Roots
不存在引用,那么可以总结为:
- object 1、object 2、object 3、object 4与根节点仍然存在引用;
- object 5、object 6、object 7与根节点不存在引用,所以可以被GC回收。