您好,欢迎来到华佗小知识。
搜索
您的当前位置:首页引起OOM的一些简单原因及应对方案

引起OOM的一些简单原因及应对方案

来源:华佗小知识
引起OOM的⼀些简单原因及应对⽅案

Android oom 有时出现很频繁,这⼀般不是Android设计的问题,⼀般是我们的问题。  就我的经验⽽⾔,出现oom,⽆⾮主要是以下⼏个⽅⾯:  ⼀、加载对象过⼤

  ⼆、相应资源过多,没有来不及释放。  解决这样的问题,也有⼀下⼏个⽅⾯:

  ⼀:在内存引⽤上做些处理,常⽤的有软引⽤、强化引⽤、弱引⽤  ⼆:在内存中加载图⽚时直接在内存中做处理,如:边界压缩.  三:动态回收内存

  四:优化Dalvik虚拟机的堆内存分配  五:⾃定义堆内存⼤⼩

  可真有这么简单吗,不见得,看我娓娓道来:

  软引⽤(SoftReference)、虚引⽤(PhantomRefrence)、弱引⽤(WeakReference),这三个类是对heap中java对象的应⽤,通过这个三个类可以和gc做简单的交互,除了这三个以外还有⼀个是最常⽤的强引⽤.  强引⽤,例如下⾯代码:  Object o=new Object();   Object o1=o;

  上⾯代码中第⼀句是在heap堆中创建新的Object对象通过o引⽤这个对象,第⼆句是通过o建⽴o1到new Object()这个heap堆中的对象的引⽤,这两个引⽤都是强引⽤.只要存在对heap中对象的引⽤,gc就不会收集该对象.如果通过如下代码:  o=null;     o1=null;

  heap中对象有强可及对象、软可及对象、弱可及对象、虚可及对象和不可到达对象。应⽤的强弱顺序是强、软、弱、和虚。对于对象是属于哪种可及的对象,由他的最强的引⽤决定。如下:

String abc=new String(\"abc\"); //1

SoftReference abcSoftRef=new SoftReference(abc); //2

WeakReference abcWeakRef = new WeakReference(abc); //3 abc=null; //4

abcSoftRef.clear();//5在此例中,透过 get() 可以取得此 Reference 的所指到的对象,如果返回值为 null 的话,代表此对象已经被清除。这类的技巧,在设计 Optimizer 或 Debugger 这类的程序时常会⽤到,因为这类程序需要取得某对象的信息,但是

  虚引⽤

  就是没有的意思,建⽴虚引⽤之后通过get⽅法返回结果始终为null,通过源代码你会发现,虚引⽤通向会把引⽤的对象写进referent,只是get⽅法返回结果为null.先看⼀下和gc交互的过程在说⼀下他的作⽤.

不把referent设置为null, 直接把heap中的new String(\"abc\")对象设置为可结束的(finalizable).

与软引⽤和弱引⽤不同, 先把PhantomRefrence对象添加到它的ReferenceQueue中.然后在释放虚可及的对象. 你会发现在收集heap中的new String(\"abc\")对象之前,你就可以做⼀些其他的事情.通过以下代码可以了解他的作⽤.

  虽然这些常见引⽤,能够使其gc回收,但是gc⼜不是⾮常的智能了,因⽽oom乱免。  ⼆:在内存中加载图⽚时直接在内存中做处理。

  尽量不要使⽤setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置⼀张⼤图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存。

  因此,改⽤先通过BitmapFactory.decodeStream⽅法,创建出⼀个bitmap,再将其设为ImageView的 source,decodeStream最⼤的秘密在于其直接调⽤JNI>>nativeDecodeAsset()来完成decode,⽆需再使⽤java层的createBitmap,从⽽节省了java层的空间。

  如果在读取时加上图⽚的Config参数,可以跟有效减少加载的内存,从⽽跟有效阻⽌抛out of Memory异常,另外,decodeStream直接拿的图⽚来读取字节码了, 不会根据机器的各种分辨率来⾃动适应, 使⽤了decodeStream之后,需要在hdpi和mdpi,ldpi中配置相应的图⽚资源, 否则在不同分辨率机器上都是同样⼤⼩(像素点数量),显⽰出来的⼤⼩就不对了。

  另外,以下⽅式也⼤有帮助:

InputStream is = this.getResources().openRawResource(R.drawable.pic1); BitmapFactory.Options options=new BitmapFactory.Options(); options.inJustDecodeBounds = false;

options.inSampleSize = 10; //width,hight设为原来的⼗分⼀ Bitmap btp =BitmapFactory.decodeStream(is,null,options);

if(!bmp.isRecycle() ){

bmp.recycle() //回收图⽚所占的内存 system.gc() //提醒系统及时回收 }

  以下奉上⼀个⽅法,以最省内存的⽅式读取本地资源的图⽚

/** *

* @param context

* @param resId

* @return */

public static Bitmap readBitMap(Context context, int resId){

BitmapFactory.Options opt = new BitmapFactory.Options();

opt.inPreferredConfig = Bitmap.Config.RGB_565;

opt.inPurgeable = true;

opt.inInputShareable = true;

//获取资源图⽚

InputStream is = context.getResources().openRawResource(resId);

return BitmapFactory.decodeStream(is,null,opt); }

昨天在模拟器上给gallery放⼊图⽚的时候,出现java.lang.OutOfMemoryError: bitmap size exceeds VM budget 异常,图像⼤⼩超过了RAM内存。

模拟器RAM⽐较⼩,只有8M内存,当我放⼊的⼤量的图⽚(每个100多K左右),就出现上⾯的原因。

由于每张图⽚先前是压缩的情况,放⼊到Bitmap的时候,⼤⼩会变⼤,导致超出RAM内存,具体解决办法如下:

```java

//解决加载图⽚ 内存溢出的问题

//Options 只保存图⽚尺⼨⼤⼩,不保存图⽚到内存

BitmapFactory.Options opts = new BitmapFactory.Options();

//缩放的⽐例,缩放是很难按准备的⽐例进⾏缩放的,其值表明缩放的倍数,SDK中建议其值是2的指数值,值越⼤会导致图⽚不清晰 opts.inSampleSize = 4; Bitmap bmp = null;

bmp = BitmapFactory.decodeResource(getResources(), mImageIds[position],opts);

...

//回收

bmp.recycle();

通过上⾯的⽅式解决了,但是这并不是最完美的解决⽅式。通过⼀些了解,得知如下:优化Dalvik虚拟机的堆内存分配

对于Android平台来说,其托管层使⽤的Dalvik JavaVM从⽬前的表现来看还有很多地⽅可以优化处理,⽐如我们在开发⼀些⼤型游戏或耗资源的应⽤中可能考虑⼿动⼲涉GC处理,使⽤dalvik.system.VMRuntime类提供的setTargetHeapUtilization⽅法可以增强程序堆内存的处理效率。当然具体原理我们可以参考开源⼯程,这⾥我们仅说下使⽤⽅法:private final static floatTARGET_HEAP_UTILIZATION = 0.75f; 在程序onCreate时就可以调⽤VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);即可。Android堆内存也可⾃⼰定义⼤⼩

对于⼀些Android项⽬,影响性能瓶颈的主要是Android⾃⼰内存管理机制问题,⽬前⼿机⼚商对RAM都⽐较吝啬,对于软件的流畅性来说RAM对性能的影响⼗分敏感,除了 优化Dalvik虚拟机的堆内存分配外,我们还可以强制定义⾃⼰软件的对内存⼤⼩,我们使⽤Dalvik提供的dalvik.system.VMRuntime类来设置最⼩堆内存为例:private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;

VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE); //设置最⼩heap内存为6MB⼤⼩。当然对于内存吃紧来说还可以通过⼿动⼲涉GC去处理bitmap 设置图⽚尺⼨,避免 内存溢出 OutOfMemoryError的优化⽅法

★android 中⽤bitmap 时很容易内存溢出,报如下错误:Java.lang.OutOfMemoryError : bitmap size exceeds VM budget● 主要是加上这段:

BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2;

● eg1:(通过Uri取图⽚)

private ImageView preview;

BitmapFactory.Options options = new BitmapFactory.Options();

options.inSampleSize = 2;//图⽚宽⾼都为原来的⼆分之⼀,即图⽚为原来的四分之⼀ Bitmap bitmap = BitmapFactory.decodeStream(cr .openInputStream(uri), null, options); preview.setImageBitmap(bitmap);

以上代码可以优化内存溢出,但它只是改变图⽚⼤⼩,并不能彻底解决内存溢出。● eg2:(通过路径去图⽚)

private ImageView preview;

private String fileName= \"/sdcard/DCIM/Camera/2010-05-14 16.01.44.jpg\"; BitmapFactory.Options options = new BitmapFactory.Options();

options.inSampleSize = 2;//图⽚宽⾼都为原来的⼆分之⼀,即图⽚为原来的四分之⼀

Bitmap b = BitmapFactory.decodeFile(fileName, options); preview.setImageBitmap(b); filePath.setText(fileName);

    这样,能够压缩⾜够的⽐例,但是对于⼩内存⼿机,特别是那种16mheap的⼿机是避免不了了。   三.动态分配内存

  动态内存管理DMM(Dynamic Memory Management)是从Heap中直接分配内存和回收内存。  有两种⽅法实现动态内存管理。

  ⼀是显⽰内存管理EMM(Explicit Memory Management)。

在EMM⽅式,内存从Heap中进⾏分配,⽤完后⼿动回收。程序使⽤malloc()函数分配整数数组,并使⽤free()函数释放分配的内存。

  ⼆是⾃动内存管理AMM(Automatic Memory Management)。

AMM也可叫垃圾回收器(Garbage Collection)。Java编程语⾔实现了AMM,与EMM不同,Run-time system关注已分配的内存空间,⼀旦不再使⽤,⽴即回收。  ⽆论是EMM还是AMM,所有的Heap管理计划都⾯临⼀些共同的问题和前在的缺陷:  1)内部碎⽚(Internal Fragmentation)

当内存有浪费时,内部碎⽚出现。因为内存请求可导致分配的内存块过⼤。⽐如请求128字节的存储空间,结果Run-time system分配了512字节。  2)外部碎⽚(External Fragmentation)

当⼀系列的内存请求留下了数个有效的内存块,但这些内存块的⼤⼩均不能满⾜新请求服务,此时出现外部碎⽚。  3)基于定位的延迟(Location-based Latency)

延迟问题出现在两个数据值存储得相隔很远,导致访问时间增加。

  EMM往往⽐AMM更快。  EMM与AMM⽐较表:

—————————————————————————————————————— EMM AMM

——————————————————————————————————————Benefits 尺⼨更⼩、速度更快、易控制 stay focused on domain issuesCosts 复杂、记账、内存泄露、指针悬空 不错的性能

——————————————————————————————————————早期的垃圾回收器⾮常慢,往往占⽤50%的执⾏时间。

垃圾回收器理论产⽣于1959年,Dan Edwards在Lisp编程语⾔的开发时实现了第⼀个垃圾回收器。垃圾回收器有三种基本的经典算法:

1)Reference counting(引⽤计数)

基本思想是:当对象创建并赋值时该对象的引⽤计数器置1,每当对象给任意变量赋值时,引⽤记数+1;⼀旦退出作⽤域则引⽤记数-1。⼀旦引⽤记数变为0,则该对象可以被垃圾回收。

引⽤记数有其相应的优势:对程序的执⾏来说,每次操作只需要花费很⼩块的时间。这对于不能被过长中断的实时系统来说有着天然的优势。但也有其不⾜:不能够检测到环(两个对象的互相引⽤);同时在每次增加或者减少引⽤记数的时候⽐较费时间。在现代的垃圾回收算法中,引⽤记数已经不再使⽤。

2)Mark-sweep(标记清理)

基本思想是:每次从根集出发寻找所有的引⽤(称为活对象),每找到⼀个,则对其做出标记,当追踪完成之后,所有的未标记对象便是需要回收的垃圾。

也叫追踪算法,基于标记并清除。这个垃圾回收步骤分为两个阶段:在标记阶段,垃圾回收器遍历整棵引⽤树并标记每⼀个遇到的对象。在清除阶段,未标记的对象被释放,并使其在内存中可⽤。

3)Copying collection(复制收集)

基本思想是:将内存划分为两块,⼀块是当前正在使⽤;另⼀块是当前未⽤。每次分配时使⽤当前正在使⽤内存,当⽆可⽤内存时,对该区域内存进⾏标记,并将标记的对象全部拷贝到当前未⽤内存区,这是反转两区域,即当前可⽤区域变为当前未⽤,⽽当前未⽤变为当前可⽤,继续执⾏该算法。拷贝算法需要停⽌所有的程序活动,然后开始冗长⽽繁忙的copy⼯作。这点是其不利的地⽅。近年来还有两种算法:

1)Generational garbage collection(分代)其思想依据是:

(1) 被⼤多数程序创建的⼤多数对象有着⾮常短的⽣存期。 (2) 被⼤多数程序创建的部分对象有着⾮常长的⽣存期。

简单拷贝算法的主要不⾜是它们花费了更多的时间去拷贝了⼀些长期⽣存的对象。

⽽分代算法的基本思想是:将内存区域分两块(或更多),其中⼀块代表年轻代,另⼀块代表⽼的⼀代。针对不同的特点,对年轻⼀代的垃圾收集更为频繁,对⽼代的收集则较少,每次经过年轻⼀代的垃圾回收总会有未被收集的活对象,这些活对象经过收集之后会增加成熟度,当成熟度到达⼀定程度,则将其放进⽼代内存块中。分代算法很好的实现了垃圾回收的动态性,同时避免了内存碎⽚,是⽬前许多JVM使⽤的垃圾回收算法。2)Conservative garbage collection(保守)哪⼀种算法最好?答案是没有最好。

EMM作为很常⽤的垃圾回收算法,有5种基本⽅法:  1)Table-driven algorithms

  表驱动算法把内存分为固定尺⼨的块集合。这些块使⽤抽象数据结构进⾏索引。⽐如⼀个bit对应⼀个块,⽤0和1表⽰是否分配。不利因素:位映射依赖于内存块的尺⼨;另外,搜索⼀系列的空闲内存块可能需要搜索整个bit映射表,这影响性能。

  2)Sequential fit

顺序适应算法允许内存分为不同的尺⼨。此算法跟踪已分配和空闲的Heap,标记空闲块的起始地址和结束地址。它有三种⼦分类: (1) First fit(⾸次适应)——分配找到的第⼀个适合内存请求的块 (2) Best fit(最佳适应)——分配最适合内存请求的块 (3) Worst fit(最不适应)——分配最⼤的块给内存请求

3)Buddy systems

  Buddy systems算法的主要⽬的是加速已分配内存在释放后的合并速度。显⽰内存管理EMM使⽤Buddy systems算法可能导致内部碎⽚。4)Segregated storage

  隔离存储技术涉及到把Heap分成多个区域(zone),并为每个区域采⽤不同的内存管理计划。这是很有效的⽅法。

5)Sub-allocators

  ⼦配置技术尝试解决在Run-time System下分配⼤块内存并单独管理的内存分配问题。换句话说,程序完全负责⾃⼰的私有存储堆(stockpile)的内存分配和回收,⽆需run-time System的帮助。它可能带来额外的复杂性,但是你可以显著地提⾼性能。在1990年的《C Compiler Design》⼀书中,Allen Holub就极好地利⽤了Sub-allocators来加速其编译器的实现。

  注意,显⽰内存管理EMM必须是灵活的,能够响应数种不同类型的请求。

  最后,使⽤EMM还是使⽤AMM?这是⼀个Religious question,凭个⼈喜好。EMM在复杂的开销下实现了速度和控制。AMM牺牲了性能,但换来了简单性。  ⽆论是emm,amm分配内存,出现了oom的问题,由于加载内存过⼤也是在所难免。  四。优化Dalvik虚拟机的堆内存分配

  对于Android平台来说,其托管层使⽤的Dalvik Java VM从⽬前的表现来看还有很多地⽅可以优化处理,⽐如我们在开发⼀些⼤型游戏或耗资源的应⽤中可能考虑⼿动⼲涉GC处理,使⽤dalvik.system.VMRuntime类提供的setTargetHeapUtilization⽅法可以增强程序堆内存的处理效率。当然具体原理我们可以参考开源⼯程,这⾥我们仅说下使⽤⽅法:private final static float TARGET_HEAP_UTILIZATION = 0.75f;在程序onCreate时就可以调⽤VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);即可。 Android堆内存也可⾃⼰定义⼤⼩

对于⼀些Android项⽬,影响性能瓶颈的主要是Android⾃⼰内存管理机制问题,⽬前⼿机⼚商对RAM都⽐较吝啬,对于软件的流畅性来说RAM对性能的影响⼗分敏感,除了优化Dalvik虚拟机的堆内存分配外,我们还可以强制定义⾃⼰软件的堆内存⼤⼩,我们使⽤Dalvik提供的 dalvik.system.VMRuntime类来设置最⼩堆内存为例: private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;

VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE); //设置最⼩heap内存为6MB⼤⼩。当然对于内存吃紧来说还可以通过⼿动⼲涉GC去处理  注意了,这个设置dalvik虚拟机的配置的⽅法对Android4.0 设置⽆效。  这就是我对Android的oom的⼀点看法.

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- huatuo0.cn 版权所有 湘ICP备2023017654号-2

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务