谈谈java内存管理

参考文章:http://www.importnew.com/21463.html

                  https://blog.csdn.net/bruce_6/article/details/38553143

(本文中有部分信息是结合个人理解添加进去的,如果有错误还请指正!)

对于一个Java程序员来说,大多数情况下的确是无需对内存的分配、释放做太多考虑,对Jvm也无需有多么深的理解的。但是在写程序的过程中却也往往因为这样而造成了一些不容易察觉到的内存问题,并且在内存问题出现的时候,也不能很快的定位并解决。因此,了解并掌握Java的内存管理是一个合格的Java程序员必需的技能,也只有这样才能写出更好的程序,更好地优化程序的性能。

一、背景知识:

虽然最近这些年很多言论都号称java已死或者不久即死,但是Java的语言应用占有率一直居高不下。与高性能的C/C++相比,Java具有gc机制,并且没有那让人望而生畏的指针,上手门槛相对较低;而与上手成本更低的PHP、Ruby等脚本语言来说,又比这些脚本语言有性能上的优势(这里暂时忽略FB自己开发的HHVM)。

对于Java来说,最终是要依靠字节码运行在jvm上的。目前,常见的jvm有以下几种:

  • Sun HotSpot
  • BEA Jrockit
  • IBM J9
  • Dalvik(Android)

其中以HotSpot应用最广泛。目前sun jdk的最新版本已经到了8,但鉴于新版的jdk使用并未普及,因此本文仅仅针对HotSpot虚拟机的jdk6来讲。

二、Jvm虚拟机内存简介

2.1java运行时内存区

Java的运行时内存组成如下图所示:

  java-runtime-memory.jpg

其中,对于这各个部分有一些是线程私有的,其他则是线程共享的。

线程私有的有:

  • 程序计数器:当前线程所执行的字节码的行号指示器
  • java虚拟机栈:java方法执行的内存模型,每个方法执行时都会创建一个栈帧、存储局部变量表、操作栈、动态链接、方法出口等信息。
  1. 每个线程都有自己独立的栈空间
  2. 线程栈只存基本数据类型和对象的地址(对象的引用)
  3. 方法中的局部变量存在线程空间空间中(栈中)
  • 本地方法栈:native方法服务,在HotSpot虚拟机中和Java虚拟机栈合二为一

线程共享的有:

  • java堆:存放对象的实例,几乎所有的对象实例及其属性(非静态成员变量、成员方法)都在堆中分配内存存储
  • 方法区:存储已经被虚拟机加载的类信息(静态方法、静态变量)、常量、JIT编译后的代码等数据
  • 运行时常量池:方法区的一部分,用于存放编译期生成的各种字面量和符号引用
  • 直接内存NIO、Native函数直接分配的堆外内存。DirectBuffer引用也会使用此部分内存

2.2对象访问

java是面向对象的一种编程语言,那么如何通过引用来访问对象呢?一般有如下两种方法。

1.通过句柄访问:

   

java堆中分成两部分,一部分存放实例对象数据的实例池,另一部分存放到实例对象以及方法区中的类信息(类类型数据)的指针的句柄池。方法执行的时候,会在栈中开辟一个内存空间用来存储方法运行时出现的一些信息,包括局部变量表(基本数据类型)、对象的引用(reference)等等。而这里的reference存储的就是指向句柄池的信息(可以理解为指向句柄池的指针)。方法在执行的过程中不断地堆栈中的信息进行处理(进栈和出栈),当涉及到的reference出栈时,就会去找到句柄池中的指针,然后执行对象数据。

2.直接指针访问

  

直接指针访问相比于句柄访问来说少了句柄池这一个中间环节,原本句柄池中存放的指针数据现在分别存放如下:实例对象数据的引用(指针)存放于栈中的reference中,对象类型数据(类类型数据)的引用(指针)存放于实例对象中。也就是说在方法执行的过程中,栈中的reference出栈时,首先访问的是实例对象的数据,实例对象中的处理过程根据是否需要引用到对象类型数据而选择是否去方法区中寻找对象类型数据来执行。也就是说实例对象接管了一部分句柄池的功能(保存对象类型数据的引用),而另外一部分句柄池的功能则直接有栈来接管(保存了实例对象的引用)。

2.3内存溢出

在JVM申请内存的过程中,会遇到无法申请到足够内存,从而导致内存溢出的情况。一般有以下几种情况:

  • 虚拟机栈和本地方法栈溢出
    • StackOverflowError: 线程请求的栈深度大于虚拟机所允许的最大深度(循环递归)
    • OutOfMemoryError: 虚拟机在扩展栈时无法申请到足够的内存空间,一般可以通过不停地创建线程引起此种情况
  • Java堆溢出: 当创建大量对象并且对象生命周期都很长的情况下(java垃圾回收器回收速度小于对象创建的速度),会引发OutOfMemoryError
  • 运行时常量区溢出:OutOfMemoryError:PermGen space,这里一个典型的例子就是String的intern方法,当大量字符串使用intern时,会触发此内存溢出
  • 方法区溢出:方法区存放Class等元数据信息,如果产生大量的类(使用cglib),那么就会引发此内存溢出,OutOfMemoryError:PermGen space,在使用Hibernate等框架时会容易引起此种情况。

三、垃圾收集

在通常情况下,我们掌握java的内存管理就是为了应对网站/服务访问慢,慢的原因一般有以下几点:

  • 内存:垃圾收集占用cpu;放入了太多数据,造成内存泄露(java也是有这种问题的^_^。内存泄漏:内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。)
  • 线程死锁
  • I/O速度太慢
  • 依赖的其他服务响应太慢
  • 复杂的业务逻辑或者算法造成响应的缓慢

其中,垃圾收集对性能的影响一般有以下几个:

  • 内存泄露
  • 程序暂停
  • 程序吞吐量显著下降
  • 响应时间变慢

垃圾收集的一些基本概念

  • Concurrent Collector:收集的同时可运行其他的工作进程
  • Parallel Collector: 使用多CPU进行垃圾收集
  • Stop-the-word(STW):收集时必须暂停其他所有的工作进程
  • Sticky-reference-count:对于使用“引用计数”(reference count)算法的GC,如果对象的计数器溢出,则起不到标记某个对象是垃圾的作用了,这种错误称为sticky-reference-count problem,通常可以增加计数器的bit数来减少出现这个问题的几率,但是那样会占用更多空间。一般如果GC算法能迅速清理完对象,也不容易出现这个问题。
  • Mutator:mutate的中文是变异,在GC中即是指一种JVM程序,专门更新对象的状态的,也就是让对象“变异”成为另一种类型,比如变为垃圾。
  • On-the-fly:用来描述某个GC的类型:on-the-fly reference count garbage collector。此GC不用标记而是通过引用计数来识别垃圾。
  • Generational gc:这是一种相对于传统的“标记-清理”技术来说,比较先进的gc,特点是把对象分成不同的generation,即分成几代人,有年轻的,有年老的。这类gc主要是利用计算机程序的一个特点,即“越年轻的对象越容易死亡”,也就是存活的越久的对象越有机会存活下去(姜是老的辣)。

吞吐量与响应时间

牵扯到垃圾收集,还需要搞清楚吞吐量与响应时间的含义

  • 吞吐量是对单位时间内完成的工作量的量度。如:每分钟的 Web 服务器请求数量
  • 响应时间是提交请求和返回该请求的响应之间使用的时间。如:访问Web页面花费的时间

吞吐量与访问时间的关系很复杂,有时可能以响应时间为代价而得到较高的吞吐量,而有时候又要以吞吐量为代价得到较好的响应时间。而在其他情况下,一个单独的更改可能对两者都有提高。通常,平均响应时间越短,系统吞吐量越大;平均响应时间越长,系统吞吐量越小; 但是,系统吞吐量越大, 未必平均响应时间越短;因为在某些情况(例如,不增加任何硬件配置)吞吐量的增大,有时会把平均响应时间作为牺牲,来换取一段时间处理更多的请求。

针对于Java的垃圾回收来说,不同的垃圾回收器会不同程度地影响这两个指标。例如:并行的垃圾收集器,其保证的是吞吐量,会在一定程度上牺牲响应时间。而并发的收集器,则主要保证的是请求的响应时间。

GC的流程

  • 找出堆中活着的对象
  • 释放死对象占用的资源
  • 定期调整活对象的位置

GC算法

  • Mark-Sweep 标记-清除
  • Mark-Sweep-Compact 标记-整理
  • Copying Collector 复制算法
  • Mark-标记从”GC roots”开始扫描(这里的roots包括线程栈、静态常量等),给能够沿着roots到达的对象标记为”live”,最终所有能够到达的对象都被标记为”live”,而无法到达的对象则为”dead”。效率和存活对象的数量是线性相关的。
  • Sweep-清除扫描堆,定位到所有”dead”对象,并清理掉。效率和堆的大小是线性相关的。
  • Compact-压缩对于对象的清除,会产生一些内存碎片,这时候就需要对这些内存进行压缩、整理。包括:relocate(将存货的对象移动到一起,从而释放出连续的可用内存)、remap(收集所有的对象引用指向新的对象地址)。效率和存活对象的数量是线性相关的。
  • Copy-复制将内存分为”from”和”to”两个区域,垃圾回收时,将from区域的存活对象整体复制到to区域中。效率和存活对象的数量是线性相关的。

其中,Copy对比Mark-sweep

  1. 内存消耗:copy需要两倍的最大live set内存;mark-sweep则只需要一倍。
  2. 效率上:copy与live set成线性相关,效率高;mark-sweep则与堆大小线性相关,效率较低。

分代收集

分代收集是目前比较先进的垃圾回收方案。有以下几个相关理论

  • 分代假设:大部分对象的寿命很短,“朝生夕死”,重点放在对年青代对象的收集,而且年青代通常只占整个空间的一小部分。
  • 把年青代里活的很长的对象移动到老年代。
  • 只有当老年代满了才去收集。
  • 收集效率明显比不分代高。

HotSpot虚拟机的分代收集,分为一个Eden区、两个Survivor去以及Old Generation/Tenured区,其中Eden以及Survivor共同组成New Generatiton/Young space。通常将对New Generation进行的回收称为Minor GC;对Old Generation进行的回收称为Major GC,但由于Major GC除并发GC外均需对整个堆以及Permanent Generation进行扫描和回收,因此又称为Full GC。

  

  • Eden区是分配对象的区域。
  • Survivor是minor/younger gc后存储存活对象的区域。
  • Tenured(老年代)区域存储长时间存活的对象。

分代收集中典型的垃圾收集算法组合描述如下:

  • 年青代通常使用Copy算法收集,会stop the world
  • 老年代收集一般采用Mark-sweep-compact, 有可能会stop the world,也可以是concurrent或者部分concurrent。

那么何时进行Minor GC、何时进行Major GC? 一般的过程如下:

  • 对象在Eden Space完成内存分配
  • 当Eden Space满了,再创建对象,会因为申请不到空间,触发Minor GC,进行New(Eden + S0 或 Eden S1) Generation进行垃圾回收
  • Minor GC时,Eden Space不能被回收的对象被放入到空的Survivor(S0或S1,Eden肯定会被清空),另一个Survivor里不能被GC回收的对象也会被放入这个Survivor,始终保证一个Survivor是空的
  • 在Step3时,如果发现Survivor区满了,则这些对象被copy到old区,或者Survivor并没有满,但是有些对象已经足够Old,也被放入Old Space。
  • 当Old Space被放满之后,进行Full GC

但这个具体还要看JVM是采用的哪种GC方案。

New Generation的GC有以下三种:

  • Serial
  • ParallelScavenge
  • ParNew

对于上述三种GC方案均是在Eden Space分配不下时,触发GC。

Old Generation的GC有以下四种:

  • Serial Old
  • Parallel
  • CMS

对于Serial Old, Parallel Old而言触发机制为

  • Old Generation空间不足
  • Permanent Generation空间不足
  • Minor GC时的悲观策略
  • Minor GC后在Eden上分配内存仍然失败
  • 执行Heap Dump时
  • 外部调用System.gc,可通过-XX:+DisableExplicitGC来禁止

对于CMS而言触发机制为:

  • 当Old Generation空间使用到一定比率时触发;HopSpot V1.6中默认是92%,可通过PrintCMSInitiationStatistics(此参数在V1.5中不能用)来查看这个值到底是多少;可通过CMSInitiatingOccupancyFaction来强制指定,默认值并不是复制在这个值上,是根据如下公式计算出来的:((100 -MinHeapFreeRatio) +(double)(CMSTriggerRatio* MinHeapFreeRatio) / 100.0)/ 100.0;MinHeapFreeRatio默认值:40 CMSTriggerRatio默认值:80
  • 当Permanent Generation采用CMS收集且空间使用到一定比率触发;Permanent Generation采用CMS收集需设置:-XX:+CMSClassUnloadingEnabled Hotspot V1.6中默认为92%;可通过CMSInitiatingPermOccupancyFraction来强制指定,同样,它是根据如下公式计算出来的:((100 -MinHeapFreeRatio) +(double)(CMSTriggerPermRatio* MinHeapFreeRatio) / 100.0)/ 100.0;MinHeapFreeRatio默认值:40 CMSTriggerPermRatio默认值:80
  • Hotspot根据成本计算决定是否需要执行CMS GC;可通过-XX:+UseCmsInitiatingOccupancyOnly来去掉这个动态执行的策略。
  • 外部调用System.gc,且设置了ExplicitGCIInvokesConcurrent;需要注意,在hotspot 6中,在这种情况下如果应用同时使用了NIO,可能会出现bug。

3.2 HotSpot垃圾收集器

  

上图即为HotSpot虚拟机的垃圾收集器组成。

Serial收集器:串行收集器

  • -XX:+UserSerialGC参数打开此收集器
  • Client模式下新生代默认的收集器。
  • 较长的stop the world时间
  • 简单而高效

一个单线程的收集器,但它的单线程的意义不仅仅是说明它只会使用一个CPU或一条线程去完成垃圾回收,更重要的是在垃圾收集时,必须暂停其他所有的工作线程(“Stop the World”),直到收集结束。

Serial收集器是一个新生代收集器,单线程执行,使用复制算法。它在进行垃圾收集时,必须暂停其他所有的工作线程(用户线程)。是Jvm client模式下默认的新生代收集器。对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单 线程收集效率。

    clip_image002

此收集器的一个工作流程如下如所示:

收集前:

  

收集后:

  

ParNew收集器(并行GC)

  • -XX:+UserParNewGC
  • +UseConcuMarkSweepGC时默认开启
  • Serial收集器的多线程版本
  • 默认线程数与CPU数目相同
  • -XX:ParrallelGCThreads指定线程数目

ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器,其中一个与性能无关的重要原因是,除了Serial收集器外,目前只有ParNew收集器能与CMS收集器(一款并发的老年代收集器)配合工作。

  •   并行(Parallel):指很多条垃圾收集器并行工作,但此时用户线程仍然处于等待状态。
  • 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行,可能是交替执行),用户程序继续运行,而垃圾收集程序运行于另一个CPU上

对比Serial收集器如下图所示:

   

Parallel Scavenge收集器(并行回收GC)--吞吐量优先

  • 新生代并行收集器
  • 采用Copy算法
  • 主要关注的是达到可控制的吞吐量,“吞吐量优先”
  • -XX:MaxGCPauseMillis -XX:GCTimeRAtion两个参数精确控制吞吐量
  • -XX:UseAdaptiveSizePolicy GC自适应调节策略
  • Server模式的默认新生代收集器

Parallel Scavenge收集器也是一个新生代收集器,它也是使用复制算法的收集器,又是并行多线程收集器。parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。它是 以吞吐量最大化(即GC时间占总运行时间最小)为目标的收集器实现,它允许较长时间的STW换取总吞吐量最大化。

吞吐量= 程序运行时间/(程序运行时间 + 垃圾收集时间),虚拟机总共运行了100分钟。其中垃圾收集花掉1分钟,那吞吐量就是99%。

停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户的体验;而高吞吐量则可以最高效率地利用CPU时间,尽快的完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。

Serial Old收集器(串行GC)收集器

  • Serial的老年代版本
  • Client模式的默认老年代收集器
  • CMS收集器的后备预案,Concurrent Mode Failure时使用
  • -XX:+UseSerialGC开启此收集器

Serial Old是Serial收集器的老年代版本,它同样使用一个单线程执行收集,使用“标记-整理”算法。主要使用在Client模式下的虚拟机。

如果在Server模式下,它主要有两大用途:一是在JDK1.5及之前的版本中与Parallel Scavenge收集器搭配使用,另一个就是作为CMS收集器的后备方案,在并发收集发生Concurrent Mode Failure的时候使用。   
clip_image004

Parallel Old收集器(并行GC)收集器

  • -XX:+UseParallelGC -XX:+UseParallelOldGC启用此收集器
  • Server模式的默认老年代收集器
  • Parallel Scavenge的老年代版本,使用多线程和”mark-sweep”算法
  • 关注点在吞吐量以及CPU资源敏感的场合使用
  • 一般使用Parallel Scavenge + Parallel Old可以达到最大吞吐量保证

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。老年代版本吞吐量优先收集器,使用多线程和标记-整理算法,JVM 1.6提供,在此之前,新生代使用了PS收集器的话,老年代除Serial Old外别无选择,因为PS无法与CMS收集器配合工作。

clip_image005

CMS收集器 并发低停顿收集器

CMS 是一种以最短停顿时间为目标的收集器,使用CMS并不能达到GC效率最高(总体GC时间最小),但它能尽可能降低GC时服务的停顿时间,这一点对于实时或 者高交互性应用(譬如证券交易)来说至关重要,这类应用对于长时间STW一般是不可容忍的。CMS收集器使用的是标记-清除算法,也就是说它在运行期间会 产生空间碎片,所以虚拟机提供了参数开启CMS收集结束后再进行一次内存压缩

  • -XX:UseConcMarkSweepGC 开启CMS收集器,(默认使用ParNew作为年轻代收集器,SerialOld作为收集失败的垃圾收集器)
  • 以获取最短回收停顿时间为目标的收集器,重视响应速度,希望系统停顿时间最短,会和互联网应用。

四个步骤:

  • 初始标记 Stop the world: 只标记GC roots能直接关联到的对象,速度很快。
  • 并发标记:进行GC roots tracing,与用户线程并发进行
  • 重新标记 Stop the world:修正并发标记期间因程序继续运行导致变动的标记记录。这个阶 段的停顿时间会比初始标记阶段稍长,但比并发标记阶段要短。
  • 并发清除

由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以整体来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。

对比serial old收集器如下图所示:

  

CMS有以下的缺点:

  • CMS是唯一不进行compact的垃圾收集器,当cms释放了垃圾对象占用的内存后,它不会把活动对象移动到老年代的一端
  • 对CPU资源非常敏感。不会导致线程停顿,但会导致程序变慢,总吞吐量降低。CPU核越多越不明显
  • 无法处理浮动垃圾。可能出现“concurrent Mode Failure”失败, 导致另一次full GC ,可以通过调整-XX:CMSInitiatingOccupancyFraction来控制内存占用达到多少时触发gc
  • 大量空间碎片。这个可以通过设置-XX:UseCMSCompacAtFullCollection(是否在full gc时开启compact)以及-XX:CMSFullGCsBeforeCompaction(在进行compact前full gc的次数)

CMS收集器的优点:并发收集、低停顿,但是CMS还远远达不到完美,器主要有三个显著缺点:

CMS收集器对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降。CMS默认启动的回收线程数是:(CPU数量+3) / 4。

CMS 收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure“,失败后而导致另一次Full  GC的产生。由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自热会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。这一部分垃圾称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,即需要预留足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分内存空间提供并发收集时的程序运作使用。在默认设置下,CMS收集器在老年代使用了68%的空间时就会被激活,也可以通过参数 -XX:CMSInitiatingOccupancyFraction的值来提供触发百分比,以降低内存回收次数提高性能。要是CMS运行期间预留的内存无法满足程序其他线程需要,就会出现“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置的过高将会很容易导致“Concurrent Mode Failure”失败,性能反而降低。

最后一个缺点,CMS是基于“标记 -清除”算法实现的收集器,使用“标记-清除”算法收集后,会产生大量碎片。空间碎片太多时,将会给对象分配带来很多麻烦,比如说大对象,内存空间找不到连续的空间来分配不得不提前触发一次Full  GC。为了解决这个问题,CMS收集器提供了一个-XX:UseCMSCompactAtFullCollection开关参数,用于在Full  GC之后增加一个碎片整理过程,还可通过-XX:CMSFullGCBeforeCompaction参数设置执行多少次不压缩的Full  GC之后,跟着来一次碎片整理过程。

G1收集器

G1算法在Java6中还是试验性质的,在Java7中正式引入,但还未被广泛运用到生产环境中。它的特点如下:

  • 使用标记-清理算法
  • 不会产生碎片
  • 可预测的停顿时间
  • 化整为零:将整个Java堆划分为多个大小相等的独立区域
  • -XX:+UseG1GC可以打开此垃圾回收器
  • -XX:MaxGCPauseMillis=200可以设置最大GC停顿时间,当然JVM并不保证一定能够达到,只是尽力。

使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小不等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。

G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需要时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。

 

在G1收集器中,Region之间的对象引用以及其他收集器的新生代与老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描的。G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中,如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set之中。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不会对全堆扫描也不会有遗漏。

G1收集器的运作步骤:

  • 初始标记(Initial Marking)
  • 并发标记(Concurrent Marking)
  • 最终标记(Final Marking)
  • 筛选回收(Live Data Counting and Evacuation)

  

初始标记阶段只是标记一下GC Roots 能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这阶段需要停顿线程,但耗时很短。

并发标记阶段:从GC Roots开始对堆中对象进行可达性分析,找出存活对象,这个阶段耗时较长,但可与用户程序并发执行。

最终标记阶段:为了修正在并发标记阶段因用户程序运作而导致标记变化的那一部分标记记录,虚拟机将这部分记录记录在Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs里的数据合并到Remembered Set中,这阶段需要停顿线程,但可与用户程序并发执行。

筛选回收阶段:首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来指定回收计划。这个阶段可与用户程序并发执行,因为只回收一部分Region,时间是用户可控制的。

总结如下:

四、Java7、8带来的一些变化

  • Java7带来的内存方面的一个很大的改变就是String常量池从Perm区移动到了Heap中。调用String的intern方法时,如果存在堆中的对象,则会直接保存对象的引用,而不会重新创建对象。
  • Java7正式引入G1垃圾收集器用于替换CMS。
  • Java8中,取消掉了方法区(永久代),使用“元空间”替代,元空间只与系统内存相关。
  • Java 8 update 20所引入的一个很棒的优化就是G1回收器中的字符串去重(String deduplication)。由于字符串(包括它们内部的char[]数组)占用了大多数的堆空间,这项新的优化旨在使得G1回收器能识别出堆中那些重复出现的字符串并将它们指向同一个内部的char[]数组,以避免同一个字符串的多份拷贝,那样堆的使用效率会变得很低。可以使用-XX:+UseStringDeduplication这个JVM参数来试一下这个特性。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值