jvm内存管理

参考链接

参考链接

Garbage Collection Concepts

  • garbage collector的作用包括:
    • 分配内存
    • 确定活着的对象不被清理
    • 回收死了的对象占用的内存
  • 寻找和释放垃圾占用的内存空间的过程称为garbage collection
  • 一般情况下,整个堆或堆的一部分被填满时或者达到一个阈值百分比时,开始进行回收。
  • 为对象分配内存的请求主要是在堆中找到一个能放得下对象的内存块,这是一个难点。大部分动态内存分配策略的主要问题就是要避免fragmentation,同时保证分配和回收的效率。

Desirable Garbage Collector Characteristics(垃圾回收器特性)

  • A garbage collector必须是安全的,即或者的对象不能被回收,而死了的对象也应该在少数的几个collection cycles内被回收。
  • garbage collector的效率需要比较高,从而不会引入长时间的暂停。但是对于一些用于计算的系统而言,需要在time, space, and frequency之间进行一些权衡。例如,如果堆空间小,则回收频率高,时间少。如果堆空间大,则回收频率低,时间长。
  • garbage collector需要产生较少的fragmentation。垃圾回收后会产生很多小的空间,压缩这些碎片空间的技术叫compaction。

Design Choices (设计)

  • Serial versus Parallel
    serial collection只会有一个线程执行任务。而parallel collection会把任务分成多个小任务,这些小任务在不同的cpu上同时执行,从而提高执行效率,但是同时也会增加垃圾回收的复杂度以及可能产生一些fragmentation。
  • Concurrent versus Stop-the-world
    stop-the-world garbage collection运行时,应用程序线程会全部停止。如果是Concurrent的方式,则 一个或多个garbage collection tasks会和应用程序线程同时执行。一般情况下,concurrent garbage collector会并发的执行大多数任务,但是有时候必须执行一些短暂的stop-the-world暂停。Stop-the-world garbage collection比concurrent collection要容易,因为这个时候对象不会被改变。因为并发阶段对象可能会被应用程序修改,所以concurrent garbage collector需要特别处理这种情况,有些已经死了的对象可能被标记成活着的不会被回收,所以需要大一些的堆空间。
  • Compacting versus Non-compacting versus Copying
    • Compacting collector: 当垃圾收集器标记完哪些是活着的对象,哪些是垃圾对象后,把活着的对象移动到一起,然后清空剩下的空间。压缩空间之后,为对象分配内存空间会变的简单并且快速,只需要一个pointer记录空闲空间的起始位置,分配对象空间后,这个pointer向后移即可。
    • non-compacting collector:只把垃圾对象占用的空间清除,并不会移动活着的对象压缩空间。这种方式的好处是快,但是会产生内存碎片。这种情况下分配内存的开销会更大。因为每次分配内存时都需要在堆中找到能够放的下新的对象的内存块。
    • copying collector: 把活着的对象拷贝到另外一块不同的内存空间,然后这块内存空间就认为是空闲的了,后续的对象可以分配到这块空间。代价时需要付出额外的时间拷贝活着的对象以及需要额外的内存空间用于存放拷贝的对象。

Performance Metrics(性能指标)

  • Throughput—the percentage of total time not spent in garbage collection, considered over long periods of time.
  • Garbage collection overhead—the inverse of throughput, that is, the percentage of total time spent in garbage collection.
  • Pause time—the length of time during which application execution is stopped while garbage collection is occurring.
  • Frequency of collection—how often collection occurs, relative to application execution.
  • Footprint—a measure of size, such as heap size.
  • Promptness(及时性)—the time between when an object becomes garbage and when the memory becomes available.

Generational Collection(分代收集)

  • 最常用的分代收集配置是分成两代,一代存放年轻对象,一代存放老对象。不同代采用不同的回收算法,这些算法针对观察到的不同代对象的特点做相应的优化。
  • Generational garbage collection利用了以下观察结果,即weak generational hypothesis(弱世代假设),适用于多种编程语言编写的应用程序,包括Java编程语言。
    • 大多数年轻对象不会活的太久
    • 老年对象对年轻对象的引用很少
  • 年轻代回收的会比较频繁并且效率比较高并且很快,因为一般年轻代的空间会比较小并且可能包含很多已经不被引用的对象。
  • 经历了很多次young generation collections并且仍然存活的对象,会被promoted(晋升) or tenured to 老年代,如下图所示。老年代一般比年轻代空间要大,并且老年代被占用的速度会比较慢,所以old generation collections不会很频繁,但是会花费比较多的时间结束。
    jvm内存管理_第1张图片
  • 年轻代的垃圾收集算法通常会提高速度,因为young generation collections是频繁的。而老年代的算法通常是对大空间效率高的,因为老年代占了堆大部分空间。并且老年代算法必须在低垃圾密度下工作良好,因为老年代的垃圾密度比较低。

Garbage Collectors in the J2SE 5.0 HotSpot JVM(垃圾收集器种类)

  • 在J2SE5中包含四种garbage collectors,所有的garbage collectors都是分代回收的。这个章节将会描述generations(分代) and the types of collections(垃圾回收的类型),并且会讨论为什么对象的空间分配会很快并且效率很高。然后会讨论每种garbage collectors的具体信息。

HotSpot Generations(hotspot 分代)

  • Java HotSpot virtual machine的内存分三代:年轻代,老年代,永久代。绝大部分对象初始都会分配在年轻代。年轻代中经历若干次young generation collections仍然存活的对象会被晋升到老年代,然后就是有一些大对象会直接分配到老年代。永久代存放一些类的元数据信息。
  • 年轻代包含一个Eden和两个空间小一些的survivor,如下图所示。绝大部分对象初始会被分配在Eden区。Eden区中经历过一次young generation collection还活着的对象会被复制到survivor区。在任何时刻,只有一个survivor会存放这些年龄大于等于1的对象,另一个survivor保持是空的,直到下一次collection。
    jvm内存管理_第2张图片

Garbage Collection Types(垃圾回收的类型)

  • 当年轻代满了时,会发生young generation collection,也叫minor collection。
  • 当老年代或永久代满了时,会发生full collection,有时也叫major collection。所有分代都会被回收。通常会先回收年轻代,并且使用针对年轻代的回收算法,这种算法能够更高效的识别年轻代的垃圾。然后开始回收老年代和永久代,对于不同的garbage collector,使用对应的老年代的回收算法。
  • 当回收年轻代时,如果老年代没有足够的空间存放年轻代晋升的对象时,则年轻代回收算法将停止运行,老年代回收算法开始运行回收整个堆空间。除了CMS垃圾回收器,因为CMS的老年代回收算法不能回收年轻代。

Fast Allocation(快速分配内存)

  • 在下面的garbage collector描述中,你可以看到大多数情况下,堆中会有连续的大内存块可以为对象分配内存使用。在使用bump-the-pointer(撞击指针)技术大内存块中分配内存效率很高。这个技术是指每次追踪上一个对象分配完内存的指针,后续分配对象内存时,只需判断从这个指针开始,剩下的内存是否能够分配该对象,如果可以则更新指针同时初始化对象。
  • Thread-Local Allocation Buffers (TLABs)。分配内存需要是线程安全的,如果采用全局锁的方式实现线程安全,那么分配内存必然会成为瓶颈。Hotspot jvm的实现方式是为每个线程分配一块缓存,这块缓存是堆中的一小部分内存,线程在各自的内存中分配对象空间,不需要锁。只有这块空间满了需要重新分配一块新缓存的时候才需要同步,重新为线程分配缓存。

Serial Collector

  • 使用Serial Collector,新生代和老年代的collections都会以单线程的方式进行,并且会以stop-theworld(STW)的方式进行垃圾回收。

Young Generation Collection Using the Serial Collector(年轻代垃圾回收)

  • Eden中活着的对象,除了大对象,都会被拷贝到To区。大对象会直接拷贝到老年代。
  • From区中没有到指定年龄的活着的对象都会被拷贝到To区,到指定年龄的对象会被拷贝到老年代。
  • 如果To区满了的话,则剩下的没有拷贝的活着的对象,不管年龄是多少,都会直接晋升到老年代。
  • 年轻代垃圾收集后,Eden区和From区都是空的,To区包含活对象。这时,From区和To区的角色会互换。
    jvm内存管理_第3张图片
    jvm内存管理_第4张图片

Old Generation Collection Using the Serial Collector(老年代和永久代垃圾收集)

  • 老年代和永久代都使用mark-sweep-compact垃圾收集算法
  • mark阶段先标记出或者的对象
  • sweep阶段扫描整个代,确认垃圾对象。
  • compact阶段执行sliding compaction(滑动压实),把活着的对象都移动到开始的地方,剩下的空间就是一整块连续的内存空间。因为这个阶段的压缩工作,后续在老年代或永久代的内存分配工作才可以使用bump-the-pointer技术提高内存分配效率。
    jvm内存管理_第5张图片

When to Use the Serial Collector

64M的堆内存,并且最多能够接受的full GC的时间是半秒。

Serial Collector Selection

J2SE 5中,在不是Server-Class的机器上,serial collector是默认的垃圾回收器。可以使用-XX:+UseSerialGC选项明确指定它。

Parallel Collector( throughput collector)

Young Generation Collection Using the Parallel Collector(年轻代收集)

  • 年轻代收集使用的算法和serial collector一样,只不过是并行的版本。它仍然是一个stop-the-world and copying 的collector,只不过是使用多个cpu并行执行垃圾回收。
  • 下面是parallel collector和serial collector 年轻代垃圾回收的区别
    jvm内存管理_第6张图片

Old Generation Collection Using the Parallel Collector(老年代垃圾回收)

  • 和serial collector的老年代回收算法一模一样,mark-sweep-compact

When to Use the Parallel Collector

  • 那些运行在超过一个cpu的机器上并且对停顿时间没有严格要求的应用,可以使用这个垃圾收集器。这个垃圾收集器的老年代垃圾收集是串行的,所以老年代的垃圾收集时间可能会比较久。例如:批处理、科学计算的应用适合用这个。
  • parallel compacting collector和Parallel Collector比较,前者不管是年轻代还是老年代的垃圾回收都是并行执行的。

Parallel Collector Selection

在J2SE5版本中,在Server-Class类型的机器上,Parallel Collector是默认的垃圾回收器。

Parallel Compacting Collector

  • 这个垃圾收集器是在J2SE 5.0 update 6中出现的,它和Parallel Collector的区别是它使用了新的老年代的垃圾收集算法。注意:这个垃圾收集器最终会替换Parallel collector。

Young Generation Collection Using the Parallel Compacting Collector(年轻代垃圾收集)

  • 和parallel collector的年轻代垃圾收集算法一样

Old Generation Collection Using the Parallel Compacting Collector(老年代垃圾收集)

  • 老年代和永久代使用stop-the-world,并且以基本并行的方式收集,并且会进行sliding compaction(滑动压缩)
  • 老年代收集分为三个阶段,首先每个代的内存逻辑上会被分成固定大小的regions.
  • marking阶段,被应用代码直接引用的活对象的集合被分到不同的垃圾回收线程,然后所有线程开始对所有活着的对象进行标记,一旦一个对象被标记成活的,这个对象的大小和位置的信息会被更新到该对象所在的region的数据中。
  • summary阶段,这个阶段操作的基本单位是region而不是对象。因为每次垃圾收集都会进行压缩,所以每代的左边的空间活着的对象是比较密集的,压缩这部分的空间的成本比较高,而收集的垃圾却比较少。所以summary阶段第一件要做的事情就是从最左边的region开始检测每个region的密度,直到找到一个指针,从这个指针开始向右收集垃圾和压缩空间的代价是值得的。这个指针左边的regions中的对象都不会被移动。summary阶段会计算并存储每个region第一个活着的对象的第一个字节应该被移动到的位置。注意:summary阶段执行是串行的,虽然也可以实现成并行的,但是和marking阶段和compaction阶段相比,这个阶段时间比较短,串行也没关系。
  • compaction阶段,多个垃圾回收线程使用summary阶段计算的数据识别出那些将会被填充的regions,然后所有的垃圾回收线程独立的拷贝活着的对象到那些region中。压缩之后,这一代的一端是紧密的活对象,另一端是整块的空闲的空间。

Parallel Compacting Collector Selection

-XX:+UseParallelOldGC.

Concurrent Mark-Sweep (CMS) Collector(low-latency collector)

  • 对很多应用来说,吞吐量没有响应时间重要。
  • 年轻代垃圾回收一般很快,老年代回收虽然不频繁,但是会产生长时间的暂停。特别是堆内存比较大的时候,为了解决这个问题,Hotspot jvm推出了CMS垃圾回收器。

Young Generation Collection Using the CMS Collector(年轻代垃圾回收)

  • 和parallel collector年轻代回收的算法一样

Old Generation Collection Using the CMS Collector(老年代回收)

  • CMS collector的老年代收集,大部分的阶段是和应用程序并发运行的
  • initial mark阶段,这个阶段会有一个短暂的暂停,这个阶段会标记出Root对象初始集合。这个阶段标志着一个collection cycle的开始
  • concurrent marking阶段,这个阶段会标记出从Root对象出发关联的所有活着的对象,由于这个过程是和应用程序并发运行的,所以对象的引用可能会被应用程序修改,所以这个阶段完成后,可能有些活着的对象没有被标记。
  • remark阶段,为了解决上个阶段的问题,这个阶段会第二次暂停应用程序,然后重新扫描concurrent marking阶段被修改过的对象,标记出活着的对象,注意并不是扫描全部对象。因为remark阶段的工作量会比initial mark阶段大,所以这个阶段会有多个垃圾回收线程并行执行。
  • concurrent sweep阶段,remark阶段完成后,所有活对象就已经被标记了,所以这个阶段就可以清理垃圾了。
  • 下图描述了使用serial mark-sweep-compact算法的老年代回收和CMS collector的老年代回收的区别。
    jvm内存管理_第7张图片

CMS老年代垃圾回收的缺点

  • fragmentation(内存碎片),CMS collector是唯一一个不会进行压缩的垃圾回收器,它收集完垃圾后,不会把活着的对象移动到左边,如下图所示。不压缩空间虽然可以节省时间,但是也造成了一个问题,就是老年代的空闲空间不是连续的。那么为对象分配空间时就没办法使用simple pointer来表示可分配内存的起始点。为了解决这个问题CMS维护了一个集合,这个集合中包含了空的regions,每次为对象分配空间时,就需要扫描这个集合直到找到可以放得下该对象的连续空间。这个解决方案和bump-the-pointer相比,效率较低。
    这也会影响到年轻代垃圾回收的性能,因为在老年代为对象分配内存的情况大多数都是年轻代垃圾回收时晋升到老年代的对象。
    CMS为了解决内存碎片的问题,会统计对象的大小,预估之后分配内存的大小,可能会拆分或合并内存块以满足需求。
    jvm内存管理_第8张图片
  • CMS需要更大的堆内存,因为在concurrent marking阶段,应用程序可以不断的产生新的对象,需要为这些对象分配内存,可能会增加老年代的占用空间。
  • floating garbage(浮动垃圾),虽然concurrent marking和remark阶段只保证活着的对象都能被标记出来,但是这些活着的对象在marking阶段可能会再次变成垃圾,而这些垃圾需要到下次垃圾回收时才能被回收。

CMS老年代垃圾回收开始时机

  • 和其它垃圾回收器不同,CMS的老年代回收不是等到老年代满了才进行回收,而是足够早的提前进行回收,以避免老年代填满。因为如果老年代满了的话,CMS就会使用parallel collector和serial collector的老年代算法stop-the-world mark-sweep-compact进行回收。为了避免这种情况的发生,CMS会根据关于垃圾回收需要的时间以及老年代内存分配的速度的统计信息计算出垃圾回收应该提前开始的时间。
  • initiating occupancy,当老年代的内存占用达到某个比例时也会触发垃圾回收,这个比例叫initiating occupancy。可以用 –XX:CMSInitiatingOccupancyFraction=n选项指定这个比例,n是老年代内存的比例,默认是68.

老年代总结

  • 和parallel collector相比,CMS减少了老年代垃圾回收的停顿时间,代价是年轻代的垃圾回收暂停时间会稍微变长,应用程序的性能会减少一些,需要额外的内存。

Incremental ModeI

  • 在这个模式下concurrent phases会增量运行,不会一直运行。
  • 会周期性的停止concurrent phases,把cpu资源全部让给应用程序执行。可以减少长时间concurrent phases的影响。
  • concurrent phases会被调度,在年轻代垃圾回收的时候运行。
  • 这种模式适合的场景是,应用程序需要concurrent collector提供的短暂的垃圾回收暂停时间,但是机器只有很少的cpu,比如1个或者2个。

CMS Collector Selection

  • 开启CMS使用-XX:+UseConcMarkSweepGC。如果想开启Incremental ModeI模式,使用–XX:+CMSIncrementalMode选项。

Garbage-First Garbage Collector

  • G1执行针对整个堆的操作时,例如global marking(全局标记)时,是和应用程序线程并发执行的,这避免了随着堆的增多或存活对象的增多,垃圾回收的暂停时间变的很长。

G1实现高性能和暂停时间目标的技术

  • 整个堆被分成了相同大小的regions(区域)。
  • G1并发的执行marking阶段,标记出活着的对象。当marking阶段结束后,G1就知道哪些regions垃圾是最多的。它会首先回收这些区域的垃圾,这就叫Garbage-First。如这个名字所示,它会在垃圾比较多的区域进行collection and compaction。
  • G1使用暂停预测模型来满足用户定义的暂停时间目标,并基于指定的暂停时间指标来选择要收集的region的数量。
  • G1会从一个或多个regions中拷贝存活对象到单个的region中,在这个过程中完成回收内存和压缩的目的。这个evacuation(疏散,清除)是并行执行的,以减少暂停时间。因此,每次garbage collection后,G1都会持续的减少碎片。这种压缩的方式比之前的垃圾回收器都要好,CMS是不执行压缩的,parallel collector是针对整个堆执行压缩的,这会造成很长的暂停时间。
  • 知道G1不是一个real-time的垃圾回收器是很重要的,G1会尽力实现暂停时间目标但不是一定可以实现。基于G1收集的collections统计信息,它可以预估在暂停时间目标之内可以回收多少regions。因此G1有一个关于收集regions需要花费多少时间的合理精确的模型,它用这个模型来决定收集哪些以及收集多少个regions,不超过设定的暂停时间。

G1的适用场景

  • G1首要关注的点就是为为那些大内存并且需要低延迟的应用提供解决方案。堆内存在6G及以上,期望的暂停时间在0.5秒内的应用都适合使用G1。

G1的分代模型

jvm内存管理_第9张图片

  • G1在逻辑上是分代的,图中深蓝色的是年轻代,浅蓝色的是老年代,红色的是正在回收的区域。
  • 对象内存分配是在年轻代完成的,当年轻代空间不足时,就会触发年轻代regions的回收(a young collection)。在某些情况下,老年代的regions也会同时被回收,这叫mixed collection。图中红色的region是正在被回收的区域。图中的回收就是一次mixed collection,因为回收的区域中既有年轻代又有老年代。根据回收对象的存活年龄,它们可能被复制到survivor区,也有可能复制到old区。
  • humongous objects, 标记为H的regions是用来存放超过region的一半大小的对象。这种对象会被特殊处理。参考链接

Allocation (Evacuation) Failure(分配或疏散失败),发生full GC

  • 和CMS一样,G1垃圾回收有些步骤是和应用程序并行的,程序分配内存的速度可能比垃圾回收的速度快,有这个风险。这个过程和CMS的很相似,Concurrent Mode Failure。
  • 在G1中,当拷贝一个region中的存活对象到另一个空region的时候,如果找不到可以存放下这些对象的空region时,说明堆中没有空的region了,就会发生allocation failure(因为被回收的区域已经没有空间分配给新对象了),此时会发生Full GC。

Floating Garbage(浮动垃圾)

  • 在垃圾回收期间,一个对象可能已经没有引用了,但是在这次GC中可能不会被回收。
  • SATB, G1使用snapshot-at-the-beginning (SATB)技术来保证所有活着的对象都可以被找到。在the concurrent marking(这个阶段会标记整个heap)开始的时候,SATB会标记所有活着的对象,即使后面这个对象死了,也会认为它是活着的。SATB允许浮动垃圾的产生,和CMS的incremental update相似,Floating Garbage

Pauses(暂停应用程序)

  • G1拷贝活着的对象到新的regions时会暂停应用程序。这些暂停可能发生在young collections,也可能发生在mixed collections。
  • 和CMS一样,G1有一个最终标记阶段也会暂停应用程序。
  • CMS还有一个初始标记阶段,G1的初始标记阶段是在evacuation pause(疏散暂停)的间隙中完成的。
  • G1在collection的末尾有一个cleanup phase,这个阶段是部分STW,部分和应用程序并发的。cleanup phase的STW部分,G1会寻找空的regions同时会在old regions中寻找下一次垃圾回收的候选regions。

Card Tables and Concurrent Phases(卡表和并发阶段)

  • 如果一个garbage collector一次只回收堆的一部分,那么它需要知道未被回收的区域是否有对正在回收区域的对象的引用。这种情况一般发生在分代回收的的garbage collector中,未被回收的区域一般是老年代,正在回收的区域是年轻代。保存老年代对新生代的引用的数据结构叫remembered set。card table是remembered set的一种实现。java hotspot jvm使用字节数组表示一个card table,每一个字节就是一个card。一个card对应堆中的一段内存范围。Dirtying a card 意思是修改这个card对应的字节的值为dirty value,dirty value可能包含card所覆盖地址范围内从老年代到新生代的新指针。
  • Processing a card意思查找card,看看在这个card中是否有老年代到年轻代的指针,并可能对这些信息做一些处理,例如将其转换为另一个数据结构。
  • concurrent marking phase, 这个阶段标记活着的对象。这个阶段从evacuation pause结束开始直到remark phase,initial marking在evacuation pause时完成。
  • concurrent cleanup phase会把回收完的regions添加到free regions的列表中,同时会清除这些regions的remembered sets。

Starting a Concurrent Collection Cycle

  • 正如之前提到的,在mixed collection中,新生代和老年代都会被回收,为了回收老年代regions,G1会扫描整个堆标记活着的对象。concurrent marking phase就是做这件事的。当整个堆的占用百分比达到参数InitiatingHeapOccupancyPercent设置的值时,concurrent marking phase就会开始。InitiatingHeapOccupancyPercent参数的默认值是45.

Pause Time Goal(暂停时间目标)

  • MaxGCPauseMillis指定暂停时间目标,默认值是200ms。
  • G1使用自己的预测模型预测在暂停时间目标之内能够回收多少regions。
  • 在一个collection结束的时候,G1会选出下次垃圾回收的regions(the collection set)。

Ergonomics – Automatic Selections and Behavior Tuning

  • 从 J2SE 5.0开始,jvm会根据当前操作系统和平台的类型,自动选择合适的garbage collector、堆大小、client或server类型的jvm。
  • 另外,这个jdk版本提供了一种针对parallel collectors的动态调整垃圾回收行为的方式。用户可以设定期望的目标,然后garbage collector就会自动的调整heap regions的大小以满足目标。
  • 以上两个功能:基于平台自动选择配置和garbage collection tuning,组合在一起称之为ergonomics。
  • ergonomics的目的是让用户配置最少的配置但可以获得最好的性能。

Automatic Selection of Collector, Heap Sizes, and Virtual Machine(自动选择垃圾回收器,堆大小,vm类型)

  • server-class类型机器的定义,以下定义对于除了32位的Windows操作系统以外的操作系统都适用:
    • 至少两核cpu
    • 至少2G的物理内存
  • 对于不是server-class类型的机器,JVM, garbage collector和堆大小的默认值分别如下:
    • the client JVM
    • the serial garbage collector
    • Initial heap size of 4MB
    • Maximum heap size of 64MB
  • 在server-class类型的机器上,除非命令行明确指定了-client选项,否则jvm默认是server JVM。在运行这server jvm的server-class类型的机器上,默认的垃圾回收器是parallel collector。否则默认垃圾回收器是 serial collector。
  • 在server-class类型的机器上,不管运行的是client还是server类型的jvm,只要garbage collector是parallel garbage collector,则默认的初始堆大小和最大堆大小如下:
    • 初始堆大小为物理内存的六十四分之一,最大为1G。
    • 最大堆大小为物理内存的四分之一,最大为1G。
  • 如果垃圾回收器不是parallel collector,则默认的初始堆大小和最大堆大小和non-server-class机器上的默认值一样,初始堆大小为4M,最大堆大小为64M。

Behavior-based Parallel Collector Tuning

  • 在J2SE 5.0版本中,新增了一种针对parallel garbage collectors的GC tuning方式。用户可以在命令行上指定maximum pause time and application throughput,jvm会自动调整以满足这个目标。

Maximum Pause Time Goal(最大暂停时间目标)

  • 使用-XX:MaxGCPauseMillis=n, 命令行参数指定,n是暂停时间目标,单位是ms。
  • parallel collector会调整堆大小和其他的垃圾回收相关参数,以满足这个目标。这个调整可能会降低系统的吞吐量。这个参数没有默认值。
  • 在某些情况下,这个目标不一定能实现
  • 最大暂停时间目标会单独应用到每一代上,一般情况下,如果这一代的目标没有达到,这这一代会减少自己的大小以满足目标。

Throughput Goal(吞吐量目标)

  • 吞吐量目标用垃圾回收时间和应用时间的比例来衡量。垃圾回收时间是指所有代的垃圾回收时间之和。
  • 使用-XX:GCTimeRatio=n 命令行参数指定,垃圾回收时间和应用程序时间的比例是1 /(1 + n)。例如-XX:GCTimeRatio=19,设置垃圾回收时间占总时间的比例是20%。
    默认值是1%,即n=99。
  • 如果吞吐量目标没有达到,那么每代的大小会增加,越大填满需要越久的时间,这样应用程序就可以运行更久的时间。

Footprint Goal

  • 如果吞吐量目标和最大暂停时间都实现了,那么garbage collector会减少堆大小直到吞吐量目标不能实现。

Goal Priorities(目标的优先级)

  • parallel garbage collectors会优先实现最大暂停时间目标,然后才会实现吞吐量目标,最后才会实现Footprint Goal。

Recommendations(建议)

When to Select a Different Garbage Collector

–XX:+UseSerialGC
–XX:+UseParallelGC
–XX:+UseParallelOldGC
–XX:+UseConcMarkSweepGC

Heap Sizing

  • 可以设置堆的初始大小,最大值,每代的大小。

Tuning Strategy for the Parallel Collector

What to Do about OutOfMemoryError(碰到outOfMemoryError时如何处理)

  • 当没有足够的空间为一个新对象分配空间时会抛出OutOfMemoryError。也就是垃圾回收不能回收足够的空间并且堆也达到最大不能继续扩展了。
  • 抛出OutOfMemoryError并不一定是因为内存泄漏,可能只是因为对当前应用来说堆设置的太小了。
  • 解决OutOfMemoryError的第一步是要看跟在"java.lang.OutOfMemoryError"后面的详细错误日志。下面是常见的例子:
    • Java heap space,表示堆中没有足够空间分配对象了,原因可能是因为配置的堆太小了。也有可能是内存泄漏, HAT 工具可以用来查看所有可达的对象以及这些对象的引用,用来定位这个问题。也有可能是应用代码在finalizers方法中执行了耗时长的任务导致,执行finalizers方法的速度跟不上添加到队列中的finalizers的速度,jconsole management tool可以用来监控在等待执行finalization的对象。
    • PermGen space,表明permanent generation(永久代)满了,可以用命令行参数–XX:MaxPermSize=n,指定永久代大小,n表示大小。
    • Requested array size exceeds VM limit,表明应用程序产生了一个很大的数组对象,比堆的总大小还大。

Tools to Evaluate Garbage Collection Performance(评估垃圾回收性能的工具)

–XX:+PrintGCDetails Command Line Option

  • 在命令行指定–XX:+PrintGCDetails参数是获取garbage collections的初始信息的最简单的方式。每次垃圾回收时,都会相关信息,如垃圾回收之前和之后每一代中活着的对象的大小,每一代的总大小,以及每次垃圾回收花费的时间。

–XX:+PrintGCTimeStamps Command Line Option

  • 除了GC的详细信息外,加了这个参数,还会在每次GC日志的前面加上本次GC的开始时间。这个时间可以帮助你把GC日志和其它的相关日志联系起来。

jmap

  • 这个命令行工具只在linux版本的jdk中才有,Windows系统的jdk中没有。
  • 这个命令会输出内存相关的统计信息。
  • 如果没有跟任何选项,这个命令默认输出加载的共享对象。想要获取更详细的信息,可以加上 –heap, –histo, or –permstat选项。
  • –heap选项可以获取garbage collector的名字,垃圾回收算法的细节(如 parallel garbage collection使用的线程数),堆配置信息,堆的使用情况统计。
  • –histo可以在Class维度获取堆中的对象的直方图。对于每个Class,会输出这个类有多少个实例,以及这些实例占用的总内存,以及Class的全路径名。这个直方图对于了解堆是怎么被使用的很有帮助。
  • –permstat可以输出永久代中对象的统计信息。

jstat

  • jstat使用Hotspot jvm内置的指令集获取应用程序的性能和资源消耗情况的信息。
  • 这个工具可以在定位性能问题时,特别是heap sizing和garbage collection的问题时使用。
  • 这个工具的很多选项可以输出垃圾回收行为的统计信息,以及每一代内存的容量以及使用量。

HPROF: Heap Profiler

  • HPROF是和JDK5.0一起发行的profiler agent。它是一个动态链接的库,通过 Java Virtual Machine Tools Interface (JVM TI)和JVM交互。
  • HPROF可以获取cpu使用率,堆内存分配统计信息。并且它还可以输出heap dumps,还可以打印jvm中所有线程和monitor的状态。
  • HPROF在分析性能问题,lock contention(锁争用),内存泄漏和其他问题时非常有用。

HAT: Heap Analysis Tool

  • HAT工具帮助debug unintentional object retention(无意的对象持有),该术语用于描述对象不再需要,但由于某些活动对象通过某些路径进行引用而保持活动状态。
  • HAT提供了一种方便的方式,可以从HPROF导出的heap快照中浏览某个对象的引用拓扑关系。
  • HAT支持很多查询方式,包括“show me all reference paths from the rootset to this object.”(查看从Root对象到指定对象的所有引用)

Key Options Related to Garbage Collection

  • 很多命令行参数可以使用,比如选择一个garbage collector,指定堆或代的大小,修改garbage collection的行为,获取garbage collection的统计信息。这一章展示一些最常用的选项。如果想获取更多可用的选项以及对应的详细信息,可以参考下一章。

Garbage Collector Selection

jvm内存管理_第10张图片

Garbage Collector Statistics

jvm内存管理_第11张图片

Heap and Generation Sizes

jvm内存管理_第12张图片

Options for the Parallel and Parallel Compacting Collectors

jvm内存管理_第13张图片

Options for the CMS Collector

jvm内存管理_第14张图片

Garbage-First Garbage Collector Tuning(G1调优)

参考链接

  • G1是一个分代并且分区域的垃圾收集器,它的堆被逻辑上分成相同大小的区域。jvm启动的时候就会设置regions的大小。regions的大小范围在1M-32M之间,目标是不超过2048个regions。
  • G1有一个暂停时间目标。在young collection的时候,G1 GC调整回收的年轻代regions的数量来满足暂停时间目标。在mixed GC的时候,G1根据mixed garbage collections的目标数量,堆中每个region存活对象比例以及可接受的堆浪费比例调整回收的old regions。
  • G1会增量的并行的从一个或多个regions(collection Sets(CSets))中拷贝活着的对象到一个或多个不同的新的regions中,这个叫compaction,可以减少内存碎片。拷贝会从可以释放较多空间的regions开始,同时会尽量在暂停时间内完成。
  • G1使用独立的Remembered Sets(RSets)来跟踪其它regions对某个region的引用,每个region有一个RSet。
  • 独立的RSet使独立和并行的收集regions成为可能,因为扫描一个region中对象的引用时,只需要扫描region中的Rset就可以了,而不用扫描整个堆。

Garbage Collection Phases

  • 除了构成young collection或mixed collection的STW的evacuation pauses( Allocation (Evacuation) Failure)外,G1 GC还有并行、并发和multiphase marking cycles。
  • G1在marking cycle开始的时候,使用snapshot-at-the-beginning (SATB)算法逻辑上记录整个堆中活着的对象集合快照。从marking cycle开始以后产生的活着的对象也会被加入到这个快照中。

Young Garbage Collections(年轻代回收)

  • G1为大部分对象分配内存时,会先从Eden区的regions分配。在young garbage collection时,G1会回收Eden区和一个survivor区,这两个区中的活对象会被拷贝到其它regions中。一个特定对象要被拷贝的哪个区的regions中要看这个对象的年龄。年龄足够大的对象会被evacuates到老年代,也叫promoted。否则这个对象会被evacuates到另一个survivor区。并且这个对象会被放在下次young or mixed garbage collection的CSet中。

Mixed Garbage Collections

  • 成功完成concurrent marking cycle后,G1 GC将从执行young garbage collections切换到执行mixed garbage collections。
  • 在mixed garbage collection执行时, G1会随意的添加一些old regions到要收集的eden和survivor regions中。添加的old regions的数量由一些flags控制( Recommendations)。经过多次mixed collections后,G1 GC回收了足够多的old regions后,G1又会开始执行young garbage collections,直到下一次concurrent marking cycle完成,又会开始执行mixed collections。

Phases of the Marking Cycle

marking cycle如下阶段:

  • Initial marking phase(初始化标记): 这个阶段是在young garbage collection的时候发生的,标记roots对象,会发生STW.
  • Root region scanning phase: G1 GC扫描在初始标记阶段标记的幸存区域,以查找对老年代的引用,并标记被引用的对象。此阶段与应用程序同时运行,必须在下一个STW年轻代垃圾收集开始之前完成。
  • Concurrent marking phase(并发标记阶段): G1 GC在整个堆中扫描活着的对象。此阶段与应用程序同时发生,并且可以被STW年轻代垃圾收集中断。
  • Remark phase(重复标记阶段): 这一阶段是STW的,G1 GC会清空SATB缓存,查找未被标记的活着的对象并且执行对应引用的处理。
  • Cleanup phase(清理阶段): 在这个阶段,G1 GC会定义空的regions以及mixed garbage collection将要回收的区域,并且会清理region的Rset。这两个操作是STW的。

Important Defaults(重要的默认值)

jvm内存管理_第15张图片
jvm内存管理_第16张图片

  • -XX:InitiatingHeapOccupancyPercent=45,设置触发marking cycle的整个堆空间的占用阈值,默认是45.
  • -XX:G1MixedGCLiveThresholdPercent=85,设置被包含到mixed GC中的老年代region的空间占用阈值
  • -XX:G1HeapWastePercent=5,设置可以接受的浪费的堆空间的百分比。如果当可回收空间的百分比小于这个值时,不会启动mixed GC的初始化。

Recommendations(调优建议)

  • Young Generation Size,建议不要设置-Xmn或者-XX:NewRatio的值,如果设置了新生代的大小,则G1的pause-time goal就不会生效了。
  • 可以通过尝试调整以下参数,调整mixed GC。
    • -XX:InitiatingHeapOccupancyPercent: 用于改变触发marking的堆内存占用内存阈值百分比。
    • -XX:G1MixedGCLiveThresholdPercent and -XX:G1HeapWastePercent: Use to change the mixed garbage collection decisions.
    • -XX:G1MixedGCCountTarget and -XX:G1OldCSetRegionThresholdPercent: 用于调整老年代Cset的数量。

Overflow and Exhausted Log Messages(溢出和耗尽的日志)

  • 当看到to-space溢出或者耗尽的日志时,说明G1 GC没有足够的空间容纳存活或者promoted(晋升的)对象了,或者它们之和。
    在这里插入图片描述
  • 为了解决上面的问题,可以调整以下参数
    • Increase the value of the -XX:G1ReservePercent option (and the total heap accordingly) to increase the amount of reserve memory for “to-space”.
    • Start the marking cycle earlier by reducing the value of -XX:InitiatingHeapOccupancyPercent.
    • Increase the value of the -XX:ConcGCThreads option to increase the number of parallel marking threads.

Humongous Objects and Humongous Allocations(大对象分配内存)

  • 对于G1 GC,大于region一半的对象就是humongous object。这种对象会被直接分配到老年代的humongous regions中。这些humongous regions是连续的regions。StartsHumongous标记这些连续regions的开始,ContinuesHumongous标记这些region。
  • 分配任何humongous region之前,G1都会检查marking threshold,如果有必要的话,就会初始化一个concurrent cycle。
  • 死了的humongous对象会在marking cycle结束时,在cleanup phase,也会在full garbage collection cycle时释放掉。
  • 为了减小copy对象的代价,在任何evacuation pause都不会处理humongous对象,只有在full garbage collection cycle时,会压缩这种对象。
  • 每个StartsHumongous and ContinuesHumongous set都只包含一个humongous对象。在humongous object对象结束的地方和最后一个region结束的地方之间的空间是被浪费的,如果一个humongous object 的大小只比region的整数倍大一点,那么就会造成很多内存碎片。
  • 如果你发现有很多因为humongous对象分配造成的concurrent cycles发生,并且这些对象的分配使老年代多了很多内存碎片,那么可以调整-XX:G1HeapRegionSize,使这种大小的humongous对象不再是humongous对象,那么这种对象的内存分配就会走正常的内存分配流程了。

你可能感兴趣的:(java基础,jvm,java,算法)