JVM本身具有垃圾回收机制,所以很多开发人员认为不需要理解jvm的内存管理部分。其实,深入理解jvm的内存管理机制可以帮助我们更好地编写高质量的代码,并减少内存泄露问题,同时对jvm的运行进行性能调优。
为了引入话题,首先我们看以下一段代码
public class OutOfMemTest {
public static void main(String[] args){
List<Object> v = new ArrayList<Object>(10);
while(true) {
Object o = new Object();
v.add(o);
}
}
}
运行后很快程序会出错,抛出错误java.lang.OutOfMemoryError: Java heap space,表示堆空间溢出,程序退出。我们再看以下一段代码
public class JVMTest {
public static void main(String[] args) throws InterruptedException {
JVMTest test = new JVMTest();
while(true){
test.testList();
}
}
private void testList(){
List<Object> v = new ArrayList<Object>(10);
Object o = null;
int i = 0 ;
while(i++<100) {
o = new Object();
v.add(o);
}
}
}
上述代码,运行会发现没有出现内存溢出的问题,同样都是有一个无限死循环,而第一段代码会内存溢出,但第二段代码却可以正常运行。而这里就具体涉及到JVM的垃圾回收的相关问题。
垃圾回收需要考虑的是三个问题:
1、回收哪些数据;
2、何时回收;
3、如何回收。
针对第一个问题,回收哪些数据,主要是两种算法:
(1)引用计数法,简单来说每次生成一个对象,都会有一个引用指向它,而当该将该引用置为null,则引用会减一,当该对象没有被引用时,则表示可以被回收。
(2)根搜索算法,指的是从一个跟引用开始进行遍历,遍历到叶子节点为止,并将上述节点进行标记,而那些没有被标记的节点,则为不可达状态,表示可以被回收。
如上图中红色标记部分则为不可达的对象,会在垃圾回收的过程中进行回收。而根搜索算法中的可以被做为根节点的对象主要为一下三个:
- JVM栈中引用的对象;
- 方法区中的静态变量以及常量引用的对象;
- 本地栈中JNI中引用的对象;
这里需要注意的是跟搜索算法中要注意引用类型,强引用,软引用,弱引用以及虚引用。
针对第二个问题,具体这些对象何时回收,一般是当对象所占用的堆空间过大,会进行回收,针对不同的回收算法,这个具体的触发回收的点也是稍有不同。
第三个问题,如何进行回收,就会涉及到具体的相关回收算法。主要由四种:标记-清除算法,复制算法,标记-整理算法,分代算法;