美文网首页JVM
JVM垃圾回收之前世今生

JVM垃圾回收之前世今生

作者: sun4lower | 来源:发表于2017-03-04 12:09 被阅读120次

    本文将带领大家一起来了解JVM垃圾回收(Garbage Collection)的前世今生,希望对初学者有所帮助。

    JVM

    首先用一张图简单的描述JVM的架构:

    JVM的运行时数据区分为线程私有(图中的白色部分)和数据共享区域(图中的黄色部分),而JVM的GC就是针对Heap空间进行的。

    说明:文中如未特殊说明,默认是使用HotSpot JVM进行说明。

    关于性能

    我们研究学习GC机制的主要目的就是为了是我们开发的应用程序具有更高的性能,而根据具体应用程序需求的不同可以将对性能的需求大致分为两方面:

    • 更快的响应时间 -- Responsiveness
    • 更大的吞度量 -- Throughput

    例如在浏览网页时,我们需要更快的响应时间;而更大的吞吐量则表现在需要在特定时间内处理尽可能多的任务,所以我们需要根据实际项目的需求来分析出我们到底是需要更快的响应时间还是更大的吞吐量。

    为什么要进行垃圾回收?

    在C语言中,我们需要手动的分配和释放内存,而在Java中我们使用JVM的垃圾回收机制自动且动态的为我们释放堆内存(Heap),即删除掉Dead状态的对象(我们的应用程序中不在持有该对象的引用)并保留Alive状态的对象(我们的应用程序中仍然有持有该对象引用的地方),如果Heap中的对象越来越多就会造成OOM(内存溢出)并中断我们应用程序的正常运行,所以对Heap进行垃圾回收是必须的,并且垃圾回收的性能将直接影响应用程序的性能。

    垃圾回收的方法

    Marking

    首先GC会将会对Heap中的对象进行扫描(扫描所有的objects将会是一项非常耗时的操作),并将Dead状态的对象所占有的内存区域进行标记(黄色部分),如下图所示:

    傻傻的删除操作

    标记完成后,GC就会将标记的内存区域中的对象回收并清理,清理完成后Memory Allocator会持有这些空闲部分引用的list,如果有新的内存分配请求Memory Allocator会查找出一块满足要求的内存进行分配。之所以称这种方法为“傻傻的删除操作”就是因为会产生很多的内存碎片,就类似我们的磁盘碎片一样,而且Memory Allocator每次分配内存的时候都需要找出一块能够满足需求的内存,更槽糕的是如果没有能够找到满足需求的内存那就悲剧了。

    Compacting

    为了提高性能,诞生出了Compacting的方法,即现将标记的部分删除,然后将Referenced objects占用的内存移动到一起(Compacting),Memory Allocator会持有空闲部分开始的引用。这样下次分配操作就变得方便和快速,但是当对象逐渐增多的时候,Compacting操作又会占用过多的时间。

    JVM Generations

    为了继续提高GC的性能,heap被分为了几个部分,即我们通常所说的generations。具体分为:Young Generation(年轻代)、Old Generation(老年代)和Permanent Generation(永久代,Java8提出了MetaSpace),我在这里举一个更能吸引大家的例子:将GC比喻成媒婆,Young Generation中的对象比喻成一个情窦初开的美女,而到Old Generation时刚才的美女变成了大龄剩女,而Permanent Generation中的元数据信息代表了女方的家人们(主要存储的是被应用程序使用的classes、Java SE library classes和methods的元数据信息),他们都一直在背后默默地支持女方。(这里需要说明一下,举这个例子只为方便大家理解,没有不尊重女性的意思,希望所有的女性朋友都能够找到自己的如意郎君!)

    而Yong Generation分成了三个部分:eden、S0、S1,下面我们来描述一下它们各自的作用:

    在程序运行时不断有新的objects被分配到eden中,即这里都是情窦初开的美女,当eden部分被填满时,就会引发minor garbage collection,在这个阶段应用程序暂停,直到完成回收,下面我们用几幅图来描述这个过程:

    上图说明:当Eden被填满后,执行minor garbage collection,对Unreferenced objects进行标记,然后将Referenced objects复制到S0,而被标记的就被回收和清理(很多美女找到了如意郎君),当然被标记的objects越多,执行minor garbage collection的效率就会越高。

    上图说明:当瑕疵minor garbage collection的时候,会将Eden和S0中未被标记的objects(依然没有找到男朋友的美女)复制到S1,并且年龄增加了1,被标记的当然就是找到如意郎君的。

    上图表示又进行了一次minor garbage collection,然后一直重复此过程。

    当S0或者S1中的一些Unreferenced objects达到一定年龄(美女的年龄超多了指定的年龄)就会被传入到Old Generation(情窦初开的美女编程了大龄剩女),而以上的这些步骤都属于minor garbage collection。

    同时,在Old Generation的objects也会被执行垃圾回收,即major garbage collection,即大龄剩女最终找到了如意郎君。

    下面我们通过监控工具来向大家动态的展示这个过程。

    动起来

    首先我们要准备JDK及其Demo:下载地址

    然后使用如下命令运行Java2demo这个例子:(注意路径改成自己的,详细的参数会在后面进行解释)

    java -Xmx100m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar /Users/mac/Documents/jdkexample/demo/jfc/Java2D/Java2Demo.jar
    

    运行成功会出现以下界面:(这里我使用的是Mac系统)

    然后我们点击Transforms部分,显示如下:

    然后我们使用jvisualvm命令启动Visual VM:

    然后我们选择Tool--Plugins,在Available Plugins中选择Visual GC并点击Install进行安装,安装好后可以在Installed中看到Visual GC:

    然后我们在Java VisualVM左侧的Applications列表中选择启动的Java2Demo,右键点击选择打开,在右边就可以看到Java2Demo运行的基本信息:

    然后选择Visual GC,就会看到动态的GC效果:

    可以看到图中的Eden、S0、S1的动态效果和我们上面的描述一致。

    Garbage Collectors

    首先我们看一下运行上述例子使用到的一些基本配置参数:

    参数 描述
    -Xms JVM运行的初始的Heap的大小
    -Xmx 最大的Heap大小
    -Xmn Yong Generation所占的大小
    -XX:PermSize 设置初始的Permanent Generation的大小
    -XX:MaxPermSize 设置Permanent Generation的最大尺寸

    GC的几种方式

    下面我们介绍几种GC的方式:

    Serial GC

    Serial GC是在Java SE 5和6中使用的,minor和major GC都是连续的执行且使用一个虚拟的CPU,内部使用的是我们在上面讨论过的mark-compact的方法。该方式通常适用于不要求响应速度且运行在客户端的机器,如果要使用Serial Collector可以使用如下配置参数:

    -XX:+UseSerialGC
    

    Parallel GC

    Parallel GC使用多条线程来执行minor garbage collection,可以使用如下配置设置使用的线程数:

    -XX:ParallelGCThreads=<desired number>
    

    Parallel GC可以缩短minor garbage collection的执行时间,进而缩短应用程序的暂停时间。通常Parallel GC用来处理对吞吐量需求比较高的应用,可以使用如下配置参数使用Parallec GC:

    -XX:+UseParallelGC
    

    使用这个配置参数,minor部分会使用多个线程,而major部分使用一个线程。

    -XX:+UserParallelOldGC
    

    使用这个配置参数,minor和major都会使用多个线程进行处理,并且是多线程执行compacting操作。HotSpot JVM仅仅在major部分使用compacting,而minor部分则使用copy操作。

    Concurrent Mark Sweep(CMS)Collector

    该方式通常应用在对响应速度要求较高的应用中,minor部分使用的和和Parallel方式相同,即都是copy的方式,而major部分是和应用程序并行执行的,所以该方式只需要应用程序暂停很短的时间。使用CMS方式需要进行如下配置:

    -XX:+UseConcMarkSweepGC  #开启CMS的方式
    
    -XX:ParallelCMSThreads=<n>  #使用的线程数
    

    G1 Garbage Collector

    G1 Garbage Collector是Java 7开始提出的一种不同于以上几种实现方式的全新垃圾回收机制,我们会用另一篇文章单独进行说明,如果我们想使用这种方式可以使用如下配置参数:

    -XX:+UseG1GC
    

    本文我们简单的图像表示方法分析了垃圾回收机制的流程和几种实现方式,并使用Java VisualVM动态的展示了其过程,希望给初学者一些引导和灵感。

    本文为原创,欢迎转载,转载请注明出处、作者,谢谢!

    相关文章

      网友评论

        本文标题:JVM垃圾回收之前世今生

        本文链接:https://www.haomeiwen.com/subject/rnodgttx.html