## 前置知识
- 静态变量在类被加载的时候分配内存.当我们启动一个App的时候,系统会创建一个进程,此进程会加载一个JVM的实例,然后代码就运行在JVM之上.也就是说类在被加载的时候,静态变量
---
判断一个对象是否有垃圾
判断一个对象是否存在垃圾需要回收,目前有两种算法.
引用计数法
Java是通过引用去和对象进行关联的,如果要操作对象,则必须通过引用去完成.这样就有一个简单粗暴的方法可以去判断某个对象是否是"垃圾",如果一个对象没有任何的引用与之关联,则该对象无法在其他地方被用到,那么该对象则可以作为被回收对象. 此种方法为引用计数法,虽然这样简单粗暴,效率奇高,但是也存在着知名的问题,如果存在某些对象被循环引用,则该算法无法成功进行垃圾回收.
代码如下:
public class test { public test instance; public static void main(String[] args) { test a = new test(); test b = new test(); a.instance = b; b.instance = a; a = null; b = null; }}
> 虽然变量a和变量b已经赋值为null,但是在堆内存中a的instance指向b,b的instance指向a, 这样依然存在着引用,所以引用计数法无法解决该问题.
### 可达算法
GC roots:
虚拟机栈中引用的对象,每个方法执行的时候,jvm都会创建一个相应的栈帧(操作数栈,局部变量表,运行时常量池),栈帧中包含着该方法内部使用的所有对象的引用(当然也包括其他的一些基本数据类型),当方法执行完毕的时候,该栈帧会从虚拟机栈中弹出,这样临时创建的对象的引用也就不在了,或者说没有任何GC roots指向这些对象,这些对象会在下一次的 垃圾回收时 被回收掉.
方法区中类静态属性引用的对象,静态属性是该类的属性,不属于任何的单独实例,是被该类下所有对象共有的,因此该属性自然会作为GC roots只要该类存在,该引用指向的对象也会一直存在,类也是会被回收的,后面有.
本地方法栈引用的对象.
软引用(softReference)将在内存溢出的时候,被判定为可回收对象.弱引用(weakRederence)会在下一轮的内存回收中作为可回收对象.
---
目前垃圾回收算法有四种:标记清除法,标记压缩法,复制法,引用计数法.目前流行的垃圾回收算法一般是上面集中算法的取长补短.
标记清除法
mutator:
- 垃圾收集器之外的部分,其职责一般是new,read,write.
- mutator roots,mutator根对象一般指的是分配在堆内存之外,可以直接被mutator直接访问到的对象,一般是指静态/全局变量以及thread local变量.
collector:
- 垃圾收集器,回收不再使用的内存以供mutator继续new.
可达对象:
- 从mutator根对象开始进行遍历,可以被访问到的对象都是可达对象,这些对象也是mutator(目前运行的应用程序)正在使用的对象.
标记-清除
- 标记阶段 : cellector从mutator根对象开始进行遍历,对从mutator根对象开始就可以访问到的对象都打上一个标识,一般实在对象的header中,将其记录为可达对象.
这样就完成了标记的工作.
- 清除阶段 : collector从堆内存从头至尾开始线性遍历,如果发现某个对象没有被标记为可达对象,则将其回收.
标记阶段mutator有1,2,3. 其中1可以到达B,B可以到达E 则B,E为可达对象,同理得其他可达对象.
> 清除阶段所有的非可达对象都会被回收.同时collector在进行标记和清除的阶段因为设计到修改和访问内存,所以需要暂停整个应用程序,等待标记清除结束之后回复程序运行状态. (Stop The World ???)
---
下面附上垃圾回收的伪代码:
New(): ref <- allocate() //分配新的内存到ref指针 if ref == null collect() //内存不足,则触发垃圾收集 ref <- allocate() if ref == null throw "Out of Memory" //垃圾收集后仍然内存不足,则抛出Out of Memory错误 return refatomic collect(): markFromRoots() sweep(HeapStart,HeapEnd)
下面是对应的标记算法:
markFromRoots(): worklist <- empty foreach fld in Roots //遍历所有mutator根对象 ref <- *fld if ref != null && isNotMarked(ref) //如果它是可达的而且没有被标记的,直接标记该对象并将其加到worklist中 setMarked(ref) add(worklist,ref) mark()mark(): while not isEmpty(worklist) ref <- remove(worklist) //将worklist的最后一个元素弹出,赋值给ref foreach fld in Pointers(ref) //遍历ref对象的所有指针域,如果其指针域(child)是可达的,直接标记其为可达对象并且将其加入worklist中 //通过这样的方式来实现深度遍历,直到将该对象下面所有可以访问到的对象都标记为可达对象。 child <- *fld if child != null && isNotMarked(child) setMarked(child) add(worklist,child)
下面附上清除伪代码:
sweep(start,end): scan <- start while scan < end if isMarked(scan) setUnMarked(scan) else free(scan) scan <- nextObject(scan)
---
缺点: 标记清除算法比较大的缺点就是垃圾收集后可能会造成大量的内存碎片,如果需要超出碎片长度的连续空间的时候,可能会造成"Out of Memory"的情况.