这篇博客是对Java垃圾回收的总结,主要是对Java Garbage Collection Introduction以及后续的三篇博客的翻译。我把这四篇博客翻译到这一篇博客里,把参考的其余博客的连接附在文章末尾。java
在Java中,分配和回收对象的内存空间是在JVM中、由垃圾回收机制自动完成的。不像C语言,使用Java的开发人员不须要写代码来进行垃圾回收。Java之因此如此流行以及可以帮助程序员更好的开发,这是其中一个缘由。程序员
这个系列一共有四篇博客来介绍Java的垃圾回收,分别是:算法
这一节是这篇博客的第一部分。主要内容是解释一些基础的术语,好比JDK, JVM, JRE, HotSpot VM,而后理解JVM的总体架构和Java堆内存结构。理解这些概念对学习Java垃圾回收颇有帮助。数组
Java API:Java API是帮助程序员进行Java开发的一系列封装库的集合。缓存
Java Development Kit (JDK):JDK是一套帮助程序员进行Java开发的工具,这些工具主要是进行编译、运行、打包、分配和监视Java程序。安全
Java Virtual Machine (JVM):JVM是一个抽象的计算机。Java程序是针对JVM规范来写的。对不一样的操做系统来讲,JVM有所不一样,它主要做用是把Java指令翻译为操做系统的指令,而且执行它们。JVM可以保证Java代码独立于平台。服务器
Java Runtime Environment (JRE):JRE包括JVM实现和Java API。架构
不一样的JVM在垃圾回收的实现上可能有点不同。 在SUN被收购以前,Oracle有JRockit JVM,而且在SUN收购以后,Oracle得到了HotSpot JVM。目前Oracle维护了两个JVM实现,而且已经声明,在一段时间内这两个JVM实现将被合并到一个。并发
HotSpot JVM是标准Oracle SE平台的核心组件。在这个垃圾回收教程中,咱们将看到基于HotSpot虚拟机的垃圾回收原理。app
下图总结了JVM的关键组件。在JVM架构中,两个主要涉及到垃圾回收的组件是堆内存(heap memory)和垃圾回收器(garbage collector)。堆内存是运行时数据所在的区域,这个区域存着对象实例,垃圾回收器也在这个区域内操做。咱们如今看看这些东西如何适应到更大的方案。
正如虚拟机规范所说的那样,JVM中的内存分为5个虚拟的区域:
理解Java Heap Memory在JVM模型中的角色很重要。在运行期间,Java实例保存在heap memory区域。当一个对象没有引用指向它时,它就须要从内存里被清除。在垃圾回收的过程当中,这些对象在heap memory里被清除,而后这些空间可以再被利用。heap memory主要有三个区域:
更新:持久代区域已经从Java SE 8移除。取代它的是另外一个内存区域也被称为元空间(本地堆内存中的一部分)。
这个系列博客主要是帮助理解Java回收的基本概念以及其如何工做。这部分是这个系列的第二部分,但愿你有阅读过第一部分Java Garbage Collection Introduction。
Java垃圾回收是一个自动处理的过程,目的是管理程序运行时的内存问题。经过这种方式,JVM可以自动减轻程序员在分配和释放内存资源的问题。
垃圾回收做为一个自动的过程,程序员不用在代码里显式地启动垃圾回收机制。System.gc()和Runtime.gc()可以显式地让JVM执行垃圾回收过程。
虽然这个机制可以让程序员启动垃圾回收,可是这个责任仍是在JVM上。JVM可以选择拒绝你的请求,因此咱们无法保证这两个命令确定会执行垃圾回收。JVM会经过eden区的可用内存来判断是否执行垃圾回收。JVM规范把这个选择留给了JVM的具体实现,因此这些实现的细节对不一样的JVM来讲也不同。
毫无疑问的是,垃圾回收机制不能强制执行。我刚刚发现了一个调用System.gc()的应用场景,看这篇文章就知道何时调用System.gc()是合适的。
垃圾回收是这样一个过程:回收再也不使用的内存空间,让这些空间可以被未来的实例对象所用。
当一个实例被建立的时候,它首先存在堆内存区域的年轻代的eden区。
注意:假如你不懂这些术语,建议你去读Java Garbage Collection Introduction,这篇博客把内存模型、JVM架构和一些专业术语解释的很详细。
做为次要垃圾回收(minor GC)周期的一部分,那些仍然被引用的对象会从eden区被搬到survivor的S0区。一样的过程,垃圾回收器会扫描S0区,而后把实例搬到S1区。
那些没有被引用的对象会被标记。经过不一样的垃圾回收器(一共有四类垃圾回收器,后续的教程会讲这个)来决定这些被标记的对象在内存中被移除仍是在单独的进程中来完成。
年老代或者持久代是堆内存中的第二个逻辑部分。当垃圾回收器执行次要垃圾回收周期的时候,那些仍在S1区存活的实例将被移到年老代,S1区域中没有被引用的实例将会被标记以便清除。
在垃圾回收的过程当中,年老代是实例对象生命周期的最后一个环节。Major GC是扫描年老代的堆内存的垃圾回收过程。若是有对象没有被引用,那它们将被标记以便清除,若是有被引用,那它们将继续留在年老代。
一旦实例对象在堆内存中被删除,那它们所占的区域就又能够被未来的实例对象使用。这些空的区域在内存中将会造成不少碎片。为了更快的为实例分配内存,咱们应该解决这个碎片问题。根据不一样垃圾回收器的选择,被回收的内存将在回收的过程同时或者在GC另外独立的过程当中压缩整合。
就在清除一个对象并回收它的内存空间以前,垃圾回收器将会调用各个实例的finalize()方法,这样实例对象就有机会能够释放掉它占用的资源。尽管finalize()方法是保证在回收内存空间以前执行的,可是对具体的执行时间和执行顺序是没有任何保证的。多个实例之间的finalize()执行顺序是不能提早预知的,甚至有可能它们是并行执行的。程序不该该预先假设实例执行finalize()的顺序,也不该该使用finalize()方法来回收资源。
Java中有多种不一样的引用类型。实例的可回收性取决于它的引用类型。
引用 | 垃圾回收 |
---|---|
强引用 | 不适合被垃圾回收 |
软引用 | 可能会被回收,可是是在最后被回收 |
弱引用 | 适合被回收 |
虚引用 | 适合被回收 |
在编译期间,Java编译器有优化技术来选择把null值赋给一个实例,从而把这个实例标记为可回收。
class Animal { public static void main(String[] args) { Animal lion = new Animal(); System.out.println("Main is completed."); } protected void finalize() { System.out.println("Rest in Peace!"); } }
在上面的类中,lion实例在初始化后就没被用过了,因此Java编译器在lion实例化后把lion赋值为null而做为一个优化的手段。这样finlizer方法可能会在Main方法以前打印结果。咱们不能肯定地保证结果,由于这和JVM的具体实现以及运行时的内存有关。可是咱们能知道的一点是:编译器发现一个实例在以后的程序中再也不被引用时能够选择提早释放实例的内存。
Class GCScope { GCScope t; static int i = 1; public static void main(String args[]) { GCScope t1 = new GCScope(); GCScope t2 = new GCScope(); GCScope t3 = new GCScope(); // No Object Is Eligible for GC t1.t = t2; // No Object Is Eligible for GC t2.t = t3; // No Object Is Eligible for GC t3.t = t1; // No Object Is Eligible for GC t1 = null; // No Object Is Eligible for GC (t3.t still has a reference to t1) t2 = null; // No Object Is Eligible for GC (t3.t.t still has a reference to t2) t3 = null; // All the 3 Object Is Eligible for GC (None of them have a reference. // only the variable t of the objects are referring each other in a // rounded fashion forming the Island of objects with out any external // reference) } protected void finalize() { System.out.println("Garbage collected from object" + i); i++; }
垃圾回收不保证内存的安全问题,粗心的代码会形成内存溢出。
import java.util.LinkedList; import java.util.List; public class GC { public static void main(String[] main) { List l = new LinkedList(); // Enter infinite loop which will add a String to the list: l on each // iteration. do { l.add(new String("Hello, World")); } while (true); } }
输出为
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.LinkedList.linkLast(LinkedList.java:142) at java.util.LinkedList.add(LinkedList.java:338) at com.javapapers.java.GCScope.main(GCScope.java:12)
在这个教程中,咱们将学习各类各样的垃圾回收器。Java垃圾回收是一个自动处理的过程,目的是管理程序运行时的内存问题。这是这个系列教程的第三部分,在前面两个部分,咱们学习了How Java Garbage Collection Works,这部分颇有意思,我建议你完整读一遍。而第一部分Java Garbage Collection Introduction中,咱们学习了JVM的架构、堆内存模型和Java相关的术语。
Java有四种垃圾回收器:
这四种垃圾回收器各有本身的利弊。更重要的是,咱们程序员能够选择JVM用哪一种垃圾回收器。咱们通常是经过设置JVM参数来选择垃圾回收器。各个垃圾回收器在不一样应用场景下的效率会有很大的差别。所以了解各类不一样类型的垃圾回收器以及它们的应用场景是很是重要的。
串行垃圾回收器控制全部的应用线程。它是为单线程环境设计的,只使用一个线程来执行垃圾回收工做。它的工做方式是暂停全部应用线程来执行垃圾回收工做,这种方式不适用于服务器的应用环境。它最适用的是简单的命令行程序。
使用-XX:+UseSerialGC JVM参数来开启使用串行垃圾回收器。
并行垃圾回收器也称做基于吞吐量的回收器。它是JVM的默认垃圾回收器。与Serial垃圾回收器不一样的是,它使用多个线程来执行垃圾回收工做。和Serial回收器同样,它在执行垃圾回收工做是也须要暂停全部应用线程。
并发标记清除(Concurrent Mark Sweep,CMS)垃圾回收器,使用多个线程来扫描堆内存并标记可被清除的对象,而后清除标记的对象。CMS垃圾回收器只在下面这两种情形下暂停工做线程:
对比与并行垃圾回收器,CMS回收器使用更多的CPU来保证更高的吞吐量。若是咱们能够有更多的CPU用来提高性能,那么CMS垃圾回收器是比并行回收器更好的选择。
使用-XX:+UseParNewGC JVM参数来开启使用CMS垃圾回收器。
G1垃圾回收器应用于大的堆内存空间。它将堆内存空间划分为不一样的区域,对各个区域并行地作回收工做。G1在回收内存空间后还当即堆空闲空间作整合工做以减小碎片。CMS倒是在所有中止(stop the world,STW)时执行内存整合工做。G1会根据各个区域的垃圾数量来对区域评判优先级。
使用-XX:UseG1GC JVM参数来开启使用G1垃圾回收器。
在使用G1垃圾回收器时,开启使用-XX:+UseStringDeduplacaton JVM参数。它会经过把重复的String值移动到同一个char[]数组来优化堆内存占用。这是Java 8 u 20引入的选项。
以上给出的四个Java垃圾回收器,在何时使用哪个取决于应用场景,硬件配置和吞吐量要求。
下面是与Java垃圾回收相关、比较重要的JVM选项。
选项 | 描述 |
---|---|
-XX:+UseSerialGC | Serial Garbage Collector |
-XX:+UseParallelGC | Parallel Garbage Collector |
-XX:+UseConcMarkSweepGC | CMS Garbage Collector |
-XX:ParallelCMSThreads= | CMS Collector – number of threads to use |
-XX:+UseG1GC | G1 Gargbage Collector |
选项 | 描述 |
---|---|
-Xms | Initial heap memory size |
-Xmx | Maximum heap memory size |
-Xmn | Size of Young Generation |
-XX:PermSize | Initial Permanent Generation size |
-XX:MaxPermSize | Maximum Permanent Generation size |
java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar java-application.jar
这篇教程主要介绍垃圾回收机制的监控和分析,咱们会用到一个工具来监控一个Java应用的垃圾回收过程。若是你是一个新手,你最好把这系列博客的前几篇都看一下,你能够从Java Garbage Collection Introduction开始。
下面说的工具各自都有利弊,咱们能够经过选择合适的工具来进行分析以此提升Java应用的性能。咱们这篇教程主要用Java VisualVM。
Java VisualVM能够经过Java SE SDK来免费安装使用。看看你的Java JDK安装路径,就在\Java\jdk1.8.0\bin路径下。固然还有不少其余的工具,jvisualvm只是其中之一。
Java VisualVM提供了一个包含Java程序信息的可视化接口。它包含不少其余的工具且集成到这一个。如今像JConsole, jstat, jinfo, jstack, and jmap都被集成到Java VisualVM中了。
Java VisualVM主要用来:
JDK目录下就有Java VisualVM。
咱们须要安装一个可视化插件,以便观察Java GC过程。
启动你的Java应用,Java VisualVM会自动开始监控,并把结果展现在Java VisualVM可视化接口中。你能够选择不一样的组件来观察你感兴趣的指标。