垃圾回收的小知识
触发主GC的条件
当应用程序空闲时,即没有应用线程在运行时,GC会被调用。因为GC在优先级最低的线程中进行,所以当应用忙时,GC线程就不会被调用
Java堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制地调用GC线程,以便回收内存用于新的分配。若GC一次之后仍不能满足内存分配的要求,JVM会再进行两次GC作进一步的尝试,若仍无法满足要求,则 JVM将报“out of memory”的错误,Java应用将停止。
在编译过程中作为一种优化技术,Java 编译器能选择给实例赋 null 值,从而标记实例为可回收。
由于是否进行主GC由JVM根据系统环境决定,而系统环境在不断的变化当中,所以主GC的运行具有不确定性,无法预计它何时必然出现,但可以确定的是对一个长期运行的应用来说,其主GC是反复进行的。
垃圾回收按照扫描区域进行分类
GC 分为Scavenge GC和Full GC。
Scavenge GC :发生在新生代区的垃圾回收。
Full GC :对整个堆进行整理,包括Young、Tenured和Perm
Full GC因为需要对整个堆进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。以下情况会触发Full GC:
年老代(Tenured)被写满;
持久代(Perm)被写满;
System.gc()被显示调用;
上一次GC之后Heap的各域分配策略动态变化.
影响GC 时间的两个函数
1. System.gc()方法
命令行参数监视垃圾收集器的运行:
使用System.gc()可以不管JVM使用的是哪一种垃圾回收的算法,都可以请求Java的垃圾回收。在命令行中有一个参数-verbosegc可以查看Java使用的堆内存的情况,它的格式如下:
java -verbosegc classfile
需要注意的是,调用System.gc()也仅仅是一个请求(建议)。JVM接受这个消息后,并不是立即做垃圾回收,而只是对几个垃圾回收算法做了加权,使垃圾回收操作容易发生,或提早发生。
2. finalize()方法
概述:理解为析构方法就好,在JVM垃圾回收器收集一个对象之前,一般要求程序调用适当的方法释放资源。但在没有明确释放资源的情况下,Java提供了缺省机制来终止该对象以释放资源,这个方法就是finalize()。在finalize()方法返回之后,对象消失,垃圾收集开始执行。
意义:之所以要使用finalize(),是存在着垃圾回收器不能处理的特殊情况。假定你的对象(并非使用new方法)获得了一块“特殊”的内存区域,由于垃圾回收器只知道那些经由new分配的内存空间,所以它不知道该如何释放这块“特殊”的内存区域,那么这个时候Java允许在类中定义一个finalize()方法。
特殊的区域例如:
- 由于在分配内存的时候可能采用了类似 C语言的做法,而非JAVA的通常new做法。这种情况主要发生在native method中,比如native method调用了C/C++方法malloc()函数系列来分配存储空间,但是除非调用free()函数,否则这些内存空间将不会得到释放,那么这个时候就可能造成内存泄漏。但是由于free()方法是在C/C++中的函数,所以finalize()中可以用本地方法来调用它。以释放这些“特殊”的内存空间。
- 打开的文件资源,这些资源不属于垃圾回收器的回收范围。
finalize()的主要用途是释放一些其他做法开辟的内存空间,以及做一些清理工作。因为在Java中并没有提够像“析构”函数或者类似概念的函数,要做一些类似清理工作的时候,必须override Object这个类中的finalize()方法。
注意:一旦垃圾回收器准备好释放对象占用的存储空间,首先会去调用finalize()方法进行一些必要的清理工作。只有到下一次再进行垃圾回收动作的时候,才会真正释放这个对象所占用的内存空间。
JAVA里的对象并非总会被垃圾回收器回收。1 对象可能不被垃圾回收,2 垃圾回收并不等于“析构”,3 垃圾回收只与内存有关。也就是说,并不是如果一个对象不再被使用,是不是要在finalize()中释放这个对象中含有的其它对象呢?不是的。因为无论对象是如何创建的,垃圾回收器都会负责释放那些对象占有的内存。
当 finalize() 方法被调用时,JVM 会释放该线程上的所有同步锁。
减少GC开销的措施
- 不要显示调用 System.gc(),这会触发Full GC
- 尽量减少临时对象的使用;一般临时变量都在新生代区,每次使用完,会由Scavenge GC进行。属于需要频繁GC的变量类型,减少临时变量就是减少垃圾
- 对象不要显示置为Null,这些被置为NUll的对象会被标记为可回收的。不过对于确实用不到的对象显示置为Null可以预防内存泄漏。
- 使用StringBuffer,而不是String来累加字符串;因为String累加表达式中每个String都是一个单独的 对象,会在常量池中占用空间,在回收的时候就会有多个垃圾
- 少用静态变量;静态变量是全局对象,在程序运行期间不会被GC回收,会一直占用内存
- 避免在短时间内大量创建和删除对象,这样会造成内存抖动,增加GC频率。比如不要再渲染函数中创建临时变量。