前言
这篇文章是JVM调优系列的第一篇,主要讲关于内存垃圾的内容。
JVM调优是指对JVM性能的调优。而制约JVM运行速度的,就是可运行的内存空间,如果内存空间被大量的垃圾占据,那么JVM的运行速度就会不可避免的降低。
那么什么是垃圾呢?
在JVM中,我们对内存垃圾的定义很简单:内存中没有引用指向的对象。
本文会介绍JVM是如何寻找垃圾的,并且介绍三种回收垃圾内存的机制。
垃圾标记方法
前面我们已经说过,所谓的垃圾就是没有引用指向的对象。那么JVM是用什么方法定位到这些垃圾的呢?
有下面这两种方法:
reference count
引用计数法。
就是对每个对象都追踪指向它们的引用数,如果引用数为0,就说明这个对象是内存垃圾了。
但是这种方法有个缺陷,就是没办法解决垃圾簇问题。
就是说,如果是好几个对象互相引用,但是这些对象作为整体没有被外部引用指向,那么这些实质上的内存垃圾就没办法被标记。
为了解决这个问题,出现了下面这种标记垃圾的方法。
root searching
根可达算法。
指从根对象开始扫描,所有可以被扫到的对象都被标记为存活对象,而扫不到的对象就是垃圾对象。
对扫到的对象一般是在该对象的header中打上一个标识,表示当前对象还是存活对象。
那么什么是根对象呢?主要由下面四种对象构成:
- main方法的栈帧中的对象。
- class文件中的静态变量
- 常量池中的对象
- JNI用到的对象
垃圾清除方法
既然已经知道哪些对象是垃圾了,就要将这些垃圾清除掉。
垃圾清除方法主要有下面三种:
mark-sweep
这种方法分为两个阶段,分别是标记(mark)阶段和清除(sweep)阶段。
其中,标记阶段就是上面提到的垃圾标记算法中的root searching
方法。
而在清除阶段,垃圾收集器就会遍历堆内存,将内存中垃圾对象所占的内存回收。
这种垃圾清除算法是存在缺陷的:
- 产生内存碎片,回收的内存并不连续,难以管理。
- 产生了两次扫描,效率比较低。
copying
为了解决mark-sweep方法存在的问题,又出现了这种垃圾回收算法——copying
。
这种算法首先将内存分为大小相等的两块,每次只使用其中的一块。
当一块内存满了之后,就将还存活的对象copy到另外一块内存上,如此反复。
这种方法虽然避免了内存的碎片化,但同样由下面的问题:
- 造成可用内存的永久减半
- 由于存在复制操作,所以效率会比较低,只适用于存活对象比较少的时候
mark-compact
如果既不想造成内存碎片化,又不想浪费一半的内存空间,就要采用第三种垃圾回收算法——mark-compact
算法。
这个方法同样分为两个阶段,标记(mark)阶段和压缩(compact)阶段。
其中标记阶段还是用root searching来标记存活对象。
而压缩阶段,则是把所有存活对象往内存开始端复制,从而可以规整出一大片连续可用的内存。
这种垃圾清除算法也有缺陷,就是效率不高,因为也会发生两次扫描,还会发生内存复制。
总结
本文主要讲了JVM中寻找垃圾以及回收垃圾的具体机制
root searching成为当下可以选择的最优的垃圾寻找算法,而三种垃圾清除算法则各有优劣,需要根据具体情况加以选择。