本人从事手机编程多年,但一直对手机存储这一方面,不是全面理解,总是一知半解的。这次工作闲瑕之余,总于下了决心,把它整明白。于是搜集各方面资料,总于理清了一个头绪,有了个全面的了解,现列出来和大家分享,其中大部内容是从网上摘抄,再溶入自己的理解。如有不对的地方,欢迎指正。
先从最基本的概念开始
存储器分为RAM和ROM。
(1)RAM是随机访问存储器,这个随机访问的含义是指可以像翻书一样哪一页都可以直接“翻开”地址读取数据,而不用从头开始找,顺序访问的一个极佳的例子是磁带,记得小时候听磁带里的一首喜欢的歌,每次都得倒回去,而不能直接指定从哪里开始听。
(2)ROM是只读存储器,只读的含义大家都知道,就是只能读不能写。大家可能会发问,既然不能写,那么数据从哪里来?其实不是真不能写,而是有一类ROM中的数据一般不希望使用ROM的人修改,比如手机里的ROM装的就是操作系统。而其实大多数ROM是可以写的,所以不要惊异flash也是一种ROM的实现形式,U盘(就是一种flash),不能写数据,那还怎么用嘛,是不!
手机内存则是个很笼统的说法,我觉得应该指手机的RAM,但事实上很多人用它来指Flash的大小,比如有人说手机内存8G什么的,这肯定是指Flash,手机RAM有256M已经很不错的,现在高端的,比如里程碑2,也就512M,
Flash其实也算ROM的一种,要知道ROM随着发展已出现很多种不同的类型,最早的是掩模ROM,这一类就像你买的D版光盘一样,数据已经用专门的工具压在里面了,你除了读数据其他什么也不能干。
然后是可编程的ROM,就像你买的CD-R光盘一样,只能写一次(当然,如果没有关闭光盘的话,你可以继续写,但同一块数据区你只能写一次),然后就不能写只能读。
后来又出现了可擦除可编程的ROM,其中又出现三种:
1、可擦除可编程ROM,这类是用紫外光照射擦除。
2、电可擦除可编程,这类虽然可擦除,但擦除电压跟写入电压不一样,也就是说,你给用专门的设备擦除。
3、快擦型存储器,这类即Flash,
具体到手机上:
(1)我们的手机CPU用的是ARM体系架构,比如说这个魅9啊用的是ARM Cortex A8(按中关村报的片子是三星 S5PC110来说),而N97用的是ARM11,具有增强的cache结构,那这cache就是RAM,具体是SRAM喽
(2)那你的手机上总要装个操作系统吧,什么android、ios、wp7、sysbian、,那就要用ROM了,一般现在都用flash做ROM,这部分空间操作系统会禁止一般用户访问,那余下的ROM空间怎么办呢,提供给用户用用吧,于是你就可以把相册啊之类的东西放进去了(可是记得flash是有读写次数的哦,每做一次就物理性伤害flash一次,一般flash的读写次数是10万次以上,数据储存年限是10年以上,所以大家用吧,用吧,不是罪),anyway,再分享一点,操作系统其实就是一个好大好大的程序而已。
(3)写的程序运行的时候要有与数据打交道吧,这时就要用到RAM了,具体就是DRAM了,所以楼主不需要去考虑RAM怎么怎么样了,那是操作系统的事情。
没有用SD卡,你用来存照片呀什么什么的就是机子内置用flash实现的ROM,和操作系统共用的,我们一般所说的手机内存其实就是只这个
手机上ROM的特殊性不过手机上的ROM和我们一般意义上定义的ROM不同,因为虽然手机上的ROM是可以储存东西,但是我们在一般情况下只能读取不能写入,这也就是为什么其叫做“只读储存器”的原因,只有在特定的专业条件下,我们才可以往ROM里面写入东西。鉴于ROM的这种特殊性,很多手机的系统文件都存储在手机的ROM 里,这样在一般情况下我们就无法修改手机的系统文件,不过目前通过一些特殊的手段,我们也可以实现往ROM里面写入东西,这种情况其实也就是我们经常所说的刷机、刷系统,因此这也被大家叫做刷ROM。综上所述,手机上的ROM是指手机系统及可安装程序的空间,ROM越大,能直接在系统里安装的程序就越多,相当于我们电脑的C盘。当然ROM空间也是越大越好。不过现在很多程序都可以完全安装到内存卡,所以这个指标对于一个手机配置来说就看来不是那么重要了,但是也不能太小,太小了,我们知道,安卓手机系统版本经常要升级,新版本有时候需要更大的ROM空间才能存储,我们选择手机的时候,看当下情况,始终就行,只是让大家知道ROM的大小和手机运行快慢没有绝对的关系。
手机上的RAM是指系统运行及软件运行可需要的临时空间,跟电脑上的内存是相同的意思。
RAM越大,可同时运行的程序就越多,一些需要大量RAM的游戏也越流畅。同等cpu配置的情况下,RAM越大运行就越流畅。同样的道理,开机系统服务及一些软件自动后台运行,都会占用一部分RAM空间。如果厂家说明是512MB,实际开机后你看到的却远远少于512,这也是正常的。这个RAM是越大越好。当然,RAM越大价格肯定越贵。
再说下怎么看ROM和RAM
目前对于手机内存配置厂商一般会标注ROM与RAM。ROM即指Flash闪存,RAM即指DRAM。此外,还要看是大写的GB还是小写的Gb,前者是后者的8倍。
好了,先来看看低端智能机的配置。低端机一般是一颗主芯片(BB与AP集成的SoC)配一个Flash+DRAM。Flash与DRAM封一起,称为ND MCP。目前主要的配置有两种:一种是ND 4Gb+2Gb;另一种是ND4Gb+4Gb。前者也即厂商通俗所称的512MBROM+2RAM,目前价格约为4.2-4.5美元;后者也即厂商所报的512MBROM+4RAM,目前报价约为7-7.5美元。现在主流低端智能机已采用第二种了。低端机的512MB ROM不能支持Android4.0。
这里先说下这个4+2G, 4+4G换算成我们熟悉的M的方法:
4G =1024*1024*1024*4 = 4294967296(b位),基中一个字节等于8位,所以这里还要除以8
4G = 1024*1024*1024*4/8= 4294967296(位)/8 = 53 687 0912B,再将53 687 0912B换算成M:
53 687 0912/1024/1024 = 512M
同理:2G = 1024*1024*1024*2 = 2147483648b 再除以8换算成字节
2G = 1024*1024*1024*2/8 = 268435456B 再换算成M
2G= 268435456/1024/1024 = 256M
所以4+2G换算成以M为单位的内存,实际上就是512M(ROM)+256M(RAM)
4+4G换算成以M为单位的内存,实际上就是512M(ROM)+512M(RAM)
我们平时编译工程时修改system、boot、userdata等分区,是在u-boot\include\configs\sp8810.h这个文件里修改的#define MTDPARTS_DEFAULT"mtdparts=sprd-nand:256k(spl),512k(2ndbl),256k(params),512k(vmjaluna),10m(modem),3840k(fixnv),3840k(backupfixnv),5120k(dsp),3840k(runtimenv),10m(boot),10m(recovery),220m(system),150m(userdata),60m(cache),256k(misc),1m(boot_logo),1m(fastboot_logo),3840k(productinfo),512k(kpanic),512k(simlock),10m(intmemory)" 这里的分区,实际上是指的4G的那个NAND ROM分区
分区信息可以通过下面的命令查询:$ adb shell
$ cat /proc/mtd
分区对应的img(源码编译后生成在out/target/product/xxx/目录下)
a) modem(多个,支持电话和GPS)
amss.mbn
b) bootloader(启动用)
appsboot.mbn
c) 空中升级
fota*(升级用, 可能多个)
d) 内核和文件系统
boot.img(内核和基本文件系统)
e) 系统分区
system.img(系统分区)
f) 用户数据分区
userdata.img(数据分区)
说到这呢,再来跟大家谈谈Android系统的RAM内存分配机制,
dalvik.vm.heapsize=24m,修改为dalvik.vm.heapsize=32m,此步骤为修改虚拟机大小为32m
这是修改 虚拟机内存,不是修改虚拟内存!! 这2者完全不同!
有人说修改后可以提升手机的性能。修改后有说好用的,也有说不好用的
我来跟你解释这个参数有什么作用,并且说说为什么我做ROM的时候最好不修改这个参数。
dalvik.vm.heapsize=24m,修改为dalvik.vm.heapsize=32m,此步骤为修改虚拟机大小为32m
安卓系统 实际上,就是建立在 linux内核上的一个JAVA系统, 了解JAVA的同学应该知道, 在运行JAVA程序的时候,需要在每个程序上建立一个虚拟机, 以获得内存的分配,优点是假设某个程序崩溃了,系统只要关闭那个虚拟机就可以了,不会影响其他程序,缺点是很耗内存,因为你每开一个程序,就要新开一个虚拟机。 举个例子,打开程序A 的时候,程序A 就自动向系统申请1份虚拟机内存, 然后不关闭,再开一个程序B ,程序B要求向系统申请 3份内存, 假设虚拟机内存设置为24M ,那么这2个程序合共占用了内存 1X24 + 3X24 = 96M 。 假设虚拟机内存设置为 32M ,则这2个程序合共占用了内存 1X32 + 3X32 =128M
那么,究竟修改好,还是不修改好呢?
假设你的机子里,平常只运行一些小程序,例如QQ、看书软件、小游戏等,那么建议你不要修改,就使用标准的24M,因为足够用,并且防止运行程序过多,而产生崩溃。那是不是越小越好呢?当然不是,因为分配给程序的内存过小,有可能因为软件申请不到足够的内存,而运行不流畅。
假设平常经常运行大型程序,例如大型3D游戏等,则可以稍微调大一些,以使大型程序得到足够的内存来运行,可以更流畅。 那是不是越大越好呢,当然不是,因为调的太大,其他程序就分配不到内存,无法运行了。
google 为什么要把系统的虚拟机设置为24M 呢? 由于有些软件吃内存,也有一些小软件不吃内存,我觉得这是属于一个中庸的设计,由于没法全部偏向大型,也没法全部偏向小型,那就中庸吧。
我们都知道,华为C8650的机身内存 RAM 是256M ,这是固定的,给一个虚拟机用24M 还是 32M ,自己决定吧,不过,对于某些程序控,在手机里装一大堆需要开启服务的软件的筒子们,建议还是别改了,否则开机的时候需要启动一大堆程序,最后弄得机子都开不了。
我们来和常用的windows系统对比一下,假设同样有256M内存
windows系统的内存机制是:来了一个程序,程序告诉系统,我要100M ,然后来了第二个程序,第二个程序说,我要150M, 这个时候,内存已经被使用100M + 150M = 250M,剩余6M 。
然后来了第三个程序,第三个程序说,我要50M, 由于不够分给他,系统就崩溃了。
安卓系统的内存机制是: 来了一个程序,程序告诉系统,我要100M ,然后来了第二个程序,第二个程序说,我要150M, 这个时候,内存已经被使用100M + 150M = 250M,剩余6M 。
然后来了第三个程序,第三个程序说,我要50M, 这个时候系统自动关闭最先申请内存程序的虚拟机,或者最次要程序的虚拟机,他把第一个程序关闭了,系统获得了100M的内存,加上剩余的6M,合共106M,分配给第三个程序50M,系统继续运行。
第一个是 三个和尚,最后大家都没水吃 第二个是 牺牲小我,完成大我。
当然,系统在实际运用中,比上面复杂多了,例如安卓还加入了“隐藏机制”,只要不运行的程序,都自动马上释放内存, windows系统出现3个程序抢内存的情况,也加入了“等待机制”等,有兴趣的话可以自己研究。
其实很多人都是把使用其他系统的习惯带过来来了。android大多应用没有退出的设计其实是有道理的,这和系统对进程的调度机制有关系。如果你知道java,就能更清楚这机制了。其实和java的垃圾回收机制类似,系统有一个规则来回收内存。进行内存调度有个阀值,只有低于这个值系统才会按一个列表来关闭用户不需要的东西。当然这个值默认设置得很小,所以你会看到内存老在很少的数值徘徊。但事实上他并不影响速度。相反加快了下次启动应用的速度。这本来就是android标榜的优势之一,如果人为去关闭进程,没有太大必要。特别是自动关进程的软件。
到这里有人会说了,那为什么内存少的时候运行大型程序会慢呢?其实很简单,在内存剩余不多时打开大型程序,会触发系统自身的调进程调度策略,这是十分消耗系统资源的操作,特别是在一个程序频繁向系统申请内存的时候。这种情况下系统并不会关闭所有打开的进程,而是选择性关闭,频繁的调度自然会拖慢系统。所以,论坛上有个更改内存阀值的程序可以有一定改善。 但改动也可能带来一些问题,取决于值的设定。
那么,进程管理软件有无必要呢?有的。就是在运行大型程序之前,你可以手动关闭一些进程释放内存,可以显著的提高运行速度。但一些小程序,完全可交由系统自己管理。谈到这里,可能有的朋友会问,如果不关程序是不是会更耗电。我就说说android后台的原理,你就明白了。android的应用在被切换到后台时,它其实已经被暂停了,并不会消耗cpu资源,只保留了运行状态。所以为什么有的程序切出去重进会到主界面。但是,一个程序如果想要在后台处理些东西,如音乐播放,它就会开启一个服务。服务可在后台持续运行,所以在后台耗电的也只有带服务的应用了。这个在进程管理软件里能看到,标签是service。
所以没有带服务的应用在后台是完全不耗电的,没有必要关闭。这种设计本来就是一个非常好的设计,下次启动程序时,会更快,因为不需要读取界面资源,何必要关掉他们抹杀这个android的优点呢? 还有一个。为什么android一个应用看起来那么耗内存。大家知道,android上的应用是java,当然需要虚拟机,而android上的应用是带有独立虚拟机的,也就是每开一个应用就会打开一个独立的虚拟机。这样设计的原因是可以避免虚拟机崩溃导致整个系统崩溃,但代价就是需要更多内存。
以上这些设计确保了android的稳定性,正常情况下最多单个程序崩溃,但整个系统不会崩溃,也永远没有内存不足的提示出现。大家可能是被windows影响得太深了,总想保留更多的内存,但实际上这并不一定会提升速度,相反却丧失了程序启动快的这一系统特色,不一定有必要。大家不妨按我说的习惯来用用这个系统。至于为什么开了大程序或者开了好几个程序之后切换会变慢,我的理解如下 :
1.大程序A已经开启,占用70%内存,如果再想运行一个B,需要50%的内存,则就需要一个将A从内存中释放或者压缩的过程,所以表现出来的就是慢一会儿
2.A\B\C\D\E共占用内存80%,运行新程序Z需要20%的内存,系统内存因为没见过剩余0的时候,也就是应该剩一部分空闲内存,那么就需要从A~E这几个程序中选择一个或者几个来关闭,这一过程也需要耗费系统资源,所以会慢一会儿
3.也就是说你手动去杀程序的时候,就是替系统在释放内存,就算你不杀,在需要内存的时候系统也会自动释放。
4.不在后台运行的程序(没服务的),即使不杀也不会耗电。在后台运行的(
有服务的)程序,如后台放歌,当然会耗电。
5.不是说杀进程没用,不然作者就不会推荐进程管理软件了。哪个带服务耗电哪个后台一直在运行,看服务就能看出来,这样的该杀。
6,以qq举例,正常的退出,会在进程管理里留下qq的运行状态,但不耗电不占cpu,如果你只是切换出去(按home键而不是退出)那么自然会耗电,因为程序还在运行。
再来讨论下另一个经常碰到的问题OOM手机的RAM普通比较小,如果写代码时不注意某些细节,就有可能导导制内存溢出(OOM)错误。
1.什么是 OutOfMemoryError:
官方引用: Thrown when a request for memory ismade that can not be satisfied using the available platform resources. Such arequest may be made by both the running application or by an internal functionof the VM.
通俗的讲:就是在请求一块内存的时候,当前可用资源不够用来请求时抛出的一种错误。我们知道,每个 android 程序就是一个独立dalvik vm 实例,每个实例限制了最大内存占用,如果超过了这个限制,系统就会抛出这个错误。所以跟整个设备的剩余内存没太大关系,当然如果设备剩余内存都不足以再运行一个程序时,系统就会选择 kill 部分程序以确保有足够内存运行其他程序。
2.android 内存组成:
android 内存由 dalvik 和 native 2部分组成,dalvik 也就是 java 堆,创建的对象就是在这里分配的,而 native 是通过 c/c++ 方式申请的内存,Bitmap 就是以这两种方式分配的(android3.0以后,系统默认是通过 dalvik 分配的)。当然无论以何种方式分配,2部分加起来不能超过 android 对单个程序的内存限制。
内存溢出主要由以下几种情况引起:
1.数据库的cursor没有关闭。
2. listview构造adapter时没有使用缓存contentview。
3.调用registerReceiver后未调用unregisterReceiver()。
4.未关闭InputStream/OutputStream。
5.Bitmap使用后未调用recycle()。
6.Context泄漏。
现在我们首重说下Bitmap引起的内存泄露, 一张图片到底
占用多少内存呢,java 没有 c 的 sizeof() 函数,无法准确去量化这个数值,但是可以有粗略的计算方法:
宽 * 高 * 每个像素所占的 bytes
宽度和高度这个很容易获得,那每个像素所占的 bytes 呢,这个主要取决于decode 图片的方式:
Bitmap.Config ALPHA_8 Each pixel is stored as a single translucency(alpha) channel.
Bitmap.Config ARGB_4444 This field is deprecated. Because of thepoor quality of this configuration, it is advised to use ARGB_8888 instead.
Bitmap.Config ARGB_8888 Each pixel is stored on 4 bytes.
Bitmap.Config RGB_565 Each pixel is stored on 2 bytes andonly the RGB channels are encoded: red is stored with 5 bits of precision (32possible values), green is store d with 6 bitsof precision (64 possible values) and blue is stored with 5 bits of precision.
以上是官方文档对 Bitmap.Config 类的描述,所以,如果以 ARGB_8888 的方式 decode,那个每个像素占用4个 bytes,而如果用RGB_565 就占用2个 bytes。
我们计算一下,在 2.3 以后,程序自带的图片资源,都默认以 ARGB_8888 的方式,而在此以之前是以RGB_565 的方式(不确定,待验证),所以颜色会有损耗,典型的就是如果有渐变色的话,会出现光圈。
所以,一张1600*900分辨率大小的图片占内存大小计算如下:
1600 * 900 * 4 =5760000(b) /8 =720000B /1024 = 703.125K
当然在实际用命令打印出的内存情况上看,比这个数字要大,是因为这只是图片像素的内存,还有一些属性,变量和类本身没有计算在内。
在Android中,一个Process只能使用16/24/32M(这个值在编译工程时通过dalvik.vm.heapsize指定的)内存,如果超过了这个限制就会跳出这个异常。这样就要求我们要时刻想着释放资源。Java的回收工作是交给GC的。但是在android上,图片申请的资源,有在dalvik和 native层上申请的两部份内存。在Dalvik也就是JAVA堆上申请的内存,退出当前应用后,这部份内存就会被GC来回收,但是在native层上分配的内存,却得不到释放,必须通过.recycle()来手动释放,否则会引起内存泄露。如果一个应用里分配了一个Bitmap对象,在退出时没有释放,则如果反复退出进入这个应用几次,就会出现OOM错误
���0H H� i-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>个 bytes。我们计算一下,在 2.3 以后,程序自带的图片资源,都默认以 ARGB_8888 的方式,而在此以之前是以RGB_565 的方式(不确定,待验证),所以颜色会有损耗,典型的就是如果有渐变色的话,会出现光圈。
所以,一张1600*900分辨率大小的图片占内存大小计算如下:
1600 * 900 * 4 =5760000(b) /8 =720000B /1024 = 703.125K
在出现Bitmap内存泄露时,我们通常有两种解决办法:
方法1 : 等比例缩小图片
BitmapFactory.Optionsoptions = new BitmapFactory.Options();
options.inSampleSize = 4;
方法2 : 对图片采用软引用,及时地进行recyle()操作
SoftReference<Bitmap> bitmap;
bitmap = new SoftReference<Bitmap>(pBitmap);
if(bitmap != null){
if(bitmap.get() != null && !bitmap.get().isRecycled()){
bitmap.get().recycle();
bitmap = null;
}
}
我们计算一下,在 2.3 以后,程序自带的图片资源,都默认以 ARGB_8888 的方式,而在此以之前是以RGB_565 的方式(不确定,待验证),所以颜色会有损耗,典型的就是如果有渐变色的话,会出现光圈。
所以,一张1600*900分辨率大小的图片占内存大小计算如下:
1600 * 900 * 4 =5760000(b) /8 =720000B /1024 = 703.125K
这里说下在写应用时经常用到的一个函数:setBackgroundResource(int resid)。 在很多情况下,我们会去调这条语句来切换背景。比如我们四方解锁(自己在2.3.5系统上写的,每切到一个方向时换一张背景来表示选中了哪个应用),当我们按住锁滑向左边照相机时,我们要把背景切成照相机选中状态的那张背景,这时如果调用了setBackgroundResource(int resid)这个函数来设置背景,是可以正常设置成功,表面上也看不出什么问题。但是,如果我们不停的滑动解锁图标,会导制不停的调用setBackgroundResource(int resid)它。在这个函数里,取出了我们的图片,但是没有及时释放。 如果只调这语句一两次,或者操作不要这么频敏,还影响不大。因为当我们退出这个应用后,到下次系统显示高用system.gc()来回收垃圾时,会把这个应用虚拟机里分配的内存都释放掉。但是如果频繁操作的话,system.gc()可能还没有调用之前,或者说在当前应用里不停的调用setBackgroundResource(int resid),没有退出应用,那么就算你显示的调用system.gc(),也回收不了这里分配的空间。所以很快便会产生OOM错误.
再讨论另一种情况:明明还有很多内存,但是发生OOM了。
这种情况经常出现在生成Bitmap的时候。有兴趣的可以试一下,在一个函数里生成一个13m 的int数组。
再该函数结束后,按理说这个int数组应该已经被释放了,或者说可以释放,这个13M的空间应该可以空出来,
这个时候如果你继续生成一个10M的int数组是没有问题的,反而生成一个4M的Bitmap就会跳出OOM。这个就奇怪了,为什么10M的int够空间,反而4M的Bitmap不够呢?
这个问题困扰很久,在网上,国外各大论坛搜索了很久,一般关于OOM的解释和解决方法都是,如何让GC尽快回收的代码风格之类,并没有实际的支出上述情况的根源。
直到昨天在一个老外的blog上终于看到了这方面的解释,我理解后归纳如下:在Android中:
1.一个进程的内存可以由2个部分组成:java 使用内存 ,C 使用内存 ,这两个内存的和必须小于16M/24/32…(取决于工程配置),不然就会出现大家熟悉的OOM,这个就是第一种OOM的情况。
2.更加奇怪的是这个:一旦内存分配给Java后,以后这块内存即使释放后,也只能给Java的使用,这个估计跟java虚拟机里把内存分成好几块进行缓存的原因有关,反正C就别想用到这块的内存了,所以如果Java突然占用了一个大块内存,即使很快释放了:
C能使用的内存 = 16M - Java某一瞬间占用的最大内存。
而Bitmap的生成是通过malloc进行内存分配的,占用的是C的内存,这个也就说明了,上述的4MBitmap无法生成的原因,因为在13M被Java用过后,剩下C能用的只有3M了。
对于Android平台来说,其托管层使用的DalvikJavaVM,网上有一些针对它的优化处理,手动干涉GC处理,使用 dalvik.system.VMRuntime类提供的setTargetHeapUtilization方法可以增强程序堆内存的处理效率。
代码如下:
private final static floatTARGET_HEAP_UTILIZATION = 0.75f;
在程序onCreate时就可以调用
VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);
即可
5:自定义我们的应用需要多大的内存,这个好暴力哇,强行设置最小内存大小,代码如下:
private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;
//设置最小heap内存为6MB大小
VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);
通过setMinimumHeapSize函数设置应用的HEAP SIZE来解决这个问题,其实是不对的。
VMRuntime.getRuntime().setMinimumHeapSize(NewSize);
堆(HEAP)是VM中占用内存最多的部分,通常是动态分配的。堆的大小不是一成不变的,通常有一个分配机制来控制它的大小。比如初始的HEAP是4M大,当4M的空间被占用超过75%的时候,重新分配堆为8M大;当8M被占用超过75%,分配堆为16M大。倒过来,当16M的堆利用不足30%的时候,缩减它的大小为8M大。重新设置堆的大小,尤其是压缩,一般会涉及到内存的拷贝,所以变更堆的大小对效率有不良影响。
上面只是个例子,不过可以看到三个参数:max heap size, min heap size, heaputilization(堆利用率)。
Max Heap Size,是堆内存的上限值,Android的缺省值是16M(某些机型是24M、32M),对于普通应用这是不能改的。函数setMinimumHeapSize其实只是改变了堆的下限值,它可以防止过于频繁的堆内存分配,当设置最小堆内存大小超过上限值时仍然采用堆的上限值(16M、24M、32M),对于内存不足没什么作用。
setTargetHeapUtilization(floatnewTarget) 可以设定内存利用率的百分比,当实际的利用率偏离这个百分比的时候,虚拟机会在GC的时候调整堆内存大小,让实际占用率向个百分比靠拢,像上面说的,HEAP是4M大,当4M的空间被占用超过75%的时候,重新分配堆为8M大;当8M被占用超过75%,分配堆为16M大。倒过来,当16M的堆利用不足30%的时候,缩减它的大小为8M大。重新设置堆的大小,这里的75%是可以通过下面的方式自己定义大小的。
//程序onCreate时调用
private finalstatic floatTARGET_HEAP_UTILIZATION = 0.75f;
VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);
最后再说个系统回收机制system.gc()
1.垃圾收集算法的核心思想
Java语言建立了垃圾收集机制,用以跟踪正在使用的对象和发现并回收不再使用(引用)的对象。该机制可以有效防范动态内存分配中可能发生的两个危险:因内存垃圾过多而引发的内存耗尽,以及不恰当的内存释放所造成的内存非法引用。
垃圾收集算法的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配。垃圾收集算法的选择和垃圾收集系统参数的合理调节直接影响着系统性能,因此需要开发人员做比较深入的了解。
2.触发主GC(GarbageCollector)的条件
JVM进行次GC的频率很高,但因为这种GC占用时间极短,所以对系统产生的影响不大。更值得关注的是主GC的触发条件,因为它对系统影响很明显。总的来说,有两个条件会触发主GC:
①当应用程序空闲时,即没有应用线程在运行时,GC会被调用。因为GC在优先级最低的线程中进行,所以当应用忙时,GC线程就不会被调用,但以下条件除外:
Java堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制地调用GC线程,以便回收内存用于新的分配。若GC一次之后仍不能满足内存分配的要求,JVM会再进行两次GC作进一步的尝试,若仍无法满足要求,则 JVM将报“out of memory”的错误,Java应用将停止。
由于是否进行主GC由JVM根据系统环境决定,而系统环境在不断的变化当中,所以主GC的运行具有不确定性,无法预计它何时必然出现,但可以确定的是对一个长期运行的应用来说,其主GC是反复进行的。
3.减少GC开销的措施
根据上述GC的机制,程序的运行会直接影响系统环境的变化,从而影响GC的触发。若不针对GC的特点进行设计和编码,就会出现内存驻留等一系列负面影响。为了避免这些影响,基本的原则就是尽可能地减少垃圾和减少GC过程中的开销。具体措施包括以下几个方面:
(1)不要显式调用System.gc()
此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。
(2)尽量减少临时对象的使用
临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。
(3)对象不用时最好显式置为Null
一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。
(4)尽量使用StringBuffer,而不用String来累加字符串(详见blog另一篇文章JAVA中String与StringBuffer)
由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,如 Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer 是可变长的,它在原有基础上进行扩增,不会产生中间对象。
(5)能用基本类型如Int,Long,就不用Integer,Long对象
基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。
(6)尽量少用静态对象变量
静态变量属于全局变量,不会被GC回收,它们会一直占用内存。
(7)分散对象创建或删除的时间
集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行主GC,以回收内存或整合内存碎片, 从而增加主GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC 的机会。
总结:
手机应用开发资源是很有限的,堆内存的上限值只有16M/24M/32M(具体看工程配置)。不过只要代码写的好,这个值对于目前的手机应用需求已经足够了。
如果出现内存溢出问题,把精力放在代码优化上吧,这才是解决OOM的根本所在。