第一种说法:
不用在意剩余内存的大小其实很多人都是把使用其他系统的习惯带过来来了。android大多应用没有退出的设计其实是有道理的这和系统对进程的调度机制有关系。如果你知道java就能更清楚这机制了。其实和java的回收机制类似系统有一个规则来回收内存。进行内存调度有个阀值只有低于这个值系统才会按一个列表来关闭用户不需要的东西。当然这个值默认设置得很小所以你会看到内存老在很少的数值徘徊。但事实上他并不影响 速度。相反加快了下次启动使用的速度。这本来就是android标榜的优势之一如果人为关闭进程没有太大必要。特别是使用自动关进程的软件。
到这里有人会说了那为什么内存少的时候运行大型程序会慢呢其实很简单在内存剩余不多时打开大型程序会触发系统自身的调进程调度策略这是十分消耗系统资源的操作特别是在一个程序频繁向系统申请内存的时候。这种情况下系统并不会关闭所有打开的进程而是选择性关闭频繁的调度自然会拖慢系统。所以论坛上有个更改内存阀值的程序可以有一定改善。但改动也可能带来一些问题取决于值的设定。
那么进程管理软件有无必要呢有的。就是在运行大型程序之前你可以手动关闭一些进程释放内存可以显著的提高运行速度。但一些小程序完全可交由系统自己管理。 谈到这里可能有的朋友会问如果不关程序是不是会更耗电。我就说说android后台的原理你就明白了。
android的应用在被切换到后台时它其实已经被暂停了并不会消耗cpu资源只保留了运行状态。所以为什么有的程序切出去重进会到主界面。但是一个程序如果想要在后台处理些东西如音乐播放它就会开启一个服务。服务可在后台持续运行所以在后台耗电的也只有带服务的应用了。这个在进程管理软件里能看到标签 是service。至于广播什么的我就不涉及了。所以没有带服务的应用在后台是完全不耗电的没有必要关闭。这种设计本来就是一个非常好的设计下次启动程序时会更快因为不需要读取界面资源何必要关掉他们抹杀这个android的优点呢。
还有一个为什么android一个应用看起来那么耗内存。大家知道android上的应用是java当然需要虚拟机而android上的应用是带有独立虚拟机的也就是每开一个应用就会打开一个独立的虚拟机。这样设计的原因是可以避免虚拟机崩溃导致整个系统崩溃但代价就是需要更多内存。以上这些设计确保了android的稳定性正常情况下最多单个程序崩溃但整个系统不会崩溃也永远没有内存不足的提示出现。大家可能是被windows毒害得太深了总想保留更多的内存但实际上这并不一定会提升速度相反却丧失了程序启动快的这一系统特色很没必要。
有很多同学表示自己的机子开机内存怎么只有200m啦用一会怎么就变成100多m啦怎么有很多程序在后台关都关不了啦。回答了很多遍实在是麻烦在这里开个贴解释一下。
首先你要知道Android系统 是 基于Linux 2.6内核开发的开源操作系统linux是啥都不知道自己去百度吧而linux系统的内存管理有其独特的动态存储管理机制。不过Android系统对Linux的内存管理机制进行了优化Linux系统会在进程活动停止后就结束该进程
而Android把这些进程都保留在内存中直到系统需要更多内存为止。这些保留在内存中的进程通常情况下不会影响整体系统的运行速度并且当用户 再次激活这些进程时提升了进程的启动速度。 如果你懂java就会更容易理解Android系统的内存管理机制。与java的*******收机制类似系统有一个规则来回收内存。进行内存调度有个阈值只有低于这个值系统才会按一个列表来关闭用户不需要的东西。Android系统有六类进程前台进程、可见进程、次要服务、后台进程、内容供应节点、空进程。对于高手 而言可以用MinFreeManager之类的软件进行进程管理分别为六类进程设定不同的阈值来操纵系统的内存分配机制。不过对于一般用户而言Android系统默认的分配机制已经可以满足使用需要因此也不需要再去调整。
对于一些内存很低的低端Android机而言系统默认的内存分配机制无法实现很完善的内存调配。所以在运行大型游戏时需要先清理一下内存。512m的总内存和几十m的空余内存已经可以充分的满足系统自动调配的需要因此完全没有必要老去杀进程、清内存。 有的兄弟说后台挂着程序很费电事实上Android的应用在被切换到后台时它其实已经被暂停了并不会消耗cpu资源只保留了运行状态。至于QQ、音乐播放之类的程序可以在后台运行是因为这些程序在后台开启了服务而服务可以后台运行所以没有带服务的应用在后台是完全不耗电的没有必要关闭。这种设计本来就是一个非常好的设计下次启动程序时会更快因为不需要读取界面资源。
Android系统这样的设计不仅非常适合移动终端的需要而且减少了系统崩溃的可能确保了系统的稳定性。老想着清理内存的同学完全是因为被塞班或者 Windows毒害太深事实上经常用Taskiller之类的软件关闭后台所有进程很容易造成系统的不稳定。很多时候出现问题只要重启就能解决其原因也在于此。 说了这么多总结起来很简单牛B的人自己去操纵系统内存分配的阈值而普通用户则是想怎么用就怎么用完全不用去鸟剩余内存的问题那些内存清理的程序完全可以扔到一边了。 PS 1.按home退出程序保留状态为后台进程按返回键退出程序保留状态为空进程。空进程的oom_adj评值高于后台进程更容易被系统清理。所以推荐用返回键退出。 2.UC、愤怒小鸟、都市赛车之类程序本身提供关闭功能 的还是尽量主动关闭。浏览器 、电子市场、opera mini等不提供关闭功能的直接返回键退出就行。
我想每个人第一次用Android的时候,不可避免的会去装个任务管理器,然后对里面时刻都停留着一大堆的程序表示触目惊心,然后会在桌面 上建立一个快捷清空内存的按钮,时不时啪的按一下,看着内存剩余数量从30多变成100多然后很有快感...其实吧,Android是Linux的内核,每一个程序都是一个独立的JAVA虚拟机,就和油汤里的油花一样互不干扰,这样充分保证了万一某个程序的JAVA虚拟机崩溃,系统依旧稳定正常运行.而 Android和传统Linux不一样的地方又在于,传统Linux在进程活动停止后就结束了,这就类似于我们用S60和WM一样,关闭程序,内存释放. 而Android会把这些进程保留在内存里,干嘛呢?为了保证你再次激活这些进程时候启动的更快,比如说我们挂在桌面的Widgets,具体一点我们拿新浪微博举例吧.我刚看完,退出,突然我想我发一条微博吧,那么这个时候我可以直接在桌面Widgets上操作----设想一下如果我退出的时候这个进程就终止了,那么我在桌面上点击Widgets的时候会不会卡顿一下甚至没有响应?----这就跟我们把Widgets挂在桌面的行为完全背离了,放在桌面上就是为了能随时观察到程序运行的情况,以及随时可以快速调用程序.所以Android并没有在进程活动停止就释放对应的内存.那么也许你还是会有疑问,那么内存够不够用呢?
512的内存被我用的只剩56M是不是很**?其实系统一点也不卡的,蛋定蛋定 是的,我理解,因为大家这么多年Windows都用习惯了,Windows内存不足的时候机器卡的会让你想砸掉机箱,而且调用虚拟内存的时候硬盘喀喀喀想的让你肉疼.你肯定也会怕你的手机明明512M内存结果就剩下30来M把你卡到崩溃.事实上呢,Android会在系统需要更多内存的时候,去释放掉那些占用内存的进程----这个活动是智能的.最早大家认为是有个排序,比如最近使用过哪些程序(LRU机制,Last Recently Used),然后结束最早的进程.不过并非如此,否则就变成我们上小学时候那样,个子高的块头大的男生跟班长下去拔草扛新书,女生们留在班里绣花吧... 这样很明显不公平而且没准会结束掉那些我们并不想结束掉的进程----譬如说这会儿我想切回到刚才后台的网页继续浏览结果悲怆的发现它被系统给我强制关闭了...
Android把进程分成了一些优先级,比如
前台进程(Foreground),比如我们正在看书,那么看书的程序就是前台进程,这些进程是不会被系统优先结束的.当我把它切到后台的时候,它就变成后台进程了.
还有可见进程(Visible),这个怎么说呢,譬如输入法 程序,你平时是看不见它的,但是在你打开输入界面的时候,它会很快的弹出来,而不是让你等啊等啊等,看不到的原因是透明度的机制,咱就不要钻牛角尖讨论为啥我看不见了...还有桌面的Widgets,比如我们的桌面时钟,这个东西就是可见的,如果它被系统终止了会有什么样的结果?这个Widgets依然会显示在桌面上,但是时针不走了...
主要服务,比如说,电话 的拨号功能,你也不想正急着打电话呢结果人家给你卡半天吧,尤其像我这样联系人上2000的,载入一遍真的很慢啊...所以这些主要服务平时也不会被系统自动结束,除非你非要关它,关了也会自己重新加载的.这也是你完全释放内存以后过一会就看着内存可用值又慢慢降低的原因.
次要服务(secondary server),诸如谷歌企业套件,Gmail,联系人,看着这些程序出现在任务管理器里可能你会非常的莫名其妙,丫的这都哪跟哪啊我没开啊...其实它们和一些系统功能也是息息相关的,比如Gmail的邮件推送,我们时常需要用到它们,所以系统也太会去终止它们.甚至于台电Teclast机器上著名的Status BAR,这个也是次要服务,但是其实它承接着整个系统界面的运行,所以,如果你强行关闭所有进程的时候,你的屏幕会变成一片白...然后慢慢等Status BAR加载.
后台进程(hidden),就是我们通常意义上理解的启动后被切换到后台的进程,比如如浏览器和阅读器.后台进程的管理策略有多种,但是一般来讲,系统都会视内存情况,尽可能多的保留后台程序,这样会影响到你启动别的程序的运行速度----我想这个很好理解,因为内存确实不够了,而且你还没让系统自动释放内存.但好处是,你再次切换到这些已启动的程序时几乎是无缝的,速度绝对比你从0开始启动它要快得多.所以,这种后台进程在内存极度不够的时候,肯定会被系统选择性的干掉的.
内容供应节点(content provider),没有程序实体,仅提供内容供别的程序去用的,比如日历供应节点,邮件供应节点等.在系统自动终止进程时,这类程序享有优先的被干掉权...
空进程(empty),没有任何东西在内运行的进程,有些程序在退出后,依然会在进程中驻留一个空进程,这个进程里没有任何数据在运行,作用往往是提高该程序下次的启动速度或者记录程序的一些历史信息.这部分进程无疑是系统最先终止的. 说了这么多,其实还是要结合实际的程序来看一下的,比如Android这个很有名的自动内存调配的软件,Auto Memory Manager,它的设置 和帮助界面就如上面所说的,它自动提供了多种默认配置,例如极速模式,这个模式下,会帮助你在设定好的临界值区间上,结束空进程以及内容供应节点等等低优先级保留权的进程,来给你腾出更多的内存,加速新运行程序打开的速度,但是它也说明了这种模式的弊端,就是一些可能你不想被关闭的进程会被过早的关闭,比如说,闹钟----在G2 G3还很火爆的2009年,很多用户在买完手机后给我抱怨,哎呀这个机器闹钟怎么老不响啊...上班老迟到...其实这就是因为手动结束进程的时候结果把闹钟也给干掉了.系统的时间是会一直走的,这属于主要服务,而闹钟呢,只是主要服务的一个附属品,所以被结束后,是不会自动被启动的,既然没有启动自然就不会响了.与此类似的例子就是里程碑不充电的BUG,这是因为Moto的机器里有个USB的进程,如果你把它结束后,理论上会重新启动的但是也会不启动,后面这种情况出现的结果就是你插充电器没反应,插数据线 连电脑 没反应...重启手机就好了.
当然我知道大家的洁癖很多,有的人就是见不得内存值太小...好吧如果你不想一些被系统认为不太重要而你又很需要的进程被你自己亲手扼杀的话,那么我推荐你使用高级任务管理器这个程序,你可以把一些进程自动隐藏起来,也就是说当你挥起狼牙棒横扫一堆进程的时候,你设置好的几个进程是不会受任何影响的,比如桌面Launcher,比如闹钟,比如USB,等等等等.但话说回来,我是不建议大家去手动管理Android的内存,也许你会不习惯----我也没啥好劝告的,总之,不要把你的智能机想的那么笨就行了. 刚才全杀掉进程后,过了一会,又变成剩余60M内存,还是没啥鸭梨啊...如果你感兴趣可以做个试验,内存很少的时候,你打开一个大游戏,然后退出,你会发现...
第二中说话:Android内存(内存溢出 内存不足 内存低 .)优化详解
一、 Android的内存机制
Android的程序由Java语言编写,所以Android的内存管理与Java的内存管理相似。程序员通过new为对象分配内存,所有对象在java堆内分配空间;然而对象的释放是由垃圾回收器来完成的。C/C++中的内存机制是“谁污染,谁治理”,java的就比较人性化了,给我们请了一个专门的清洁工(GC)。
那么GC怎么能够确认某一个对象是不是已经被废弃了呢?Java采用了有向图的原理。Java将引用关系考虑为图的有向边,有向边从引用者指向引用对象。线程对象可以作为有向图的起始顶点,该图就是从起始顶点开始的一棵树,根顶点可以到达的对象都是有效对象,GC不会回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。
二、Android的内存溢出
Android的内存溢出是如何发生的?
Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M。因此我们所能利用的内存空间是有限的。如果我们的内存占用超过了一定的水平就会出现OutOfMemory的错误。
为什么会出现内存不够用的情况呢?我想原因主要有两个:
三、万恶的static
static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context的情况最多),这时就要谨慎对待了。
以上的代码是很危险的,如果将Activity赋值到么mContext的话。那么即使该Activity已经onDestroy,但是由于仍有对象保存它的引用,因此该Activity依然不会被释放。
我们举Android文档中的一个例子。
sBackground, 是一个静态的变量,但是我们发现,我们并没有显式的保存Contex的引用,但是,当Drawable与View连接之后,Drawable就将View设置为一个回调,由于View中是包含Context的引用的,所以,实际上我们依然保存了Context的引用。这个引用链如下:
Drawable->TextView->Context
所以,最终该Context也没有得到释放,发生了内存泄露。
如何才能有效的避免这种引用的发生呢?
第一,应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。
第二、Context尽量使用Application Context,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。
第三、使用WeakReference代替强引用。比如可以使用WeakReference
该部分的详细内容也可以参考Android文档中Article部分。
四、都是线程惹的祸
线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。
这段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:假设MyThread的run函数是一个很费时的操作,当我们开启该线程后,将设备的横屏变为了竖屏,一般情况下当屏幕转换时会重新创建Activity,按照我们的想法,老的Activity应该会被销毁才对,然而事实上并非如此。
由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。
有些人喜欢用Android提供的AsyncTask,但事实上AsyncTask的问题更加严重,Thread只有在run函数不结束时才出现这种内存泄露问题,然而AsyncTask内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露的问题。
这种线程导致的内存泄露问题应该如何解决呢?
第一、将线程的内部类,改为静态内部类。
第二、在线程内部采用弱引用保存Context引用。
解决的模型如下:
事实上,线程的问题并不仅仅在于内存泄露,还会带来一些灾难性的问题。由于本文讨论的是内存问题,所以在此不做讨论。
五、超级大胖子Bitmap
可以说出现OutOfMemory问题的绝大多数人,都是因为Bitmap的问题。因为Bitmap占用的内存实在是太多了,它是一个“超级大胖子”,特别是分辨率大的图片,如果要显示多张那问题就更显著了。
如何解决Bitmap带给我们的内存问题?
第一、及时的销毁。
虽然,系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过java堆的限制。因此,在用完Bitmap时,要及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:“该图片可以释放了”。
第二、设置一定的采样率。
有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存。如下面的代码:
第三、巧妙的运用软引用(SoftRefrence)
有些时候,我们使用Bitmap后没有保留对它的引用,因此就无法调用Recycle函数。这时候巧妙的运用软引用,可以使Bitmap在内存快不足时得到有效的释放。如下例:
六、行踪诡异的Cursor
Cursor是Android查询数据后得到的一个管理数据集合的类,正常情况下,如果查询得到的数据量较小时不会有内存问题,而且虚拟机能够保证Cusor最终会被释放掉。
然而如果Cursor的数据量特表大,特别是如果里面有Blob信息时,应该保证Cursor占用的内存被及时的释放掉,而不是等待GC来处理。并且Android明显是倾向于编程者手动的将Cursor close掉,因为在源代码中我们发现,如果等到垃圾回收器来回收时,会给用户以错误提示。
所以我们使用Cursor的方式一般如下:
有一种情况下,我们不能直接将Cursor关闭掉,这就是在CursorAdapter中应用的情况,但是注意,CursorAdapter在Acivity结束时并没有自动的将Cursor关闭掉,因此,你需要在onDestroy函数中,手动关闭。
CursorAdapter中的changeCursor函数,会将原来的Cursor释放掉,并替换为新的Cursor,所以你不用担心原来的Cursor没有被关闭。
你可能会想到使用Activity的managedQuery来生成Cursor,这样Cursor就会与Acitivity的生命周期一致了,多么完美的解决方法!然而事实上managedQuery也有很大的局限性。
managedQuery生成的Cursor必须确保不会被替换,因为可能很多程序事实上查询条件都是不确定的,因此我们经常会用新查询的Cursor来替换掉原先的Cursor。因此这种方法适用范围也是很小。
七、其它要说的。
其实,要减小内存的使用,其实还有很多方法和要求。比如不要使用整张整张的图,尽量使用9path图片。Adapter要使用convertView等等,好多细节都可以节省内存。这些都需要我们去挖掘,谁叫Android的内存不给力来着。
from:http://blog.sina.com.cn/s/blog_9564cb6e0101i7mi.html
4.关于Android的内存泄漏:如何检测
Android下的内存泄漏很隐晦呀。
(1) 不同的引用类型:
Java2平台里面引入了java.lang.ref包,这个包中的类可以让我们引用对象,但这些对象可以不用停留在内存中。这些引用类和Java本身的垃圾回收器还存在一定的交互(在垃圾回收的不同阶段)。
Java对引用的分类(Strong reference, SoftReference, WeakReference, PhatomReference):
级别 |
什么时候被垃圾回收 |
用途 |
生存时间 |
强 |
从来不会 |
对象的一般状态 |
JVM停止运行时终止 |
软 |
在内存不足时 |
对象简单?缓存 |
内存不足时终止 |
弱 |
在垃圾回收时 |
对象缓存 |
gc运行后终止 |
假象 |
Unknown |
Unknown |
Unknown |
如何用Memory Analyzer Tool(MAT)来分析,前提是Android开发和测试的工具安装完整,SDK,Eclipse.
更多关于MAT的内容,refer:
http://blog.csdn.net/studyvcmfc/archive/2010/06/05/5649431.aspx
http://itnewsvendor.appspot.com/1780002-使用_memory_analyzer.html
1).打开Eclipse
2).选择 Help->Install New Software;
3).在Work with中添加站点:http://download.eclipse.org/mat/1.0/update-site/(这个地址可能会变化,但是新的地址可以在官方网站上找到:http://www.eclipse.org/mat/downloads.php )
4).生成.hprof文件:插入SD卡(Android机器很多程序都需要插入SD卡),并将设备连接到PC,在Eclipse中的DDMS中选择要测试的进程,然后点击Update Heap 和Dump HPROF file两个Button。
.hprof 文件会自动保存在SD卡上,把 .hprof 文件拷贝到PC上的\ android-sdk-windows\tools目录下。这个由DDMS生成的文件不能直接在MAT打开,需要转换。
运行cmd打开命令行,cd到\ android-sdk-windows \tools所在目录,并输入命令hprof-conv xxxxx.hprof yyyyy.hprof,其中xxxxx.hprof为原始文 件,yyyyy.hprof为转换过后的文件。转换过后的文件自动放在android-sdk-windows\tools 目录下。
OK,到此为止,.hprof文件处理完毕,可以用来分析内存泄露情况了。
5).打开MAT:
在Eclipse中点击Windows->Open Perspective->Other->Memory Analysis
6).导入.hprof文件
在MAT中点击 File->Open File,浏览到刚刚转换而得到的.hprof文件,并Cancel掉自动生成报告,点击Dominator Tree,并按Package分组,选择自己所定义的Package 类点右键,在弹出菜单中选择List objects->With incoming references。
这时会列出所有可疑类,右键点击某一项,并选择Path to GC Roots->exclude weak/soft references,会进一步筛选出跟程序相关的所有有内存泄露的类。据此,可以追踪到代码中的某一个产生泄露的类。
. 在当前程序中,例如framework中某些代码中,可以使用android.os.Debug中的:
public static void dumpHprofData(String fileName) throws IOException
方法,手动的指定.hprof文件的生成位置。例如:
xxxButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
android.os.Debug.dumpHprofData("/data/temp/myapp.hprof");
... ...
}
}
上述代码意图是希望在xxxButton被点击的时候开始抓取内存使用信息,并保存在我们指定的位置:/data/temp/myapp.hprof,这样就没有权限的限制了,而且也无须用sdcard。但要保证/data/temp目录是存在的。这个路径可以自己定义,当然也可以写成sdcard当中的某个路径。
使用Memory Analyzer tool(MAT)分析内存泄漏(一)
前言的前言:本文是自2005年8月以来,首次在一个月之内发布三篇文章。谨以此文献给这么多年始终不济的我。所谓少不入川,而今已非年少。北漂快两年了,何时能回到故乡,回去后又会怎样,也许永远是个未知……
前言
在平时工作过程中,有时会遇到OutOfMemoryError,我们知道遇到Error一般表明程序存在着严重问题,可能是灾难性的。所以找出是什么原因造成OutOfMemoryError非常重要。现在向大家引荐Eclipse Memory Analyzer tool(MAT),来化解我们遇到的难题。如未说明,本文均使用Java 5.0 on Windows XP SP3环境。
为什么用MAT
之前的观点,我认为使用实时profiling/monitoring之类的工具,用一种非常实时的方式来分析哪里存在内存泄漏是很正确的。年初使用了某profiler工具测试消息中间件中存在的内存泄漏,发现在吞吐量很高的时候profiler工具自己也无法响应,这让人很头痛。后来了解到这样的工具本身就要消耗性能,且在某些条件下还发现不了泄漏。所以,分析离线数据就非常重要了,MAT正是这样一款工具。
为何会内存溢出
我们知道JVM根据generation(代)来进行GC,根据下图所示,一共被分为young generation(年轻代)、tenured generation(老年代)、permanent generation(永久代, perm gen),perm gen(或称Non-Heap非堆)是个异类,稍后会讲到。注意,heap空间不包括perm gen。
绝大多数的对象都在young generation被分配,也在young generation被收回,当young generation的空间被填满,GC会进行minor collection(次回收),这次回收不涉及到heap中的其他generation,minor collection根据weak generational hypothesis(弱年代假设)来假设young generation中大量的对象都是垃圾需要回收,minor collection的过程会非常快。young generation中未被回收的对象被转移到tenured generation,然而tenured generation也会被填满,最终触发major collection(主回收),这次回收针对整个heap,由于涉及到大量对象,所以比minor collection慢得多。
JVM有三种垃圾回收器,分别是throughput collector,用来做并行young generation回收,由参数-XX:+UseParallelGC启动;concurrent low pause collector,用来做tenured generation并发回收,由参数-XX:+UseConcMarkSweepGC启动;incremental low pause collector,可以认为是默认的垃圾回收器。不建议直接使用某种垃圾回收器,最好让JVM自己决断,除非自己有足够的把握。
Heap中各generation空间是如何划分的?通过JVM的-Xmx=n参数可指定最大heap空间,而-Xms=n
则是指定
最小heap空间。在JVM初始化的时候,如果最小heap空间小于最大heap空间的话,如上图所示JVM会把未用到的空间标注为Virtual。除了这两个参数还有-XX:MinHeapFreeRatio=n和 -XX:MaxHeapFreeRatio=n来分别控制最大、最小的剩余空间与活动对象之比例。在32位Solaris SPARC操作系统下,默认值如下,在32位windows xp下,默认值也差不多。
参数 |
默认值 |
MinHeapFreeRatio |
40 |
MaxHeapFreeRatio |
70 |
-Xms |
3670k |
-Xmx |
64m |
由于tenured generation的major collection较慢,所以tenured generation空间小于young generation的话,会造成频繁的major collection,影响效率。Server JVM默认的young generation和tenured generation空间比例为1:2,也就是说young generation的eden和survivor空间之和是整个heap(当然不包括perm gen)的三分之一,该比例可以通过-XX:NewRatio=n参数来控制,而Client JVM默认的-XX:NewRatio是8。至于调整young generation空间大小的NewSize=n和MaxNewSize=n参数就不讲了,请参考后面的资料。
young generation中幸存的对象被转移到tenured generation,但不幸的是concurrent collector线程在这里进行major collection,而在回收任务结束前空间被耗尽了,这时将会发生Full Collections(Full GC),整个应用程序都会停止下来直到回收完成。Full GC是高负载生产环境的噩梦……
现在来说说异类perm gen,它是JVM用来存储无法在Java语言级描述的对象,这些对象分别是类和方法数据(与class loader有关)以及interned strings(字符串驻留)。一般32位OS下perm gen默认64m,可通过参数-XX:MaxPermSize=n指定,JVM Memory Structure一文说,对于这块区域,没有更详细的文献了,神秘。
回到问题“为何会内存溢出?”。
要回答这个问题又要引出另外一个话题,既什么样的对象GC才会回收?当然是GC发现通过任何reference chain(引用链)无法访问某个对象的时候,该对象即被回收。名词GC Roots正是分析这一过程的起点,例如JVM自己确保了对象的可到达性(那么JVM就是GC Roots),所以GC Roots就是这样在内存中保持对象可到达性的,一旦不可到达,即被回收。通常GC Roots是一个在current thread(当前线程)的call stack(调用栈)上的对象(例如方法参数和局部变量),或者是线程自身或者是system class loader(系统类加载器)加载的类以及native code(本地代码)保留的活动对象。所以GC Roots是分析对象为何还存活于内存中的利器。知道了什么样的对象GC才会回收后,再来学习下对象引用都包含哪些吧。
从最强到最弱,不同的引用(可到达性)级别反映了对象的生命周期。
l Strong Ref(强引用):通常我们编写的代码都是Strong Ref,于此对应的是强可达性,只有去掉强可达,对象才被回收。
l Soft Ref(软引用):对应软可达性,只要有足够的内存,就一直保持对象,直到发现内存吃紧且没有Strong Ref时才回收对象。一般可用来实现缓存,通过java.lang.ref.SoftReference类实现。
l Weak Ref(弱引用):比Soft Ref更弱,当发现不存在Strong Ref时,立刻回收对象而不必等到内存吃紧的时候。通过java.lang.ref.WeakReference和java.util.WeakHashMap类实现。
l Phantom Ref(虚引用):根本不会在内存中保持任何对象,你只能使用Phantom Ref本身。一般用于在进入finalize()方法后进行特殊的清理过程,通过 java.lang.ref.PhantomReference实现。
有了上面的种种我相信很容易就能把heap和perm gen撑破了吧,是的利用Strong Ref,存储大量数据,直到heap撑破;利用interned strings(或者class loader加载大量的类)把perm gen撑破。
关于shallow size、retained size
Shallow size就是对象本身占用内存的大小,不包含对其他对象的引用,也就是对象头加成员变量(不是成员变量的值)的总和。在32位系统上,对象头占用8字节,int占用4字节,不管成员变量(对象或数组)是否引用了其他对象(实例)或者赋值为null它始终占用4字节。故此,对于String对象实例来说,它有三个int成员(3*4=12字节)、一个char[]成员(1*4=4字节)以及一个对象头(8字节),总共3*4 +1*4+8=24字节。根据这一原则,对String a=”rosen jiang”来说,实例a的shallow size也是24字节(很多人对此有争议,请看官甄别并留言给我)。
Retained size是该对象自己的shallow size,加上从该对象能直接或间接访问到对象的shallow size之和。换句话说,retained size是该对象被GC之后所能回收到内存的总和。为了更好的理解retained size,不妨看个例子。
把内存中的对象看成下图中的节点,并且对象和对象之间互相引用。这里有一个特殊的节点GC Roots,正解!这就是reference chain的起点。
从obj1入手,上图中蓝色节点代表仅仅只有通过obj1才能直接或间接访问的对象。因为可以通过GC Roots访问,所以左图的obj3不是蓝色节点;而在右图却是蓝色,因为它已经被包含在retained集合内。
所以对于左图,obj1的retained size是obj1、obj2、obj4的shallow size总和;右图的retained size是obj1、obj2、obj3、obj4的shallow size总和。obj2的retained size可以通过相同的方式计算。
Heap Dump
heap dump是特定时间点,java进程的内存快照。有不同的格式来存储这些数据,总的来说包含了快照被触发时java对象和类在heap中的情况。由于快照只是一瞬间的事情,所以heap dump中无法包含一个对象在何时、何地(哪个方法中)被分配这样的信息。
在不同平台和不同java版本有不同的方式获取heap dump,而MAT需要的是HPROF格式的heap dump二进制文件。想无需人工干预的话,要这样配置JVM参数:-XX:-HeapDumpOnOutOfMemoryError,当错误发生时,会自动生成heap dump,在生产环境中,只有用这种方式。如果你想自己控制什么时候生成heap dump,在Windows+JDK6环境中可利用JConsole工具,而在Linux或者Mac OS X环境下均可使用JDK5、6自带的jmap工具。当然,还可以配置JVM参数:-XX:+HeapDumpOnCtrlBreak,也就是在控制台使用Ctrl+Break键来生成heap dump。由于我是windows+JDK5,所以选择了-XX:-HeapDumpOnOutOfMemoryError这种方式,更多配置请参考MAT Wiki。
使用Memory Analyzer tool(MAT)分析内存泄漏(二)
http://www.blogjava.net/rosen/archive/2010/06/13/323522.html
写blog就是好,在大前提下可以想说什么写什么,不像投稿那么字字斟酌。上周末回了趟成都办事,所以本文来迟了。K117从达州经由达成线往成都方向走的时候,发现铁路边有条河,尽管我现在也不知道其名字,但已被其深深的陶醉。河很宽且水流平缓,河边山丘森林密布,民房星星点点的分布在河边,河里偶尔些小船。当时我就在想,在这里生活是多么的惬意,夏天还可以下去畅游一番,闲来无事也可垂钓。唉,越来越讨厌北漂了。
参考资料
MAT Wiki
Interned Strings
Strong,Soft,Weak,Phantom Reference
Tuning Garbage Collection with the 5.0 Java[tm] Virtual Machine
Permanent Generation
Understanding Weak References译文
Java HotSpot VM Options
Shallow and retained sizes
JVM Memory Structure
GC roots
请注意!引用、转贴本文应注明原作者:Rosen Jiang 以及出处: http://www.blogjava.net/rosen