Java不要求程序员显示地分配内存和释放内存,Java在创建对象的时候会自动分配内存,并在该对象不再使用时自动释放对象所使用的内存。
Java中使用垃圾收集器来监视Java程序的运行,当对象不再使用时,就自动释放对象所使用的内存。
垃圾收集器是自动运行的,一般情况下,无须显示地请求垃圾收集器。调用System类中的静态gc()方法可以运行垃圾收集器,但这样并不能保证会立即回收指定对象。
因为不同的JVM实现者可能使用不同的算法管理GC,并且通常GC的线程优先级较低,JVM调用GC的策略也有很多种,有些是内存使用达到一定程度时才会执行GC,也有定时执行的,有的是平缓执行的,还有的是中断式执行的。
Java的垃圾回收机制是为所有的Java应用进程服务的,而不是为某个特定的进程服务的。因此,任何一个进程都不能命令垃圾回收机制做什么、怎么做或做多少。
在JVM垃圾收集器收集一个对象前,一般要求程序调用适当的方法释放资源,但在没有明确释放资源的情况下,Java提供了默认机制终止化该对象来释放资源,这个方法就是protected void finalize() throws Throwable。
垃圾收集器(GC)是和具体JVM实现紧密相关的,不同厂商(IBM、Oracle),不同版本的JVM,提供的选择也不同。
Serial GC:
它是最古老的垃圾收集器,串行运行,作用于新生代,采用复制算法,它的收集工作是单线程的,并且在进行垃圾收集过程中,会进入到“Stop-The-World”(虚拟机运行GC的时候会需要让所有的线程都停止工作,等待GC完成)状态。其单线程设计也意味着精简的GC实现,无需维护复杂的数据结构,初始化也简单,所以一直是Client模式下JVM的默认选项。
Serial Old:
Serial GC的串行运行,作用于老年代,采用标记-整理算法的实现。
ParNew GC:
作用于新生代,采用复制算法,它实际是Serial GC的多线程版本,在多CPU环境Server模式下与CMS配合使用。
CMS GC:
并行运行,作用于年老代,采用标记-清除算法,设计目标是尽量减少停顿时间,这对于web等反应时间敏感的应用非常重要,但是它采用标记-清除算法,存在着内存碎片化的问题,并且并行运行,会占用更多的CPU资源。在JDK 9中已经被标记为废弃。
Parrallel GC:
在早期的JDK 8等版本中,它是server模式JVM的默认GC选择,也被称作是吞吐量优先的GC。它的算法和Serial GC比较相似,但实现要复杂。其特点是新生代和年老代GC都是并行进行的,在常见的服务器环境中更加高效。 并且引入了开发者友好的配置项,我们可以直接设置暂停时间或吞吐量等目标,JVM会自动进行适应性调整。
G1 GC:
并行运行,可以作用于新生代或年老代,是一种兼顾吞吐量和停顿时间的GC实现,是Oracle JDK 9以后的默认GC选项。G1可以直观的设定停顿时间的目标,相比于CMS GC,G1未必能做到CMS在最好情况下的延时停顿,但是最差情况要好很多。
G1 GC 仍然存在着年代的概念,但是其内存结构并不是简单的条带式划分,而是类似棋盘的一个个region(区域)。Region之间是复制算法,但整体上实际可以看作是标记-整理算法,可以有效地避免内存碎片。
注意:
JVM有两种运行模式Server与Client。两种模式的区别在于,Client模式启动速度较快,Server模式启动较慢;但是启动进入稳定期长期运行之后Server模式的程序运行速度比Client要快很多。这是因为Server模式启动的JVM采用的是重量级的虚拟机,对程序采用了更多的优化;而Client模式启动的JVM采用的是轻量级的虚拟机。所以Server启动慢,但稳定后速度比Client远远要快。
复制算法:
新生代GC基本都是基于复制算法,将活着的对象复制到to区域,拷贝过程中将对象顺序放置,这样就可以避免内存碎片化。因为要进行复制,所以要提前预留内存空间,有一定的浪费。另外,对于G1这种内存分拆为大量region的GC,GC需要维护region之间对象引用关系,需要很大的开销。
标记-清除算法:
首先进行标记工作,标识出需要回收的对象,然后进行清除。标记、清除的过程效率有限,并且会不可避免的出现碎片化问题,不适合特别大的堆。
标记-整理算法:
类似于标记-清除算法,但为避免内存碎片化,它会在清理过程中将对象移动,以确保移动后的对象占用连续的内存空间。
Java程序不断创建的对象,通常都是分配在堆内存的Eden区域,当其空间占用到一定的阈值时,触发minor GC(指发生在新生代的垃圾收集动作)。仍然被引用的对象(绿色方块)存活下来,被复制到JVM选择的Survivor区域,而没有被引用的对象(黄色方块)则被回收。(方块中的数字表示对象的存活时间)
经过一次Minor GC,Eden区域就会空闲下来,直到再次达到Minor GC触发条件,这时候,另外一个Survivor区域则会成为to区域,Eden区域的存活对象和From区域对象,都会被复制到to区域,并且存活年龄会被+1。
类似上面的过程会发生很多次,直到有对象的年龄达到阈值,这时候就会发生晋升过程,超过年龄阈值的对象会被晋升为老年代。这个阈值是可以通过参数指定的。
后面就是Major GC(老年代GC),具体方式取决于选择的GC选项以及对应的算法。在标记整理算法过程中,无用的老年代对象被清除后,GC会对对象进行整理,以防止内存碎片化。