java.lang.OutOfMemoryError简称OOM内存溢出,这是一种很常见的致使的程序崩溃的问题,但也是很容易被开发者忽视的一个问题,由于它不像java.lang.NullPointerException这样的错误,程序一运行就能被发现,它不是每次运行或每台手机都出现,有时可能要等到项目上线,后台产生了大量数据以后才能被发现。
最近作了一个新闻类的项目,和商城类的项目相比,因为涉及更多的图片和视频,很明显数据量要大得多,因此更容易产生OOM的问题。如何解决这个异常呢,首先咱们来看看它是怎么产生的。java
常见缘由有如下几种:
1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
3.代码中存在死循环或循环产生过多重复的对象实体;
4.使用的第三方软件中的BUG;
5.启动参数内存值设定的太小;web
对应的Log显示的错误提示以下:
1.tomcat:java.lang.OutOfMemoryError: PermGen space
2.tomcat:java.lang.OutOfMemoryError: Java heap space
3.weblogic:Root cause of ServletException java.lang.OutOfMemoryError
4.resin:java.lang.OutOfMemoryError
5.java:java.lang.OutOfMemoryError数据库
1.避免循环产生过多重复的对象实体tomcat
我的以为对于这个问题开发程序时应该是能够避免的,即便写程序时没有考虑到,发现是这个缘由形成的,应该也是很容易解决的;
例如,对复杂的listview进行合理设计与编码,注意重用Adapter里面的convertView,以及holder机制的运用网络
2.自定义内存大小和优化Dalvik虚拟机的堆内存ide
在Android2.2以前,有这样一个类dalvik.system.VMRuntime,它提供的setTargetHeapUtilization()方法能够加强程序堆内存的处理效率,代码以下:布局
//在程序onCreate时就能够调用便可优化
private final static floatTARGET_HEAP_UTILIZATION = 0.75f; VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);
还有setMinimumHeapSize()方法能够自定义应用须要多大的内存,强行设置最小内存大小,
//设置最小heap内存为6MB大小this
private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ; VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);
可是上面说了这种方法只适用于Android2.2如下,不然编译是通不过的,既然取消了这个方法,确定有可以代替这个方法,且比这个方法更完美的解决方案,这就是下面我要重点说的几种方法;编码
3.Fragment的懒加载
如今大多数程序一个Activity里面可能会以viewpager(或其余容器)与多个Fragment来组合使用,而若是每一个fragment都须要去加载数据,或从本地加载,或从网络加载,那么在这个activity刚建立的时候就变成须要初始化大量资源。那么,能不能作到当切换到这个fragment的时候,它才去初始化呢?
答案就在Fragment里的setUserVisibleHint这个方法里,该方法用于告诉系统,这个Fragment的UI是不是可见的。因此咱们只须要继承Fragment并重写该方法,便可实如今fragment可见时才进行数据加载操做,即Fragment的懒加载。
代码以下:
public abstract class LazyFragment extends Fragment { protected boolean isVisible; @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if(getUserVisibleHint()) { isVisible = true; onVisible(); } else { isVisible = false; onInvisible(); } } protected void onVisible(){ lazyLoad(); } protected abstract void lazyLoad(); protected void onInvisible(){} }
在LazyFragment,我增长了三个方法,一个是onVisiable,即fragment被设置为可见时调用,一个是onInvisible,即fragment被设置为不可见时调用。另外再写了一个lazyLoad的抽象方法,该方法在onVisible里面调用。
你可能会想,为何不在getUserVisibleHint里面就直接调用呢?我这么写是为了代码的复用。由于在fragment中,咱们还须要建立视图(onCreateView()方法),可能还须要在它不可见时就进行其余小量的初始化操做(好比初始化须要经过AIDL调用的远程服务)等。
而setUserVisibleHint是在onCreateView以前调用的,那么在视图未初始化的时候,在lazyLoad当中就使用的话,就会有空指针的异常。而把lazyLoad抽离成一个方法,那么它的子类就能够这样作:
public class OpenResultFragment extends LazyFragment{ // 标志位,标志已经初始化完成。 private boolean isPrepared; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.d(LOG_TAG, "onCreateView"); View view = inflater.inflate(R.layout.fragment_open_result, container, false); //XXX初始化view的各控件 isPrepared = true; lazyLoad(); return view; } @Override protected void lazyLoad() { if(!isPrepared || !isVisible) { return; } //填充各控件的数据 } }
在上面的类当中,咱们增长了一个标志位isPrepared,用于标志是否初始化完成。而后在咱们所须要的初始化操做完成以后调用,如上面的例子当中,在初始化view以后,设置 isPrepared为true,同时调用lazyLoad()方法。而在lazyLoad()当中,判断isPrepared和isVisible只要有一个不为true就不往下执行。也就是仅当初始化完成,而且可见的时候才继续加载,这样的避免了未初始化完成就使用而带来的问题。
4.利用BitmapFactory.Options限制图片的大小
有时加载数据量并非很大时也会产生OOM异常,通常是因为加载图片形成的,Bitmap加载图片最终都是经过java层的createBitmap来完成的,须要消耗大量内存,咱们能够利用BitmapFactory.Options限制图片的大小,下降图片质量,减小图片所占内存
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);
5.及时对图片进行recyle()操做
if(!bmp.isRecycle() ){ bmp.recycle() //回收图片所占的内存 system.gc() //提醒系统及时回收 }
以上是OOM的常见的几种产生缘由和解决方案,还有不少方法,例如,还有看看页面布局当中有没有大的图片,好比背景图之类的。去除xml中相关设置,改在程序中设置背景图(放在onCreate()方法中);尽可能不使用静态的图片和全局性的图片;在Activity destory时注意,bg.setCallback(null); 防止Activity得不到及时的释放。等等,还须要进一步地研究