【浅度渣文】JVM——简述垃圾回收

原文连接:http://www.dubby.cn/detail.html?id=9062html

垃圾回收的简单描述

什么是自动垃圾收集?

自动垃圾收集是查看堆内存的过程,能够识别哪些对象正在使用,哪些不是,以及删除未使用的对象。一个正在使用的对象或一个被引用的对象,意味着你的程序的某个部分仍然保持着一个指向这个对象的指针。未使用的对象或未引用的对象再也不被程序的任何部分引用。因此未被引用的对象所使用的内存能够被回收。java

在像C这样的编程语言中,分配和释放内存是一个手动过程。在Java中,释放内存的过程由垃圾收集器自动处理。基本过程能够描述以下。算法

第1步:标记

这个过程的第一步就是标记。这是垃圾收集器标记内存中哪些对象正在被使用,哪些对象已经没有被使用。数据库

image

有用的对象显示为蓝色,没有用的对象显示为黄色。在标记阶段扫描全部对象,而后作出这个决定。若是必须扫描系统中的全部对象,这多是很是耗时的过程。编程

第2步:普通删除

内存维护着一个空闲内存列表,每次分配空间时,会来这个列表上找到合适的空间分配。正常删除时,会把没有用到的对象的内存空间还给空闲列表。bash

image

另外一种第2步:删除并压缩

为了进一步提升性能,除了删除未引用的对象以外,还能够压缩剩余的引用对象。 经过移动被引用的对象,这使得新的内存分配变得更容易和更快。服务器

image

为何使用分代垃圾收集?

如前所述,标记和压缩JVM中的全部对象效率不高。 随着愈来愈多的对象被分配,对象列表的增加和增加致使更长和更长的垃圾收集时间。 然而,应用程序的实证分析代表,大多数对象是短暂的。网络

这里给个数据的例子。多线程

image

正如你所看到的,随着时间的推移对象保持存活的愈来愈少。 实际上,大多数对象的寿命都很短,如图左侧较高的值所示。并发

JVM 的分代

根据上面的对象的行为特性,咱们能够总结出一个更好的方式来提升JVM垃圾回收的效率。因此,就把堆内存分红几种代,新生代老年代永久代(Java8以后就没有永久代了,取而代之的是元数据Metaspace)。

image

一个新的对象会被分配在新生代上,而且新的对象会在新生代里慢慢变老。当新生代的空间被占满后,就会触发一次minor gc。假设新生代里的对象死亡率很高的话,那么新生代的垃圾回收就是很优的。一个充满死亡对象的新生代收集起来其实很快。幸存下来的对象会慢慢变老,直到能够移入老年代。

Stop the World Event——全部的新生代手机都是中止世界的事件。Stop the World Event的意思是,全部的应用程序的线程都会被暂停,直到垃圾回收完成。新生代GC老是Stop the World。

老年代是存放那些经历了屡次minor gc,年纪达到一个阈值以后的存活的对象。通常来讲,会给对象设置一个年龄阈值,达到阈值以后,就会移入老年代。最后,老年代须要进行垃圾回收,就会触发一次major gc。

Major gc也是致使Stop the World。在大部分状况下,major gc是会比minor gc慢不少。因此,对于一个关注响应时间的应用来讲,应该尽量的下降major gc的次数。这里也要注意到,major gc的停顿时间(Stop the World的时间)是和你选取的垃圾收集器有关的。

永久代包含了JVM所须要的class和method的定义等元数据。永久代会随着JVM运行时加载的class而填充新的元数据。除此以外,Java SE的类库也会被存储在这里。

若是JVM检测到这部分class不会被使用了,并且须要更多的内存空间来加载其余的class,那么class也会被回收(unloaded/卸载)。这个收集包含在一次full gc中。(即使是在Java8以后,没有了所谓了永久代,取而代之的是元数据,可是,也会存在类型卸载的回收)

分代垃圾回收

如今你已经明白了为何须要把堆细分红不一样的几个代,如今是时候仔细的看看这种空间是如何工做的了。下面的图演示了在JVM中,对象的分配和变老的过程。

1.首先,任何对象都会被分配在eden区。两个suvivor区一开始都是空的。

这里是为了给读者介绍垃圾回收器的设计过程,和一步步的思考过程,在以后仍是会有不少优化,可能会和一开始的设计意图相违背,请见谅。好比,有的对象甚至不分配到堆里(逃逸分析),有的大对象甚至会直接分配到old区(大对象分配),有的对象甚至会分配到堆外内存(nio等),等等各类特殊状况。

image

2.当eden满了以后,就会触发一次minor gc。

image

3.活着的对象会被移到第一个suvovor区(第一个第二个都是相对的)。没有被是用的对象就直接被清除了。

image

4.下一次minor gc发生时,一样的操做。没有被使用的对象被清除,活着的对象和被移到另外一个suvivor区。并且,这些对象年龄会+1,而后被移入第二个suvivor。全部的活着的对象都被移入这个新的suvivor1,那么eden和suvivor0又都空了。可是,如今在suvivor1中,对象的年龄是不同的。

image

5.下一次minor gc,又会重复上面的步骤。不过对象是从eden和suvicor1移入到suvivor0中了。

image

6.终于,随便不断的minor gc,对象的年龄愈来愈大,达到了阈值(这里是8)时,他们会晋升带老年代。

image

7.随着更多的minor gc,也有更多的对象晋升到老年代。

image

8.上面已经涵盖了新生代的整个过程。最后,老年代须要进行一次major gc来清除,压缩老年代的空间。

image

执行并观察

上面说了那么多,相信机智的读者已经大体了解垃圾回收的过程了。如今让咱们亲眼看一看这个执行过程。这一部分,咱们会运行一个Java应用程序,而后使用Visual VM分析回收的过程。Visual VM是JDK提供给咱们的一个工具,开发者可使用这个工具对JVM进行各个方面的监视。

1. 你须要先去Oracle官网下载JavaDemo

2.启动示例代码

确保你的电脑已经安装了JDK,并下载了上一步说的demo。而后解压到本地一个目录下。个人目录是/Users/teeyoung/Desktop/code4me/javademos8

而后执行Java2demo.jar,java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar Java2Demo.jar

注意:1.这些命令稍后会解释;2.-XX:PermSize=20m -XX:MaxPermSize=20m若是是在Java 8以后是提示无效,觉得已经被移除了。

程序运行起来是这个样子:

image

你能够看到不少tab,那些演示了Java的绘图功能(看到这个程序,让我想到了买家秀和卖家秀,别人写的代码和我写的代码)。

随意点击各个tab,大体是这样的:

image

这个界面能够看到垃圾回收行为的结果,看右下角的内存监视。咱们先让他运行着,咱们稍后会用到它的。

3.启动VisualVM

若是你的jdk/bin已经在你的path下了,那么直接执行jvisualvm,不然须要输入完整的路径,如:/usr/bin/jvisualvm

image

4.安装Visual GC插件

咱们须要安装Visual GC这个插件,可是java.net这个站点都关了,没法联网安装,因此我写了另外一篇文章演示如何安装插件。请移步jvisualvm插件安装的正确姿式(解决网络问题):http://www.dubby.cn/detail.html?id=9061

安装以后就是这个样子:

image

5.分析Java2Demo

首先双击Java2Demo这个本地进程,或者右击->Open:

image

而后点击Visual GC这个tab:

image

而后就自由尝试各个tab页,看看每一个信息表明JVM的什么指标。还有,你能够尝试着改变Java2Demo上的string和image的数量,看看对垃圾回收有什么影响。

Java垃圾收集器

如今你知道了垃圾回收的基本概念,还有如何去监视JVM的垃圾回收。如今咱们来了解Java给咱们提供的不一样的垃圾回收器,还有咱们须要掌握如何使用这些垃圾回收器的命令行。

通用的命令行

这里给出一些通用的命令行,无论你是什么收集器,都会用到的。

选项 描述
-Xms 设置JVM启动时,堆的初始大小
-Xmx 设置堆的最大的容量
-Xmn 设置新生代的容量
-XX:PermSize 设置永久代的初始大小(Java 8以废弃
-XX:MaxPermSize 设置永久代的最大容量(Java 8以废弃
-XX:MinHeapFreeRatio 设置堆最小空闲容量,低于这个阈值就扩容,可是堆总量仍是要在Xmx和Xms之间
-XX:MaxHeapFreeRatio 设置堆最大空闲容量,高于这个阈值就收缩,可是堆总量仍是要在Xmx和Xms之间

Serial收集器

Serial收集器是客户端默认的收集器。使用Serial收集器,minor gc和major gc都是单线程处理。并且,老年代使用并发-压缩算法。把老年代的活着的对象移到老年代的前面,后面空出空闲区域,以供后续分配,能够避免空间碎片。

使用场景

Serial收集器是一些客户端(PC,不是服务器)应用使用,并且对于低延时要求不高的。他的优点是单线程处理。直到今天,对于一些不是很重要,堆内存只有几百MB的应用来讲,Serial GC依然是个颇有效的垃圾收集器。

还有一个普遍使用Serial收集器的场景是,一个机器上运行着不少JVM(在某些场景下,JVM的数量比处理器的核数还要多)。在这种状况,使用Serial收集器能够减小JVM之间冲突,即使GC的时间变长了。

最后,随着嵌入式设备的普及,内存少,核数少,Serial收集器可能会从新绽开光彩。

命令行选项

开始Serial收集器:

-XX:+UseSerialGC
复制代码

给个完整的例子:

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar Java2demo.jar
复制代码

Parallel收集器

Parallel收集器在回收新生代时,使用多线程进行收集。默认的回收线程数等于机器的核数。可使用-XX:ParallelGCThreads=<desired number>来设置但愿的线程数。

在只有一个CPU的机器上,即使你已经开启了Parallel收集器,JVM仍是会使用默认的收集器来工做。

使用场景

Parallel收集器也被称为吞吐收集器。由于他能够利用多线程来加快应用的吞吐。这个收集器通常被用做有不少工做须要作,并且对低延要求时不那么高的应用。例如,批处理(报表,帐单,或者是很大的数据库查询等)。

-XX:+UseParallelGC

这个命令行选项是开启新生代的多线程收集,老年代的多线程收集。老年代也是整理方式。

给个完整的例子:

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseParallelGC -jar Java2demo.jar
复制代码

-XX:+UseParallelOldGC

这个选项是开启新生代的多线程收集,老年代的多线程收集。老年代也是整理方式。

整理:就是会把活着的对象移到内存的前面,这样就对象和对象之间的小的空闲的空间(内存碎片)。内存碎片可能致使,空闲空间足够,可是大对象没法分配的状况。

完整的例子:

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseParallelOldGC -jar Java2demo.jar
复制代码

CMS收集器

并发(Concurrent)标记(Mark)清除(Sweep)收集器(CMS)(也被叫作:并发低延时收集器)是一个手机老年代的垃圾收集器。他试图把大部分垃圾收集工做和应用程序的线程并发执行,以下降所形成的停顿(Stop the World)时间。一般状况下,CMS不会压缩整理活着对象。因此,会存在内存碎片的问题。若是内存碎片成为你的问题,那么能够考虑换用更大的堆(哈哈,也能够考虑换收集器,可是,换更大的堆是直接而且简单的方法)。

注意:CMS收集器在新生代的收集方式和Parallel在新生代的收集方式同样(单线程,复制)。

使用场景

CMS适用对低延时有高要求的应用。好比,响应事件的桌面应用,响应请求的Web服务器,或者响应查询的数据库。

命令行选项

开启命令:

-XX:+UseConcMarkSweepGC
复制代码

设置线程数:

-XX:ParallelCMSThreads=<n>
复制代码

这里给个完整的例子:

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseConcMarkSweepGC -XX:ParallelCMSThreads=2 -jar Java2demo.jar
复制代码

G1收集器

具体的能够查看【浅度渣文】JVM——G1收集器:http://www.dubby.cn/detail.html?id=9059

这里简单描述一下吧,G1在Java 7才出现的,是一个并发的,低延时的,整理收集器。对堆内存管理和以前的收集器都不同。

命令行选项

开启命令:

-XX:+UseG1GC
复制代码

完整的例子:

java -Xmx12m -Xms3m -XX:+UseG1GC -jar Java2demo.jar
复制代码
相关文章
相关标签/搜索