理解JVM的垃圾回收机制(简称GC)有什么好处呢?作为一名软件开发者,满足自己的好奇心将是一个很好的理由,不过更重要的是,理解GC工作机制可以帮助你写出更好的Java程序。
在学习GC前,你应该知道一个技术名词:“stop-the-world” ,无论你选择哪种GC算法,“stop-the-world”都会发生。“stop-the-world”意味着JVM停止应用程序,而去进行垃圾回收。当“stop-the-world”发生时,除了进行垃圾回收的线程,其他所有线程都将停止运行。被中断的任务将在GC任务完成后恢复执行。GC调优往往意味着减少“stop-the-world”的时间。
在HotSpot虚拟机中,将内存分为 年轻代(young generation)、老年代(old generation) 和 永久代(permanent generation)
我们看一下这幅图:
年轻代: 新创建的对象都存放在这里。因为大多数对象很快变得不可达,所以大多数对象在年轻代中创建,然后消失。当对象从这块内存区域消失时,我们说发生了一次“minor GC”。
老年代: 没有变得不可达,存活下来的年轻代对象被复制到这里。这块内存区域一般大于年轻代。因为它更大的规模,GC发生的次数比在年轻代的少。对象从老年代消失时,我们说“major GC”(或“full GC”)发生了。
永久代: 永久代(permanent generation) 也称为“方法区(method area)”,它存储class对象和字符串常量。所以这块内存区域绝对不是永久的存放从老年代存活下来的对象的。发生在这里的垃圾回收也被称为major GC。
垃圾收集算法如下:
该算法的核心是将可用内存按容量划分为大小相等的两块,每次只用其中一块,当这一块的内存用完,就将还存活的对象复制到另外一块上面,然后把已使用过的内存空间一次清理掉。这使得每次只对其中一块内存进行回收,分配也就不用考虑内存碎片等复杂情况,实现简单且运行高效。如下图:
该算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象(可达性分析),在标记完成后统一清理掉所有被标记的对象。如下图:
该算法会有以下两个问题:
标记清除算法会产生内存碎片,而复制算法需要有额外的内存担保空间,于是针对老年代的特点,又有了标记整理算法。标记整理算法的标记过程与标记清除算法相同,但后续步骤不再对可回收对象直接清理,而是让所有存活的对象都向一端移动,然后清理掉这一端边界以外的内存。如下图:
在方法区进行垃圾回收一般“性价比”较低,因为在方法区主要回收两部分内容:废弃常量 和 无用的类 。
回收废弃常量与回收其他年代中的对象类似。
但是判断一个类是不是无用的类的条件则相当苛刻:
但即使满足以上条件也未必一定会回收,Hotspot VM还提供了-Xnoclassgc参数控制(关闭CLASS的垃圾回收功能)。因此在大量使用动态代理、CGLib等字节码框架的应用中一定要关闭该选项,开启VM的类卸载功能,以保证方法区不会溢出。
System.gc()方法 和 finalize()方法
使用System.gc() 可以不管JVM使用的是哪一种垃圾回收的算法,都可以请求Java的垃圾回收。在命令行中有一个参数-verbosegc
可以查看Java使用的堆内存的情况,它的格式如下:java -verbosegc classfile
由于这种方法会影响系统性能,不推荐使用,所以不详细介绍。
JVM垃圾回收器在回收一个对象之前,一般要求程序调用适当的方法释放资源,但在没有明确释放资源的情况下,Java提供了缺省机制来终止该对象从而释放资源,这个方法就是finalize() 。它的原型为:protected void finalize() throws Throwable
在finalize() 方法返回之后,对象消失,垃圾收集开始执行。原型中的throws Throwable 表示它可以抛出任何类型的异常。
之所以要使用finalize(),是因为存在着垃圾回收器不能处理的特殊情况。例如:
C/C++
的malloc()
函数来分配存储空间,除非调用 free() 函数,否则这些内存空间将不会得到释放,那么这个时候就可能造成内存泄漏。但是由于 free() 是C/C++
中的函数,所以 finalize() 中可以用本地方法来调用它。以释放这些“特殊”的内存空间。Str5=Str1+Str2+Str3+Str4;
这条语句执行过程中会产生多个垃圾对象,因为每次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。int, long
就不用包装类型Integer, Long
。基本类型变量占用的内存资源比包装类型占用的少得多,如果没有必要,最好使用基本变量。在JDK7中,有5种垃圾收集器:
其中,Serial 收集器一定不能用于服务器端。这个收集器类型仅应用于单核CPU桌面电脑。使用Serial收集器会显着降低应用程序的性能。