首页 技术 正文
技术 2022年11月16日
0 收藏 943 点赞 2,512 浏览 7451 个字

很多公司都有自己的JVM实现,被Oracle收购的sun公司开发的JVM实现名为HotSpot。这一实现是我们最常用到的。

还有哪些JVM实现呢?
比较有名的有Oracle之前收购的BEA公司(就是以前做WebLogic的那家公司)的JRockit,IBM公司的J9 VM等。
还有个半像不像的Dalvik(Goolge开发的运行在android上那玩意)

当然不知名的还有很多,你可以参考这个列表了解下:http://en.wikipedia.org/wiki/List_of_Java_virtual_machines

第四章主要介绍在J2SE 5.0 HotSpot JVM中实现的垃圾收集器。
总共分为7个小部分:1.HotSpot中的分代,2.垃圾收集的类型,3.快速分配技术,4.串行收集器,5.并行收集器,6.并行压缩收集器,7.并发的标记-清扫(CMS)收集器。
帖子分为5篇,第一篇(本篇)作为综述介绍1-3部分,之后每篇帖子介绍一个垃圾收集器。

 J2SE 5.0 HotSpot JVM中的垃圾收集

自J2SE 5.0 update 6起,Java HotSpot 虚拟机包括四种垃圾收集器。所有的收集器都是代化的。本章描述这些分代和收集的类型,讨论为什么对象的
分配通常是快速高效的。然后提供关于每个收集器的详细信息。

HotSpot 中的分代

在Java HotSpot 虚拟机中,内存被组织为3个代:年轻代(young generation)、年老代(old generation)和持久代(permanent generation)。大
部分对象最初在年轻分配。年老代保存着几次年轻代收集后仍然存活的对象和一些可能直接分配到年老代的大对象。持久带存放着JVM认为可以简化垃
圾收集管理的对象,如类和方法以及他们的描述信息。

年轻代包括一个伊甸(Eden)区加上俩个小的生还者区(survivor spaces),如图2。大部分对象直接分配在伊甸区(Eden)。(上面提到的一些
大对象可能直接分配在年老代。)最后一次垃圾收集后生存的对象保存在生还者区,在它们在被认为“足够老”以晋升到年老代之前仍有机会死掉。
在任意给定的时间,一个生还者区(在图中标记为From)保存着这些对象,而另一个则是空的,直到下次垃圾收集前也不会使用。

【摘录】JAVA内存管理-JVM垃圾收集机制【摘录】JAVA内存管理-JVM垃圾收集机制

垃圾收集的类型

当年轻代填满的时候,将执行一个只在这一代的运行的垃圾收集–年轻代垃圾收集(young generation collection)(有时候隶属于一个主收集
(minor collection))。当年老代或持久代填满的时候,将执行一个典型的全收集(Full collection)(有时候隶属于一个主收集(minor
collection))。换句话说,所有的代都会被收集。通常的,使用针对年轻代设计的算法在年轻代先执行,因为这是在年轻代标记垃圾最有效的
算法。之后,收集器指定的年老代收集算法(old generation collection algorithm)在年老代和持久代使用。如果有压缩,每个代分别进行。

有时候,年轻代首先收集完后,年老代太满,以至于存不下从年轻代晋升到年老代的对象。在这种情况下,除了CMS收集器外的其他收集器的年轻
代收集算法都不运行,代替它的是,年老代的收集算法将用在整个堆上。(年老代收集算法CMS是一个特列,因为它不能用来收集年轻代)

译注:这里有点绕口,一个收集器(collector)可能包括多个算法(algorithm),一些用于年轻代,一些用于年老代。这里的意思是CMS这种收
集器外的其他的收集器会在年轻代使用年老代的收集算法(整个堆自然包括年轻代),由于年老代太满的原因。CMS收集器不这样的原因是因为CMS
算法不能用在年轻代。

快速分配

在下面的垃圾收集器描述中你会看到,在很多情况下都存在一个连续的很大的内存块可用于分配对象。用一种被称为空闲指针(bump-the-pointer)
的简单技术在这些块上分配内存是很高效的。就是说,前一次分配的痕迹会保留下来,当有一个新的分配请求时,需要做的仅仅是检查新对象是否
能够放的下,如果能,则更新指针引用并初始化对象。

对多线程应用程序来说,分配操作必须是线程安全的。如果使用全局锁保证线程安全,会使得到一代的分配成为瓶颈,并会影响性能。代替它的是
HotSpot JVM采用一种称为Thread-Local Allocation Buffers(TLABs)的技术。每个线程从自己拥有的缓存(意思是代中很小的一个部分)中分配
从而改善了多线程分配的能力。由于线程只能分配对象到自己的TLAB中,利用空闲指针技术在不需要任何的锁的情况下内存分配依然很快。很少发
生的同步只有在线程填满了自己的TLAB后申请一个新的的时候才需要。有几个技术用于最大限度的减少由于使用TLAB技术引起的空间浪费。例如,
TLABs浪费的Eden空间被分配器控制在不到1%的水平。结合使用TLABs和使用空闲指针的线性分配(linear allocations)使得每次分配都很有效,
只需要大概10条本地指令(native instructions)。

HotSpot中的垃圾收集之串行收集器

串行收集器(Serial Collector)

在串行收集器中,年轻代和年老代的收集都是串行的(只使用一个CPU),并且使用停止一切的模式。就是说收集发生时会挂起应用程序。

串行收集器的年轻代收集
   
     图3演示了在年轻代使用串行收集器的操作。在伊甸区(Eden)存活的对象除了那些太大而不合适放入的对象都要复制到在图中标记为“To”的
    最初是空闲状态的生还者区。那些太大的对象直接复制到年老代。另一个使用中的生还者区(标记为From)中的对象中,较为年轻的对象也复
    制到标记为To的生还者区中,较为年老的则复制到年老代。注意:如果“To”的区域满了,从Eden区或From区存活的对象就不再复制到To了,而
    是直接晋升而不管这些对象在年轻的的垃圾收集中存活了几次。复制完成后,根据定义,不需要检查,任何留在Eden区或From区的对象都不是
    存活的。(这些垃圾对象在图中标记为X,但真实的收集器不会检查或标记这些对象。)
【摘录】JAVA内存管理-JVM垃圾收集机制【摘录】JAVA内存管理-JVM垃圾收集机制

图3 年轻代的串行收集

年轻代的收集完成后,Eden区和以前使用的生还者区都被置空,只有之前空闲的生还者区保存活着的对象。这时,生还者区交换了角色。如图4。

【摘录】JAVA内存管理-JVM垃圾收集机制

图4 年轻代收集完成之后
   
    串行收集器的年老代收集
   
    串行收集器在年老代和持久代使用标记-清扫-压缩(mark-sweep-compact)算法。标记阶段,收集器识别哪些对象仍然活着。清扫阶段“扫荡”
    整个代,识别垃圾。之后,收集器执行平移压缩(sliding compaction),将存活的对象平移到代的前端(持久代类似),相应的在尾部留下
    一整块连续的空闲空间。如图5。压缩后,以后的分配就可以在年老代和持久代使用空闲指针(bump-the-pointer)技术。(译注:上一篇帖子的
    快速分配部分有介绍。)

【摘录】JAVA内存管理-JVM垃圾收集机制

图5 年老代的压缩
        
    何时使用串行收集器
   
    串行收集器作为一种选择,在那些运行在客户端环境(client-style machines)的对短暂停没有要求的应用程序用中大量使用。
    串行收集器是大多数客户端式(client-style machines)机器上运行的应用程序的选择,这些应用对短暂停没有要求。在今天的硬件上,串
    行收集器可以有效的管理许多拥有64M堆内存的不平凡的应用程序,并且拥有相对短的在最坏情况下小于半秒的全收集表现。
   
    选择串行收集
   
    在第五章描述中,J2SE 5.0把串行收集器自动选为非服务器级机器(not server-class machines)的默认垃圾收集器。在其他机器上,串行
    收集器可以用命令行参数 -XX:+UseSerialGC 明确指定。
HotSpot中的垃圾收集之并行收集器

今天,很多java应用程序运行在拥有很大的物理内存的多核机器上。并行收集器(parallel collector),又名吞吐量收集器(throughput
collector)被设计出来以便利用多核的优势,而不是空闲很多cpu,只用其中一个做垃圾收集。

并行收集器的年轻代收集
   
    并行收集器使用一个串行收集器使用的年轻代收集算法的并行版本。它仍然是一个stop-the-world的拷贝算法,但使用多个CPU并行的运行,
    减少了垃圾收集的开销,因此增加了吞吐量。图6说明了年轻代的串行收集器和并行收集器的不同。

【摘录】JAVA内存管理-JVM垃圾收集机制

图6 比较年轻代的串行和并行收集
   
    并行收集器的年老代收集
   
    并行收集器在年老代使用与串行收集器相同的标记-清扫-压缩(mark-sweepcompact)收集算法。
        
    何时使用并行收集器
   
    在拥有不止一个CPU的机器上运行的应用程序可以从并行收集器中获得好处,同时这些应用不能有暂停时间的约束,因为虽然不常见但可能较
    长的年老代收集仍可能发生。例如,如下应用对于并行收集器通常是合适的:批处理、广告、工资、科学计算等等。
   
    相比并行收集器你可能更愿意考虑并行压缩收集器(parallel compacting collector)(在下面描述),因为它在所有代执行并行收集,而
    不仅仅是年轻代。
   
    选择并行收集器
   
    J2SE 5.0把并行收集器自动选为服务器级机器(server-class machines)(在第五章定义)的默认垃圾收集器。在其他机器上,并行收集器
    可以用命令行参数 -XX:+UseParallelGC 明确指定。
HotSpot中的垃圾收集之并行压缩收集器

并行压缩收集器

在J2SE 5.0 update 6中包含了并行压缩收集器(parallel compacting collector)。它与并行收集器的不同是在年老代垃圾收集中使用了一个新
的算法。注意:最终,并行压缩收集器会替换并行收集器。

并行压缩收集器的年轻代收集
   
    与并行收集器在年轻代使用的收集算法一致。
   
    并行压缩收集器的年老代收集
   
    在并行压缩收集器中,年老代和持久代使用stop-the-world的、主要是并行的平行压缩算法。包括三个阶段。首先,每个代逻辑的分为固定
    大小的区域。在标记阶段(marking phase),最初在应用程序代码中能够直接到达的对象集合划分给垃圾收集线程,然后并行的标记所有存
    活的对象。如果一个对象是活着的,关于这个对象的大小和位置的信息就会更新到它所在区域的数据中。
   
    摘要阶段(summary phase)作用在区域上,而不是对象上。基于之前几次收集后的压缩结果,通常每个代的左侧的一些部分是很稠密的,存
    放着大部分存活的对象。压缩他们以从这些稠密的区域回收大量的空间是不值得的。所以在摘要阶段的第一件事是检查区域的密度,从左边开
    始,直到达到一个临界点,从这个点及其右侧的区域回收空间相比压缩的消耗是值得的。这个临界点左侧区域归为稠密前缀(dense prefix),
    在这些区域中的对象不会移动。临界点右侧的区域则被压缩以消灭所有的死亡空间。摘要阶段计算并存储在每个被压缩区域中存活的对象的第
    一个字节的位置。注意:摘要阶段一般实现成串行阶段;并行化是可行的,但是对性能来说没有并行化标记和压缩阶段更重要。
   
    在压缩阶段(compaction phase),垃圾收集线程利用摘要数据确定哪些区域需要填满,然后每个线程独立的拷贝数据到对应的区域中。最后
    产生一个在一端很稠密,另一端只有一个大空闲块的堆空间。
   
    何时使用并行压缩收集器
   
    相比并行收集器,并行压缩收集器对于运行在不止一个cpu的机器上的应用程序拥有更多好处。额外的在年老代并行的操作减少了暂停时间,
    并且使得并行压缩收集器相比并行收集器对于有暂停时间要求的应用更合适。并行压缩收集器不适合在大型共享机器(例如SunRays)上运行
    的应用,在这些机器上一个独立应用不能独占多个CPU太长时间。在这些机器上,应当考虑减少垃圾收集线程(使用命令行参数
    –XX:ParallelGCThreads=n)或者选择一个不同的收集器。
   
    选择并行压缩收集器
   
    如果你想使用并行压缩收集器,必须通过命令行参数-XX:+UseParallelOldGC显示的指定。
HotSpot中的垃圾收集之CMS收集器

并发标记-清扫收集器
   
对于很多应用程序来说,从始到终的吞吐量并没有更快的响应速度重要。年轻代的垃圾收集一般不会导致长时间的暂停。但是,年老代的收集,尽
管比较少,会导致长时间的暂停,特别是堆非常大时。为了解决这个问题,HotSpot JVM包括了一个称为并发标记-清扫收集器(concurrent
mark-sweep (CMS) collector)的收集器,也称为低延时收集器(low-latency collector)。

CMS收集器的年轻代收集

    与并行收集器一样。
   
    CMS收集器的年老代收集
   
    使用CMS收集器时,大部分的年老代收集工作都与应用系统并行的执行。

CMS收集器的一个收集周期从一个短暂的暂停开始,称为初始标记(initial mark),此时识别初始的应用程序代码直接引用的活动对象集合。
    然后进入并发标记阶段(concurrent marking phase),收集器从上面那个集合开始标记所有的间接引用的活动对象。由于在标记的同时,
    应用程序依然在运行并行在不断更新引用字段,所以在并发标记阶段结束时无法担保所有的活动对象都被标记。为了解决这个问题,应用程序会
    二次暂停,叫做重新标记(remark),重新访问所有在并发标记期间更改过的对象作为最终标记。重新标记的暂停时间通常比初始标记的暂停
    长很多,因此会使用多线程以提高效率。

在重新标记阶段的最后,保证所有在堆中的活动对象都已经标记,所以随后的并发清扫阶段(concurrent sweep phase)回收所有标记的垃圾。
    图7说明了串行标记-清扫-压缩收集器与CMS收集器在年老代收集上的差别。
      【摘录】JAVA内存管理-JVM垃圾收集机制

图7 比较串行和CMS年老代收集
   
    因为一些任务,如在重新标记阶段重新访问所有对象,收集器增加了大量的工作,同时开销也显著地增加了。对于想要减少暂停时间的收集器来说,
    这是一个典型需要权衡的场景。

CMS收集器是仅有的无压缩(non-compacting)的收集器。就是说,当它释放了由垃圾对象占用的空间后,不会移动存活的对象到年老代的一边。
    参考图8.

【摘录】JAVA内存管理-JVM垃圾收集机制

图8 CMS在年老代的清扫(无压缩)

这节省了时间,但是导致空闲空间不再连续。收集器再也不能用一个简单的指针表示可分配下一个对象的空闲空间。取而代之,现在必须使用一个
    空闲列表。它创建一些列表,将未分配的内存区域链接到一起,当需要分配对象时,必须搜索相应的列表(基于需要的内存大小)找到一个足够大
    的区域以容纳该对象,这比使用空闲指针(bump-the-pointer)技术在年老代分配对象更加耗时。年轻代的收集也增加了额外的开销,因为大部分
    的年老代收集是由于年轻代收集导致的对象晋升。
   
    CMS收集器的另外一个缺点是相比其他收集器需要更大的堆内存。由于允许应用程序在标记阶段运行,应用程序可能会继续分配内存,从而可能导致
    年老代持续增长。此外,虽然收集器保证在标记阶段找到所有活动的对象,但一些对象可能这个阶段变为垃圾,这些垃圾直到下次年老代垃圾收集之
    前不会回收。一些对象引用着悬浮垃圾(floating garbage)。
   
    最后,由于没有压缩会导致产生内存碎片。为了处理碎片,CMS收集器跟踪流行的对象大小,估算将来的需求,并可能分裂或加入空闲块以满足需求。
   
    不像其他收集器,CMS收集器不是在年老代满的时候启动的。而是试图启动的足够早以便能在年老的满之前完成收集工作。否则,CMS收集器回到被
    并行或串行收集器使用的更耗时(time-consuming)的暂停一切的标记-清扫-压缩(stop-the-world mark-sweep-compact)算法。为了避免这种情况,
    CMS收集器的开始时间是基于之前垃圾收集的耗时以及年老代收集频率的统计结果的。如果年老代的占用率超过初始占用率也会导致CMS收集器启动
    收集工作。初始占用率的数值由如下命令行参数设置–XX:CMSInitiatingOccupancyFraction=n,这里的n是年老代大小的百分比。默认值是68。
   
    总之,相比并行收集器,CMS收集器减少年老代的暂停时间–有时是很可观的–伴随着年轻代暂停时间的些微延长,吞吐量的一点降低,和额外的堆内
    存需要。
   
    增量模式(Incremental Mode)
   
    CMS收集器可以用在一种并发阶段是增量的模式下。这种模式通过定期停止并发收集阶段退换后台处理给应用程序来减少长并发阶段的影响。这个工作是
    这样实现的,收集器将时间分成小块然后在年轻代收集的间隙执行。当收集器运行在非常少的处理器的机器上(例如1或2个),应用程序又非常需要
    收集器提供的短暂停时,这个特性就非常有用。对于使用此模式的更多信息,请参考在第九章提到的“Tuning Garbage Collection with the 5.0
    Java Virtual Machine”。
   
    何时使用CMS收集器
   
    如果你的应用程序需要更短的垃圾收集暂停,并且能承受在应用程序运行期间共享处理器资源给垃圾收集器(由于并发,CMS收集器在收集期间占了
    一部分本应分配给应用程序的CPU周期),就可以使用CMS收集器。特别的,应用程序持有一大堆的存活时间又长的数据(一个很大的年老代),同时又
    运行在2个或以上处理器的机器上,有利于从这个收集器获得好处。例如web服务器。任何应用当有短暂停的需要时都应该考虑CMS收集器。在单处理器上,
    合适大小年老代的交互式应用程序也可能获得比较好的效果。
   
    选择CMS收集器
   
    如果想使用CMS收集器,你必须明确的通过这个命令行参数选择 -XX:+UseConcMarkSweepGC。如果想运行在增量模式,通过–XX:+CMSIncrementalMode选项启用。

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,104
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,581
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,428
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,200
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,835
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,918