gc 只能清除在堆上分配的内存(纯java语言的所有对象都在堆上使用new分配内存),而不能清除栈上分配的内存(当使用JNI技术时,可能会在栈上分配内存,例如java调用c程序,而该c程序使用malloc分配内存时).因此,如果某些对象被分配了栈上的内存区域,那gc就管不着了,对这样的对象进行内存回收就要靠finalize().
正解,在JVM里面GC是一条优先级非常低的thread,调用时机一般是在heap要快满了或者当前java进程闲的蛋疼的时候,但可以调整GC的频率。
是程序员手动调用的,这只是给jvm提个醒而已,告诉他该回收垃圾了,但是jvm什么时候真的去回收垃圾,我们就没有办法控制了。
java提供了从语言角度能够强制jvm进行垃圾回收,在我们的程序中可以通过调用System.gc去强制jvm进行垃圾回收,通过源码我们可以看到实际上是调用了Runtime去强制gc
- public static void gc() {
- Runtime.getRuntime().gc();
- }
Runtime的gc方法是native方法也就是Rumtime.c中的
- JNIEXPORT void JNICALL
- Java_java_lang_Runtime_gc(JNIEnv *env, jobject this)
- {
- JVM_GC();
- }
而JVM_GC方法是在jvm.cpp中实现
- JVM_ENTRY_NO_ENV(void, JVM_GC(void))
- JVMWrapper("JVM_GC");
- if (!DisableExplicitGC) {
- Universe::heap()->collect(GCCause::_java_lang_system_gc);
- }
- JVM_END
我们看到参数DisableExplicitGC,从代码中可以使用-XX:+DisableExplicitGC 可以关闭system,gc
我们把GC分成4种类型
1. SerialGC
参数-XX:+UseSerialGC
就是Young区和old区都使用serial 垃圾回收算法,
2. ParallelGC
参数-XX:+UseParallelGC
Young区:使用Parallel scavenge 回收算法
Old 区:可以使用单线程的或者Parallel 垃圾回收算法,由 -XX:+UseParallelOldGC 来控制
3. CMS
参数-XX:+UseConcMarkSweepGC
Young区:可以使用普通的或者parallel 垃圾回收算法,由参数 -XX:+UseParNewGC来控制
Old 区:只能使用Concurrent Mark Sweep
4. G1
参数:-XX:+UseG1GC
没有young/old区
对于程序员来说,分配对象使用new关键字;释放对象时,只要将对象所有引用赋值为null,让程序不能够再访问到这个对象,我们称该对象为"不可达 的". GC 将负责回收所有"不可达"对象的内存空间。
finalize 是位于Object类的一个方法,该方法的访问修饰符为protected,由于所有类为Object的子类,因此用户类很容易访问到这个方法。由 于,finalize函数没有自动实现链式调用,我们必须手动的实现,因此finalize函数的最后一个语句通常是 super.finalize()。通过这种方式,我们可以实现从下到上实现finalize的调用,即先释放自己的资源,然后再释放父类的资源。
根据Java 语言规范,JVM保证调用finalize函数之前,这个对象 是不可达的,但是JVM不保证这个函数一定会被调用。另外,规范还保证finalize函数最多运行一次。
JVM的垃圾回收采用有向图方式来管理内存中的对象,因此可以方便的解决循环引用的问题。采用有向图来管理内存中的对象具有较高的精度,但缺点是效率较低。判断一个对象是否可回收的标准就在于该对象是否被引用,一个对象在堆内存中运行时,可以把它分为三种:可达状态、可恢复状态、不可达状态。
由于JVM肯定不会回收被强引用所引用的java对象,因此强引用是造成java内存泄露的主要原因之一。
垃圾回收方式:复制、标记清除、标记压缩
复制:将堆内存分成两个相同的空间,从根开始访问每一个关联的可达对象,将空间A的可达对象全部复制到空间B,然后一次性回收整个空间A。
标记清除:也就是不压缩回收,垃圾回收器先从根开始访问所有的可达对象,将它们标记为可达状态,然后再遍历一次整个内存区域,对所有的没有标记为可达的对象进行回收处理。---两次遍历堆内存空间,整理后的对内存不连续,堆内存的碎片会很多。
标记压缩:也就是压缩回收,结合上面两种算法的优点,从根访问所有可达对象,标记为可达对象,接下来垃圾回收器会将这些活动对象搬迁在一起,然后垃圾回收机制再次回收那些不可达对象的内存空间,这样就避免了回收产生内存的碎片。
现行的垃圾回收器用分代的方式来采用不同的回收设计,分代的基本思路是根据对象生存的时间长短,把堆内存分为三个代:
Young(新生代)、Old(老年代)、Permanent(永生代)
垃圾回收器会根据不同代的特点采用不同的回收算法:
Young代:对Young代的对象而言,大部分对象都会很快就进入不可达状态,只有少量的对象能熬到垃圾回收执行时,而垃圾回收器只需要保留Young代中处于可达状态的对象,如果采用复制算法只需要少量的复制成本,因此大部分大部分垃圾回收器对Young代都采用复制算法。
Young代由一个Eden区和两个Survior区构成,绝大部分对象先分配到Eden区中,同一时间,两个Survivor区一个用来保存对象,一个是空的,用来在下次垃圾回收的时候保存Young代中的对象。每次复制就是将Eden和第一个Survivor区的可达对象复制到第二个Survivor区,然后清空Eden与第一个Survivor区。
Old代:如果Young代中的对象经过数次垃圾回收依然没有被回收掉,垃圾回收机制会将这个对象转移到Old代。Old代的垃圾回收具有如下两个特征:Old代垃圾回收的执行频率无需太高,因为很少有对象会死掉;每次Old代执行垃圾回收都需要更长的时间来完成。基于上述考虑,垃圾回收器通常会使用标记压缩算法,这可以避免复制Old代的大量对象,而且由于Old代的对象不会很快死亡,回收过程不会大量的产生内存碎片,相对比较划算。
Permanent代:Permanent代主要用于装载Class、方法等信息,默认为64MB,对于那些需要加载很多类的服务器程序,往往需要加大Permanent代的内存,否则可能会因为内存不足而导致程序终止,垃圾回收机制通常不会回收Permanent代中的对象。在运行Hibernate、Spring程序时遇到java.lang.OutOfMemoryError:PermGen space的错误,这就是Permanent代内存耗尽所导致的错误。