java内存管理以及GC

来源:互联网 发布:vmware12 mac显卡驱动 编辑:程序博客网 时间:2024/05/24 07:08

http://www.iteye.com/topic/976522

目录

内存管理简介

gc简介

好的collector的特性

设计或选择collector

gc性能指标

分代gc

java collector

快速内存分配

gc根集合

serial collector

parallel collector/throughput collector

parallel compacting collector

concurrent mark sweep collector (cms)

4种collector的对比和适用场景。

ergonomics

gc调优

outofmemoryerror

freememory(),totalmemory(),maxmemory()

jmap工具的使用

内存管理简介

内存管理的职责为分配内存,回收内存。

没有自动内存管理的语言/平台容易发生错误。

典型的问题包括悬挂指针问题,一个指针引用了一个已经被回收的内存地址,导致程序的运行完全不可知。

另一个典型问题为内存泄露,内存已经分配,但是已经没有了指向该内存的指针,导致内存泄露。

程序员要花费大量时间在调试该类问题上。

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。

ergonomics

j2se 5.0后,collector的选择,堆大小的选择,vm(client还是server)的选择,都可以依赖平台和os来做自动选择。

jvm会自动选择使用server mode还是client mode。但是我们一样可以手工设置。

java -server -client

server-class machine的选择:

2个或更多的处理器

and

2g或更多的物理内存

and

不是32bits,windows os。

client-class

the client jvm

the serial collector

initial heap size = 4m

max heap size=64m

server-class

the server jvm

the parallel collector

initial heap size= 1/64物理内存(>=32m),最大1g。

max heap size=1/4物理内存,最大1g。

基于行为的调优。

可以基于最大暂停时间或throughput。

-xx:maxgcpausemillis=n

指示vm调整堆大小和其他参数来满足这个时间需求。如果vm不满足该目标,则减小堆大小来满足该目标。该目标没有默认值。

-xx:gctimeratio=n

gc time/app time=1/(1+n)

如n=99表示gc时间占整个运行时间的1%。

如果该目标不能满足,则增大堆大小来满足该目标。默认值n=99。

footprint goal

如果最大暂停时间和throughput目标都满足了,则减少堆大小直到有一个目标不满足,然后又回调。

目标优先级:

最大暂停时间>throughput>footprint。

gc调优

由于有了ergonomics,第一个建议就是不要手工去配置各种参数。让系统自己去根据平台和os来选择。然后观测性能,如果ok的话,呵呵,不用搞了。

但是ergonomics也不是万能的。因此还是需要程序员来手工搞。

注意性能问题一定要测量/调优/测量/调优不停的循环下去。

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 输出到指定文件。

1 决定堆内存大小。

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

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

可以决定堆空间的起始值和最大值,大型程序可以考虑把起始值调大,避免程序启动时频繁gc和内存扩展申请。

以及堆空间中可用内存的比例范围,vm会动态管理堆内存来满足该比例范围。

-xx:minheapfreeratio=n

-xx:maxheapfreeratio=n

-xmsn young和old的起始内存

-xmxn young和old的最大内存

2 决定代空间大小。

young代空间越大,则minor gc的频率越小。但是,给定堆内存大小,young代空间大,则major gc频率变大。

如果没有过多的full gc或者过长的暂停时间问题,给young代尽量大的空间。

young generation guarantee。

对于serial collector,当执行minor gc时,必须保证old代中有可用的空间来处理最坏情况(即eden和survivor空间中的对象都是活对象,需要提升至old空间),如果不满足,则该minor gc触发major gc。所以对于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最大值。

3 决定使用collector

可以考虑是否需要换一个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

设定目标好于明确设定参数值。

为了增大throughput,堆大小需要变大。可以把堆大小设为物理内存允许的最大值(同时程序不swapping)来检测该环境可以支持的最大throughput。

为了减小最大暂停时间和footprint,堆大小需要变小。

2个目标有一定的矛盾,因此要视具体应用场景,做平衡。

cms collector

-xx:+cmsincrementalmode 和cms同时使用。

-xx:+cmsincrementalpacing 和cms同时使用。

-xx:parallelgcthreads=n

-xx:cmsinitiatingoccupancyfraction=n,n为百分比,默认68。

outofmemoryerror

可以指定

-xx:+heapdumponoutofmemoryerror

当发生outofmemoryerror时dump出堆内存。

发生outofmemoryerror时可以观测该error的详细信息。

java heap space:

调整堆大小。

程序中含有大量带有finalize方法的对象。执行finalize方法的线程顶不住了。

permgen space:

permanent代内存不够用了。

requested array size exceeds vm limit。

堆内存不够用。

程序bug,一次分配太多内存。

freememory(),totalmemory(),maxmemory()

java.lang.runtime类中的 freememory(), totalmemory(), maxmemory()这几个方法的反映的都是 java这个进程的内存情况,跟操作系统的内存根本没有关系。

maxmemory()这个方法返回的是java虚拟机(这个进程)能构从操作系统那里挖到的最大的内存,以字节为单位,如果在运行java程序的时 候,没有添加-xmx参数,那么就是jvm默认的可以使用内存大小,client为64m,server为1g。如果添加了-xmx参数,将以这个参数后面的值为准。

totalmemory()这个方法返回的是java虚拟机现在已经从操作系统那里挖过来的内存大小,也就是java虚拟机这个进程当时所占用的所有内存。如果在运行java的时候没有添加-xms参数,那么,在java程序运行的过程的,内存总是慢慢的从操作系统那里挖的,基本上是用多少挖多少,直 到挖到maxmemory()为止,所以totalmemory()是慢慢增大的。如果用了-xms参数,程序在启动的时候就会无条件的从操作系统中挖 -xms后面定义的内存数,然后在这些内存用的差不多的时候,再去挖。

freememory()是什么呢,刚才讲到如果在运行java的时候没有添加-xms参数,那么,在java程序运行的过程的,内存总是慢慢的从操 作系统那里挖的,基本上是用多少挖多少,但是java虚拟机100%的情况下是会稍微多挖一点的,这些挖过来而又没有用上的内存,实际上就是 freememory(),所以freememory()的值一般情况下都是很小的,但是如果你在运行java程序的时候使用了-xms,这个时候因为程 序在启动的时候就会无条件的从操作系统中挖-xms后面定义的内存数,这个时候,挖过来的内存可能大部分没用上,所以这个时候freememory()可能会有些大。

jmap工具的使用

jmap pid 查看共享对象。

jmap -heap pid 查看java进程堆的相关信息。

java代码

$ jmap -heap 5695

attaching to process id 5695, please wait...

debugger attached successfully.

server compiler detected.

jvm version is 17.0-b16

using parallel threads in the new generation.

using thread-local object allocation.

concurrent mark-sweep gc

heap configuration:

minheapfreeratio = 40

maxheapfreeratio = 70

maxheapsize= 1342177280 (1280.0mb)

newsize= 134217728 (128.0mb)

maxnewsize= 134217728 (128.0mb)

oldsize= 4194304 (4.0mb)

newratio= 2

survivorratio= 20000

permsize= 100663296 (96.0mb)

maxpermsize= 134217728 (128.0mb)

heap usage:

new generation (eden + 1 survivor space):

capacity = 134152192 (127.9375mb)

used= 34518744 (32.919639587402344mb)

free= 99633448 (95.01786041259766mb)

25.731032408326207% used

eden space:

capacity = 134086656 (127.875mb)

used= 34518744 (32.919639587402344mb)

free= 99567912 (94.95536041259766mb)

25.743608670500368% used

from space:

capacity = 65536 (0.0625mb)

used= 0 (0.0mb)

free= 65536 (0.0625mb)

0.0% used

to space:

capacity = 65536 (0.0625mb)

used= 0 (0.0mb)

free= 65536 (0.0625mb)

0.0% used

concurrent mark-sweep generation:

capacity = 671088640 (640.0mb)

used= 287118912 (273.81793212890625mb)

free= 383969728 (366.18206787109375mb)

42.7840518951416% used

perm generation:

capacity = 100663296 (96.0mb)

used= 41864504 (39.92510223388672mb)

free= 58798792 (56.07489776611328mb)

41.58864816029867% used

jmap –histo pid 查询各种对象占用的内存大小。

java代码

$ jmap -histo 5695 | less

num#instances#bytesclass name

----------------------------------------------

1:32029063305456[c

2:145701046624320java.util.concurrent.concurrenthashmap$segment

3:150250036060000java.util.concurrent.locks.reentrantlock$nonfairsync

4:8778529987632[i

5:145701023638928[ljava.util.concurrent.concurrenthashmap$hashentry;

6:28566815240784[ljava.lang.object;

7:8723910680160

8:3994829587568java.lang.string

9:165337466624[b

10:910657285072[ljava.util.concurrent.concurrenthashmap$segment;

11:872396983288

12:1257505868720

13:454095449080java.net.sockssocketimpl

14:635744936176[s

15:452944710576sun.nio.ch.socketchannelimpl

jmap –permstat pid 查看class loader。

jmap –dump:file=filename,format=b pid dump内存到文件。

可以使用mat工具分析java dump文件。


======================================================
在最后,我邀请大家参加新浪APP,就是新浪免费送大家的一个空间,支持PHP+MySql,免费二级域名,免费域名绑定 这个是我邀请的地址,您通过这个链接注册即为我的好友,并获赠云豆500个,价值5元哦!短网址是http://t.cn/SXOiLh我创建的小站每天访客已经达到2000+了,每天挂广告赚50+元哦,呵呵,饭钱不愁了,\(^o^)/