Nivelle 开拓视野冲破艰险看见世界 身临其境贴近彼此感受生活

java内存管理以及GC

2017-09-22
jvm

内存管理简介

内存管理的职责为:分配内存,回收内存,没有自动内存管理的语言容易发生错误,典型的问题为内存泄漏,内存已经分配,但是已经没有了指向该内存的指针,导致内存泄漏。

GC简介

针对以上问题,引入了garbage collect机制,由运行时环境来自动管理内存。garbage collect解决了悬挂指针和内存泄漏的大部分问题

Garbage Collector(简称Collector)和Garbage Collection(简称GC)的区别。

Garbage Collector的职责:

  • 分配内存
  • 保证有引用的内存不被释放
  • 回收没有指针引用的内存

对象被引用称为活对象,对象没有引用被称为垃圾对象/垃圾/垃圾内存,找到垃圾对象并回收是Collector的一个主要工作,该过程称为GC.

Collector一般使用一个称为堆的内存池来进行内存的分配和回收。一般的,当堆内存满或者到一个阀值时,堆内存或者部分堆内存被GC.

Collector的特性

  • 保证有引用的对象不被GC.

快速的回收内存垃圾

  • 在程序运行期间GC要高效,尽量少的影响程序运行.和大部分的计算机问题一样,这是一个关于空间,时间,效率平衡的问题.

  • 避免内存碎片,内存碎片导致占用大量内存的大对象申请难以满足.可以采用Compaction(压缩)技术避免内存碎片.

  • 良好的拓展性,内存分配和GC在多核机器上不应该成为性能瓶颈.

Compaction :把活对象移向连续内存区的一端,回收其余的内存以便以后的分配.

设计或选择Collector

  • 串行或并行

串行Collector在多核上也只有一个线程在运行,并行Collector可以同时在多个线程执行GC,但是其算法更加复杂.

  • 并发或Stop the world(暂停全世界)

stop the world collection执行GC时,需要冻住所有内存,因此更简单一些,但是,在gc时,程序是被挂起的.并发GC时,程序和GC同时执行,当然,一般的并发GC算法还是需要一些Stop the world 时间.

  • GC算法
  1. Compacting:去除内存碎片,回收内存慢,分配内存快.

  2. Non-compactiong:容易产生内存碎片,回收内存快,分配内存慢,对大对象 内存分配支持不好

  3. Copying:赋值或对象到新的内存区域,原有内存直接回收,需要额外的时间来做复制额外的空间来做存储.

GC性能指标

Throughput:程序时间(不包含GC时间)/总时间

GC overhead:GC时间/总时间

Pause time :GC运行时程序挂起时间

Frequency of GC:GC频率

Footprint:a measure of size,such as heap size

Promptness:对象变为垃圾到该垃圾被回收后内存可用的时间。

分代GC

分代GC把内存划分为多个代(内存区域),每个代存储不同年龄的对象.常见的分为2代,yong和old.分配内存时,先从yong代分配,如果yong代已满,可以执行GC(可能导致对象提升),如果有空间,则分配,如果yong代还是没有空间,可以对整个内存堆GC.yong代GC后还存活的对象可以提升为old代.

该机制基于以下观察事实:

  • 大部分新分配的对象很快就没有引用了,变成垃圾

  • 很少有old代对象引用yong代对象

基于代内存存储对象的特性,对不同代的内存可以使用不同的GC算法.Yong代GC需要高效,快速,频繁地执行,关注点在速度上.old 代由于增长缓慢,因此GC不频繁,但其内存空间比较大,因此,需要更长时间才能完成GC.关注点在内存空间利用率上.

image

java Collector

jvm的内存分为3代:yong,old,permanent

大部分对象存储在Yong代,在Yong代中经历数次GC存活的对象可以提升到Old代,大对象也可以直接分配到Old代。

Permanent代保存虚拟机自己的静态(refective)数据,例如类(class)和方法(methond)对象。

Yong代由要给Eden和2个survivor组成。大部分的对象的内存分配和回收再这里完成。Survivor存储至少经过一次GC存活下来的对象,以增大该对象在提升至old前被回收的机会。2个survivor中有一个为空。分别为From 和 to survivor.

当yong代内存满,执行yong代GC(minor GC)

当old或permanent代内存慢,执行full GC(major GC),所有代都被GC.一般先执行yong GC,再执行old.有时old代太满,以至于如果yong GC先运行,则无法存储提升对象.这时,Yong GC不运行,old GC算法在整个堆上运行.(CMS collector是个例外,该collector不能运行在young 代上)。

image

快速内存分配

大部分的内存分配请求发送时,Collector都有一块大的内存块,简单的内存大小计算和指针移动就可以分配内存了.因此非常快速.该技术称为bump-the-pointer技术

对于多线程的内存分配,每个线程使用Thread Local Allocation Buffer(TLAB)[这种技术的原理就是为每个线程分配一个缓冲,用来分配线程自己的对象。]进行分配,因此还是很高效.TLAB可以看作一个线程的特殊代.只有TLAB满的时候才需要进行同步操作.

这种技术的原理就是为每个线程分配一个缓冲,用来分配线程自己的对象。

GC根集合

GC运行时当前程序可以直接访问的对象.如线程中当前调用栈的方法参数,局部变量,静态变量,当前线程对象等等.Collector根据GC根集合来寻找所有活对象.GC根集合不可达对象自然是垃圾了.

Serial Collector(串行)

特点:单线程,Yong and old GC是串行,stop the world.

Yong GC

Eden中活对象copy到to survivor中,大对象直接进old代.

From survivor中相对老的活对象进入old代,相对年轻的对象进入to survivor中.如果to survivor放不下活对象,则这些对象直接进入old,经历过yong GC,Eden和from survivor都变成空的内存区域,to survivor存储有活的对象. to survivor和from survivor角色互换.

old permanent GC

Mark-sweep-compact算法,

  • s1标识哪些对象是活的,
  • s2标志哪些对象是垃圾,
  • s3把活的对象压缩到内存的一端,

以便可以使用bump-the-pointer处理以后的内存分配请求.

非server-class machine的默认GC,也可以使用命令行参数来设定.-XX:+UseSerialGC

Parallel Collector/Throughput Collector(并行)

利用了现代计算机大部分是多核的事实.

Yong GC

Yong GC 和Parallel Collector一样,是一个stop the world 和 copying Collector.只不过是多线程并行扫描和做copy,提高速度,减少了stop the world的时间,增大了throughput.

image

Old permanent GC:和serial collector一样,mark-sweep-compact算法,单线程.

Server-class machine的默认GC,也可以使用命令行参数来设定:-XX:+UseParalleGC

Parallel Compacting Collector

Yong GC和Parallel Collector一样.

old Permanent GC: stop the world,并且多线程并发GC.

每一代被划分为一些长度固定的区域.

  • (mark phase),GC根基合划分后分发给多个GC线程,每个GC线程更新可达活对象所在区域的信息(活对象的内存位置,大小)
  • (summary phase),操作在区域上,而不是对象上.由于以前GC的影响,内存的一端活对象的密度比较高,在该阶段找到一个临界点,该临界点以前额区域由于活对象内存密度高,不参与GC,不做compact.该临界点之后的区域参与GC,做compact.该阶段为单线程执行.
  • (compact phase).GC多线程使用summary info 做回收和compact工作.

可以设置GC线程数,防止GC线程长时间占有整台机器的资源.


-XX:ParallelGC Threads=n

使用命令行参数来设定.

-XX:+UseParallelOldGC

Concurrent Mark Sweep Collector(CMS)

Yong GC:和Parallel一样.

Old permanent GC:

GC和程序并发执行.

  • inital Phase:短暂停,标记GC根集合.单线程执行
  • Concurrent marking phase:GC多线程标记从根集合可达的所有活对象.程序和GC并发运行,有可能有活对象没有被标记上.
  • concurrent pre-clean:单线程,并发执行
  • Remark phase:短暂停,多线程标记在Concurrent marking phase中有变化的相关对象.
  • Concurrent sweep phase:和程序并发执行.单线程执行.不做compacting.
  • concurrent reset:单线程,并发执行

CMS不做compacting,不能使用bump-the-ointer技术,只能使用传统的内存空闲链表技术.

导致内存分配变慢,影响了Yong代的GC速度,因为Yong的GC如果有对象提升的话依赖于Old的内存分配.

CMS需要更多的内存空间,因为mark phase时程序还在运行,程序可以申请更多的old空间.在mark phase中,CMS保证标识活对象,但是该过程中,活对象可能转变为垃圾,只能等待下次GC才能回收.

和其他Collector不同,CMS不是等到old满时才GC,基于以前的统计数据(GC时间,Old空间消耗速度)来决定何时GC.CMS GC也可以基于old空间的占用率.

CMS的FGC在大部分是和应用程序一起并发的! CMS在FGC的时候,一开始需要做一个短暂的暂停,这个阶段称为最初标记:识别所有被引用的对象。在并发标记时候,会和应用程序一起运行。 因为并发标记是和程序一起运行的,所以在并发标记结束的时候,不能保证所有被引用的对象都被标记,为了解决这个问题,GC又进行了一次暂停,这个阶段称为:重标识(remark)。在这个过程中,GC会重新对在并发标阶段时候有修改的对象做标记。 因为remark的暂停要大于最初标记,所以在这时候,需要使用多线程来并行标记。 在上述动作完成之后,就可以保证所有被引用的对象都被标记了。 因此,并发清理阶段就可以并发的收集垃圾了。

image

CMS是唯一没有进行压缩的GC。如下图:

image

没有压缩,对于GC的过程,是节约了时间。但因此产生了内存碎片,所以对于新对象在年老区的分配,就产生了速度上的影响,当然,也就包括了对YGC时间的影响了。 CMS的另一个缺点,就是他需要的堆比较大,因为在并发标记的时候和并发清除的时候,应用程序很有可能在不断产生新的对象,而垃圾又还没有被删除。另外,在最初标记之后的并发标记时,原先被引用的对象,有可能变成垃圾。但在这一次的GC中,这是没有被删除的。这种垃圾叫做:漂流垃圾。最后,由于没有进行压缩,由此而带来了内存碎片。 为了解决这个问题,CMS对热点object大小进行了统计,并且估算之后的需求,然后把空闲的内存进行拆分或者合并来满足后续的需求。

CMS比并行GC花费了更少的暂停时间,但是牺牲了吞吐量,以及需要更大的堆区。

命令行参数:

-XX:CMSInitiatingOccupancyFraction=n,n为百分比,默认68。 
可以设置 
-XX:+UseCMSInitiatingOccupancyOnly 来使vm只使用old内存占用比来触发CMS GC。

Incremental Mode

CMS的Concurent phase可以是渐进式执行.以减少程序的一次暂停时间.

命令行参数:

- XX:+UseConcMarkSweepGC

- XX:+CMSIncrementalMode

四种Collector的对比和适用场景

  • Serial Collector:大多数client-style机器.对于低程度暂停时间没有需求的程序
  • Paralle Collector:多核机器,对于低程序暂停时间没有需求的程序.
  • Parallel Compacting Collector:多核机器,对于低程度暂停时间有需求的程序.
  • CMS Collector: 和Parallel Compacting Collector相比,降低了程序暂停时间,但是yong GC程序暂停时间变长,需要更大的堆空间,降低了程序的throughput.

Ergonomics

J2SE 5.0后,Collector的选择,堆大小的选择,VM(client还是server)的选择,都可以依赖平台和OS来做自动选择。

JVM会自动选择使用Server mode还是client mode.但是我们可以手工设置.

java -serer -client

GC调优

由于有了Ergonomics,第一个建议就是不要手工去配置各种参数。让系统自己去根据平台和OS来选择。

Vm mode选择

java -server server mode

java -client client mode

观测性能主要使用gc的统计信息

-XX:+PrintGC 输出GC信息

-XX:+PrintGCDetails输出GC详细信息

-XX:+PrintGCTimeStamps 输出时间戳,和-XX:+PrintGC 或 -XX:+PrintGCDetails 一起使用

-Xloggc:  gc.log 输出到指定文件

  • 决定堆内存大小

决定整个堆内存的大小.内存的大小对于Collector的性能影响是最大的.

使用以下参数来决定堆内存的大小:

可以决定堆空间的起始值和最大值,大型程序可以考虑把起始值调大,避免程序启动时频繁GC和内存扩展申请.以及堆空间中可用内存的比例范围,vm会动态管理堆内存来满足该比例范围.

-XX:MinHeapFreeRatio=n 
-XX:MaxHeapFreeRatio=n 
-Xmsn Young和Old的起始内存 
-Xmxn Young和Old的最大内存 

  • 决定代空间大小

Yong 代空间越大,则minor GC的频率越小.但是给定堆内存小,Yong代空间大,则major GC频率变大.如果没有过多的Full GC或过长的暂停时间问题,给yong代尽量大的空间.

Yong Generation Guarantee

对于serial collector ,当执行minor GC时,必须保证old代中由可用的空间来处理最坏(即eden和survivor空间中的对象都是活对象,需要提升至old空间),如果不满足,则该minor GC触发majorGC.所以对于serial collector,设置eden+survivor的内存不要大过old代内存。

其他collector不做该保证,只有old代无法存储提升对象时才触发major GC。

对于其他collector,由于多线程做minor GC时,考虑到最坏情况,每个线程要在old代内存预留一定空间做对象提升,因此可能导致内存碎片。因此old代内存应该调整的更大一些。

-XX:NewSize=n young代空间下限。 
-XX:MaxNewSize=n young代空间上限。 
-XX:NewRatio=n young和old代的比例。 
-XX:SurvivorRatio=n Eden和单个survivor的比例。


-XX:PermSize=n Permanent起始值。 
-XX:MaxPermSize=n Permanent最大值。

  • 决定使用Collector

可以考虑是否需要换一个Collector.

-XX:+UseSerialGC Serial 
-XX:+UseParallelGC Parallel 
-XX:+UseParallelOldGC Parallel compacting 
-XX:+UseConcMarkSweepGC Concurrent mark–sweep (CMS) 


Parallel和Parallel Compacting Collector. 
-XX:ParallelGCThreads=n 
-XX:MaxGCPauseMillis=n 
-XX:GCTimeRatio=n 
设定目标好于明确设定参数值。 

CMS Collector 
-XX:+CMSIncrementalMode 和CMS同时使用。 
-XX:+CMSIncrementalPacing 和CMS同时使用。 
-XX:ParallelGCThreads=n 
-XX:CMSInitiatingOccupancyFraction=n,n为百分比,默认68。 


为了增大Throughput,堆大小需要变大。可以把堆大小设为物理内存允许的最大值(同时程序不swapping)来检测该环境可以支持的最大throughput。为了减小最大暂停时间和footprint,堆大小需要变小。两个个目标有一定的矛盾,因此要视具体应用场景,做平衡.

OutOfMemoryError

可以指定:

-XX:+HeapDumpOnOutOfMemoryError 

当发生OutOfMemoryErroe时可以观测该Error的详细信息.

java heap space:

调整堆大小,程序中含有大量带有finalize方法的对象.执行finalize方法的线程顶不住了.

转载整理至http://www.iteye.com/topic/976522


评论