java内存分区

内存管理简介 
内存管理的职责为分配内存,回收内存。 
没有自动内存管理的语言/平台容易发生错误。 
典型的问题包括悬挂指针问题,一个指针引用了一个已经被回收的内存地址,导致程序的运行完全不可知。 
另一个典型问题为内存泄露,内存已经分配,但是已经没有了指向该内存的指针,导致内存泄露。 
程序员要花费大量时间在调试该类问题上。 

GC简介 
因此引入了Garbage Collector机制,由运行时环境来自动管理内存。 
Garbage Collector解决了悬挂指针和内存泄露大部分的问题(不是全部)。 

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

Collector的职责: 
分配内存。 
保证有引用的内存不被释放。 
回收没有指针引用的内存。 

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

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

好的Collector的特性 
保证有引用的对象不被GC。 
快速的回收内存垃圾。 
在程序运行期间GC要高效,尽量少的影响程序运行。和大部分的计算机问题一样,这是一个关于空间,时间,效率平衡的问题。 
避免内存碎片,内存碎片导致占用大量内存的大对象内存申请难以满足。可以采用Compaction技术避免内存碎片。Compaction技术:把活对象移向连续内存区的一端,回收其余的内存以便以后的分配。 
良好的扩展性,内存分配和GC在多核机器上不应该成为性能瓶颈。 


设计或选择Collector 
串行或并行。 
串行Collector在多核上也只有一个线程在运行,并行Collector可以同时有多个线程执行GC,但是其算法更复杂。 
并发或Stop the World。 
Stop the World Collection执行GC时,需要冻住所有内存,因此更简单一些,但是,在GC时,程序是被挂起的。并发GC时,程序和GC同时执行,当然,一般的并发GC算法还是需要一些Stop the World时间。 
Compacting or Non-compacting or Copying 
Compacting: 去除内存碎片,回收内存慢,分配内存快。 
Non-compacting: 容易产生内存碎片,回收内存快,分配内存慢,对大对象内存分配支持不好。 
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 
分代GC把内存划分为多个代(内存区域),每个代存储不同年龄的对象。 常见的分为2代,young和old。 
分配内存时,先从young代分配,如果young代已满,可以执行GC(可能导致对象提升),如果有空间,则分配,如果young代还是没有空间,可以对整个内存堆GC。 
young代GC后还存活的对象可以提升到old代。 
该机制基于以下观察事实: 
1 大部分新分配的对象很快就没有引用了,变成垃圾。 
2 很少有old代对象引用young代对象。 
基于代内存存储对象的特性,对不同代的内存可以使用不同的GC算法。 
Young代GC需要高效,快速,频繁的执行,关注点主要在速度上。 
Old代由于增长缓慢,因此GC不频繁,但是其内存空间比较大,因此,需要更长时间才能执行完GC。关注点在内存空间利用率上。 

 



Java Collector 
Jvm的内存分为3代。Young, Old, Permanent。 
大部分对象存储在Young代。 
在Young代中经历数次GC存活的对象可以提升到Old代,大对象也可以直接分配到Old代。 
Permanent代保存虚拟机自己的静态(refective)数据,例如类(class)和方法(method)对象。 
Young代由一个Eden和2个survivor组成。大部分的对象的内存分配和回收在这里完成。 

Survivor存储至少经过一次GC存活下来的对象,以增大该对象在提升至old代前被回收的机会。2个survivor中有一个为空。分别为From和to survivor。 

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

 


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

对于多线程的内存分配,每个线程使用Thread Local Allocation Buffer(TLAB)进行分配,因此还是很高效。TLAB可以看作一个线程的特殊代。只有TLAB满的时候才需要进行同步操作。 

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


Serial Collector 
单线程,Young and old GC是串行,stop the world GC的。 
Young GC。 
Eden中活对象copy到to survivor中,大对象直接进old代。 
From survivor中相对老的活对象进入old代,相对年轻的对象进入to survivor中。 
如果to survivor放不下活对象,则这些活对象直接进入old。 
经历过young 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 
利用了现代计算机大部分都是多核的事实。 
Young GC。 
和Serial Collector一样,是一个stop the world和copying Collector。只不过是多线程并行扫描和做copy,提高速度,减少了stop the world的时间,增大了throughput。 
Old permanent GC。 
和serial collector一样。Mark-sweep-compact算法。单线程。 

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

Parallel Compacting Collector 
Young GC。 
和Parallel Collector一样。 
Old Permanent GC。 
Stop the world,并且多线程并发GC。 
每一代被划分为一些长度固定的区域。 
第1步(mark phase),GC根集合划分后分发给多个GC线程,每个GC线程更新可达活对象所在区域的信息(活对象的内存位置,大小)。 
第2步(summary phase),操作在区域上,而不是对象上。由于以前GC的影响,内存的一端活对象的密度比较高,在该阶段找到一个临界点,该临界点以前的区域由于活对象内存密度高,不参与GC,不做compact。该临界点之后的区域参与GC,做compact。该阶段为单线程执行。 
第3步(compact phase)。GC多线程使用summary info做回收和compact工作。 

可以设置GC线程数,防止GC线程长时间占有整台机器的资源。 
-XX:ParallelGCThreads=n 
使用命令行参数来设定。 
-XX:+UseParallelOldGC 


Concurrent Mark Sweep Collector (CMS)
 
Young GC。 
和Parallel Collector一样。 
Old permanent GC。 
GC和程序并发执行。 
Initial 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-pointer技术,只能使用传统的内存空闲链表技术。 
导致内存分配变慢,影响了Young代的GC速度,因为Young的GC如果有对象提升的话依赖于Old的内存分配。 
CMS需要更多的内存空间,因为mark phase时程序还是在运行,程序可以申请更多的old空间。在mark phase中,CMS保证标识活对象,但是该过程中,活对象可能转变为垃圾,只能等待下一次GC才能回收。 

和其他Collector不同,CMS不是等到old满时才GC,基于以前的统计数据(GC时间,Old空间消耗速度)来决定何时GC。CMS GC也可以基于old空间的占用率。 
命令行参数: 
-XX:CMSInitiatingOccupancyFraction=n,n为百分比,默认68。 
可以设置 
-XX:+UseCMSInitiatingOccupancyOnly 来使vm只使用old内存占用比来触发CMS GC。 

Incremental Mode。 
CMS的concurrent phase可以是渐进式执行。以减少程序的一次暂停时间。 

命令行参数: 
-XX:+UseConcMarkSweepGC 
-XX:+CMSIncrementalMode 

4种Collector的对比和适用场景。 
直到jdk1.3.1,java只提供Serial Collector,Serial Collector在多核的机器上表现比较差。主要是throughput比较差。 
大型应用(大内存,多核)应该选用并行Collector。 
Serial Collector:大多数client-style机器。对于低程序暂停时间没有需求的程序。 
Parallel Collector:多核机器,对于低程序暂停时间没有需求的程序。 
Parallel Compacting Collector:多核机器,对于低程序暂停时间有需求的程序。 
CMS Collector:和Parallel Compacting Collector相比,降低了程序暂停时间,但是young GC程序暂停时间变长,需要更大的堆空间,降低了程序的throughput。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值