Android系统中GC内存泄漏的原因
主动回收内存System.gc();、getruntime.runtime.gc
导致内存泄漏主要的原因是,申请了内存空间而忘记了释放。如果程序中存在对无用对象的引用,那么这些对象就会驻留内存,消耗内存,因为无法让垃圾回收器GC验证这些对象是否不再需要。如果存在对象的引用,这个对象就被定义为"有效的活动",同时不会被释放。要确定对象所占内存将被回收,我们就要务必确认该对象不再会被使用。典型的做法就是把对象数据成员设为null或者从集合中移除该对象。但当局部变量不需要时,不需明显的设为null,因为一个方法执行完毕时,这些引用会自动被清理。
什么是GC
GC垃圾收集器,它让创建的对象不需要像c/c++那样delete、free掉,GC的时间系统自身决定,时间不可预测或者说调用System.gc()的时候。 对超出作用域的对象或引用置为空的对象进行清理,删除不使用的对象,腾出内存空间。
Java带垃圾回收的机制,为什么还会内存泄露呢?
举个例子 当你堆里某个对象没有被引用时,然后再过一段时间,垃圾回收机制才会回收,那么
while(true){
String str=new String("ni hao ni hao ");
}
一直循环创建 String对象。。。你觉得堆不会溢出嘛。。。
垃圾回收 要有2个条件
1 该对象没有被引用
2 过了一段时间
Java 内存泄露的根本原因就是 保存了不可能再被访问的变量类型的引用,回收不确定性
内存溢出和内存泄漏
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。memory leak会最终会导致out of memory!
内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。
从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。
Java中有内存泄露吗?举一些例子?
1.查询数据库没有关闭游标 2. 构造Adapter时,没有使用缓存的 convertView 3. Bitmap对象不再使用时调用recycle()释放内存 4. 无用时没有释放对象的引用 5. 在Activity中使用非静态的内部类,并开启一个长时间运行的线程,因为内部类持有Activity的引用,会导致Activity本来可以被gc时却长期得不到回收 6.使用Handler处理消息前,Activity通过例如finish()退出,导致内存泄漏 7.动态注册广播在Activity销毁前没有unregisterReceive
内存的优化
- 回收已经使用的资源,比如游标cursor 、I/O、Bitmap(close并且引用置为null)
- 合理的使用缓存,比如图片是很耗内存的,使用lru缓存图片和压缩
- 合理设置变量的作用范围
- 节制的使用服务,后台任务运行完,即使它不执行任何操作,服务也会一直运行,这些是十分消耗内存的,可以用intentservice
- 当界面不可见时释放内存,在activity的onTrimMemory方法里与ui的相关资源,在onstop里释放与组件相关的资源
- 合理的使用多进程,如果后台任务和前台界面是相互独立在,可以在组件标签下写process,这样这个组建就在另一个进程里了。而服务的话更倾向于开启自己所依赖的进程,而那个进程可能很多东西都不需要,比如ui
-
使用线程池、对象池
-
Bitmap对象在不使用时,应该先调用recycle()释放内存,然后才它设置为null。
说说线程池
好处
避免线程的创建和销毁所带来的性能得开销
能有效控制线程池的最大并发数,避免了大量线程间抢占资源而导致的阻塞现象
能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能
由于不需要每次处理复杂逻辑耗时操作,比如加载网络并不需要都开启一个新的线程,可以用线程池处理,把线程存起来,用的时候在取出来,
在onDestory里去销毁线程,这样就会节省内存
//AsyncTask就是Handler和线程池的封装 //自定义线程池 public class ThreadManager { private ThreadManager() { } private static ThreadManager instance = new ThreadManager(); private ThreadPoolProxy longPool; private ThreadPoolProxy shortPool; public static ThreadManager getInstance() { return instance; } // 联网比较耗时 // 开启线程数一般是cpu的核数*2+1 public synchronized ThreadPoolProxy createLongPool() { if (longPool == null) { longPool = new ThreadPoolProxy(5, 5, 5000L); } return longPool; } // 操作本地文件 public synchronized ThreadPoolProxy createShortPool() { if(shortPool==null){ shortPool = new ThreadPoolProxy(3, 3, 5000L); } return shortPool; } public class ThreadPoolProxy { private ThreadPoolExecutor pool; private int corePoolSize; private int maximumPoolSize; private long time; public ThreadPoolProxy(int corePoolSize, int maximumPoolSize, long time) { this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.time = time; } /** * 执行任务 * @param runnable */ public void execute(Runnable runnable) { if (pool == null) { // 创建线程池 /* * 1. 线程池里面管理多少个线程2. 如果排队满了, 额外的开的线程数3. 如果线程池没有要执行的任务 存活多久4. * 时间的单位 5 如果 线程池里管理的线程都已经用了,剩下的任务 临时存到LinkedBlockingQueue对象中 排队 */ pool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, time, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(10)); } pool.execute(runnable); // 调用线程池 执行异步任务 } /** * 取消任务 * @param runnable */ public void cancel(Runnable runnable) { if (pool != null && !pool.isShutdown() && !pool.isTerminated()) { pool.remove(runnable); // 取消异步任务 } } } }
-
静态内部类:尽量不要用一个生命周期长于Activity的对象来持有Activity的引用。声明handler为static类,这样内部类就不再持有外部类的引用了,就不会阻塞Activity的释放。在Activity中尽量避免使用生命周期不受控制的非静态类型的内部类,可以使用静态类型的内部类加上弱引用的方式实现。
-
静态变量:不要直接或者间接引用Activity、Service等。这会使用Activity以及它所引用的所有对象无法释放,然后,用户操作时间一长,内存就会狂升。
- 静态引用:应该避免 static 成员变量引用资源耗费过多的实例,比如 Context。尽量使用 getApplicationContext:如果为了满足需求下必须使用 Context 的话:Context 尽量使用 Application Context,因为 Application 的 Context 的生命周期比较长,引用它不会出现内存泄露的问题,而不是activity的context,单例。可以通过调用 Context.getApplicationContext() or Activity.getApplication()来获得
Handler内存泄漏
Handler作为内部类存在于Activity中,但是Handler生命周期与Activity生命周期往往并不是相同的,比如当Handler对象有Message在排队,则无法释放,进而导致本该释放的Acitivity也没有办法进行回收。
解决办法:声明handler为static类,这样内部类就不再持有外部类的引用了,就不会阻塞Activity的释放
System.gc()
我们可以调用System.gc方法,建议虚拟机进行垃圾回收工作(注意,是建议,但虚拟机会不会这样干,我们也无法预知!)
下面来看一个例子来了解finalize()和System.gc()的使用:
public class TestGC { public TestGC() {} //当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。 protected void finalize() { System.out.println("我已经被垃圾回收器回收了..."); } public static void main(String [] args) { TestGC gc = new TestGC(); gc = null; // 建议虚拟机进行垃圾回收工作 System.gc(); } }
如上面的例子所示,大家可以猜猜重写的finalize方法会不会执行?
答案是:不一定!
因为无论是设置gc的引用为null还是调用System.gc()方法都只是"建议"垃圾回收器进行垃圾回收,但是最终所有权还在垃圾回收器手中,它会不会进行回收我们无法预知!
AsynTask为什么要设计为只能够一次任务?
最核心的还是线程安全问题,多个子线程同时运行,会产生状态不一致的问题。所以要务必保证只能够执行一次
java中的soft reference
StrongReference 是 Java 的默认引用实现, 它会尽可能长时间的存活于 JVM 内, 当没有任何对象指向它时 GC 执行后将会被回收,SoftReference 会尽可能长的保留引用直到 JVM 内存不足时才会被回收(虚拟机保证), 这一特性使得 SoftReference 非常适合缓存
内存溢出OOM解决方案?(解决方法)
内存缓存的时候可能内存溢出,因为Android默认给每个app只分配16M的内存,,每个手机不一样,我的手机是3G内存,分配的内存是29m,通过这样可以获得
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
解决方法1:java中的引用(使用软引用)
- 强引用 垃圾回收器不会回收, java默认引用都是强引用
- 软引用 SoftReference 在内存不够时,垃圾回收器会考虑回收
- 弱引用 WeakReference 在内存不够时,垃圾回收器会优先回收
- 虚引用 PhantomReference 在内存不够时,垃圾回收器最优先回收
注意: Android2.3+, 系统会优先将SoftReference的对象提前回收掉, 即使内存够用
内存中使用LRUCache是最合适的。如果用HashMap来实现,不是不可以,但需要注意在合适的时候释放缓存。至于具体怎么释放,我没考虑过,但用软引用的问题在于,你很难控制缓存的大小,也就是说,只有等到你的内存快要撑爆,你的图片缓存才会被回收。是不是感觉傻傻的?
解决方法2:LruCache
least recentlly use 最少最近使用算法
会将内存控制在一定的大小内, 超出最大值时会自动回收, 这个最大值开发者自己定。他内部是是一个linkedhashmap以强引用的方式存储外界的缓存对象,提供了get,put方法来操作,当缓存满了,lru会移除较早使用的缓存对象,把新的添加进来。也可以自己remove
解决方法3:图片压缩
三级缓存
- 先读取内存缓存, 因为优先加载, 速度最快,内存缓存没有再读取本地缓存, 次优先加载, 速度也快,本地没有再加载网络缓存, 速度慢,浪费流量在网络缓存中从网络下载图片,并且保存在本地和内存中,在下载的时候可以对图片进行压缩
- 服务器端下载的图片是使用 Http的缓存机制,每次执行将本地图片的时间发送给服务器,如果俩次访问的时间间隔短,返回码是 304,会读取网络缓存(说明服务端的图片和本地的图片是相同的,直接使用本地保存的图片),如果返回码是 200,则开始下载新的图片并实现缓存。在从服务器获取到图片后,需要再在本地和内存中分别存一份,这样下次直接就可以从内存中直接获取了,这样就加快了显示的速度,提高了用户的体验。
- 大量图片加载,当用户不停的滑动时,由于ui在主线程操作的,会出现卡顿,可以在滑动的时候停止加载(setOnscrollerListener),在getView方法里只有静止才加载图片
InputStream inputStream = conn.getInputStream(); //图片压缩处理 BitmapFactory.Options option = new BitmapFactory.Options(); option.inSampleSize = 2;//宽高都压缩为原来的二分之一, 此参数需要根据图片要展示的大小来确定 option.inPreferredConfig = Bitmap.Config.RGB_565;//设置图片格式 Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, option); return bitmap; ....
本地保存时可以将名字用MD5加密保存
// 将图片保存在本地, bitmap.compress(CompressFormat.JPEG, 100, new FileOutputStream(file));//100是质量 //取 Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream( file));//decodeStream放的是输入输出流 return bitmap; Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream( file));// return bitmap;
内存缓存在保存的时候可以给图片分配内存
private LruCache<String, Bitmap> mMemoryCache; public MemoryCacheUtils() { long maxMemory = Runtime.getRuntime().maxMemory() / 8;//主流都是分配16m的8/1 mMemoryCache = new LruCache<String, Bitmap>((int) maxMemory) { @Override protected int sizeOf(String key, Bitmap value) { int byteCount = value.getRowBytes() * value.getHeight();// 获取图片占用内存大小 return byteCount; } }; }
正常一张720x1080的图片在内存中占多少空间?怎么加载大图片?
看他的渲染方式,RGB888啥的,占用的字节不同的。
如果是一张的话压缩处理,大量图片的话用lru
图片的总大小 = 图片的总像素 * 每个像素占用的大小
加载大图片
- 计算机把图片所有像素信息全部解析出来,保存至内存
-
Android保存图片像素信息,是用ARGB保存,所以每个像素占用4个字节,很容易内存溢出
-
可以对图片的宽高和质量进行压缩
首先对图片进行缩放
-
获取屏幕宽高
//设置缩放比例 opts.inSampleSize = scale; //为图片申请内存 opts.inJustDecodeBounds = false; Bitmap bm = BitmapFactory.decodeFile("sdcard/dog.jpg", opts); iv.setImageBitmap(bm); int scale = 1; int scaleX = imageWidth / screenWidth; int scaleY = imageHeight / screenHeight; if(scaleX >= scaleY && scaleX > 1){ scale = scaleX; } else if(scaleY > scaleX && scaleY > 1){ scale = scaleY; } Options opts = new Options(); //请求图片属性但不申请内存 opts.inJustDecodeBounds = true; BitmapFactory.decodeFile("sdcard/dog.jpg", opts); int imageWidth = opts.outWidth; int imageHeight = opts.outHeight; Display dp = getWindowManager().getDefaultDisplay(); int screenWidth = dp.getWidth(); int screenHeight = dp.getHeight();
-
获取图片宽高
//设置缩放比例 opts.inSampleSize = scale; //为图片申请内存 opts.inJustDecodeBounds = false; Bitmap bm = BitmapFactory.decodeFile("sdcard/dog.jpg", opts); iv.setImageBitmap(bm); int scale = 1; int scaleX = imageWidth / screenWidth; int scaleY = imageHeight / screenHeight; if(scaleX >= scaleY && scaleX > 1){ scale = scaleX; } else if(scaleY > scaleX && scaleY > 1){ scale = scaleY; } Options opts = new Options(); //请求图片属性但不申请内存 opts.inJustDecodeBounds = true; BitmapFactory.decodeFile("sdcard/dog.jpg", opts); int imageWidth = opts.outWidth; int imageHeight = opts.outHeight;
-
图片的宽高除以屏幕宽高,算出宽和高的缩放比例,取较大值作为图片的缩放比例,且大于1才缩放
//设置缩放比例 opts.inSampleSize = scale; //为图片申请内存 opts.inJustDecodeBounds = false; Bitmap bm = BitmapFactory.decodeFile("sdcard/dog.jpg", opts); iv.setImageBitmap(bm); int scale = 1; int scaleX = imageWidth / screenWidth; int scaleY = imageHeight / screenHeight; if(scaleX >= scaleY && scaleX > 1){ scale = scaleX; } else if(scaleY > scaleX && scaleY > 1){ scale = scaleY; }
2.然后按缩放比例加载图片
//设置缩放比例 opts.inSampleSize = scale; //为图片申请内存 opts.inJustDecodeBounds = false; Bitmap bm = BitmapFactory.decodeFile("sdcard/dog.jpg", opts); iv.setImageBitmap(bm);
-
-
针对这个问题,我自己一般用以下两种方法解决: 1、使用WebView来加载该图片; 2、使用MapView或者TileView来显示图片(类似地图的机制);
- 保持良好的编程习惯,不要重复或者不用的代码,谨慎添加libs,移除使用不到的libs。
- 使用proguard混淆代码,它会对不用的代码做优化,并且混淆后也能够减少安装包的大小。
- native code的部分,大多数情况下只需要支持armabi与x86的架构即可。如果非必须,可以考虑拿掉x86的部分。
- 使用Lint工具查找没有使用到的资源。去除不使用的图片,String,XML等等。
- assets目录下的资源请确保没有用不上的文件。
- 生成APK的时候,aapt工具本身会对png做优化,但是在此之前还可以使用其他工具如tinypng对图片进行进一步的压缩预处理。
- jpeg还是png,根据需要做选择,在某些时候jpeg可以减少图片的体积。
- 对于9.png的图片,可拉伸区域尽量切小,另外可以通过使用9.png拉伸达到大图效果的时候尽量不要使用整张大图。
- 有选择性的提供hdpi,xhdpi,xxhdpi的图片资源。建议优先提供xhdpi的图片,对于mdpi,ldpi与xxxhdpi根据需要提供有差异的部分即可。
- 尽可能的重用已有的图片资源。例如对称的图片,只需要提供一张,另外一张图片可以通过代码旋转的方式实现。
- 能用代码绘制实现的功能,尽量不要使用大量的图片。例如减少使用多张图片组成animate-list的AnimationDrawable,这种方式提供了多张图片很占空间。
ListView的优化
- 复用convertview , 历史的view对象
- 减少子孩子查询的次数 viewholder
- 异步加载数据(把图片缓存)
- 条目多时分页加载数据
-
加载时显示进度条让用户等待
-
Item的布局层次结构尽量简单,避免布局太深或者不必要的重绘
-
避免在 getView 方法中做耗时的操作:
例如加载本地 Image 需要载入内存以及解析 Bitmap ,都是比较耗时的操作,如果用户快速滑动listview,会因为getview逻辑过于复杂耗时而造成滑动卡顿现象。用户滑动时候不要加载图片,待滑动完成再加载,可以使用这个第三方库glide - 应该尽量避免 static 成员变量引用资源耗费过多的实例,比如 Context。
-
尽量使用 getApplicationContext:如果为了满足需求下必须使用 Context 的话:Context 尽量使用 Application Context,因为Application 的 Context 的生命周期比较长,引用它不会出现内存泄露的问题
-
在一些场景中,ScollView内会包含多个ListView,可以把listview的高度写死固定下来。
由于ScollView在快速滑动过程中需要大量计算每一个listview的高度,阻塞了UI线程导致卡顿现象出现,如果我们每一个item的高度都是均匀的,可以通过计算把listview的高度确定下来,避免卡顿现象出现 -
使用 RecycleView 代替listview:
每个item内容的变动,listview都需要去调用notifyDataSetChanged来更新全部的item,太浪费性能了。RecycleView可以实现当个item的局部刷新,并且引入了增加和删除的动态效果,在性能上和定制上都有很大的改善 -
ListView 中元素避免半透明:
半透明绘制需要大量乘法计算,在滑动时不停重绘会造成大量的计算,在比较差的机子上会比较卡。 在设计上能不半透明就不不半透明。实在要弄就把在滑动的时候把半透明设置成不透明,滑动完再重新设置成半透明。 -
尽量开启硬件加速:
硬件加速提升巨大,避免使用一些不支持的函数导致含泪关闭某个地方的硬件加速。当然这一条不只是对 ListView。
布局的优化
- 尽量重用一个布局文件,使用include标签,多个相同的布局可以复用
- 减少一个布局的不必要节点
- 尽量使用view自身的参数,例如:Button,有一个可以把图绘制在左边的参数:android:drawableLeft
- 使用< ViewStub />标签来加载一些不常用的布局;使用< merge />标签减少布局的嵌套层次
ViewPager的优化
- viewpager会默认加载左右俩个页面,有时候我们并不想看,会浪费用户的流量,可以在setOnPageChangeListener的onPageSelected的方法里选中哪个页面,初始化哪个页面
- 由于viewpager会默认销毁第三页面,可以强制让viewpager加载所有的页面pagerView.setOffscreenPageLimit(pageCount);,但是如果页面多的话就不能这样干了
- 可以定义一个集合将页面缓存起来,在destroyItem的时候保存起来,在instantiateItem读取集合,有就用,没有的话再创建,就像listview的convertView似的
class HomeAdapter extends PagerAdapter { // 当前viewPager里面有多少个条目 LinkedList<ImageView> convertView=new LinkedList<ImageView>(); @Override public int getCount() { return Integer.MAX_VALUE; } /* 判断返回的对象和 加载view对象的关系 */ @Override public boolean isViewFromObject(View arg0, Object arg1) { return arg0 == arg1; } @Override public void destroyItem(ViewGroup container, int position, Object object) { ImageView view=(ImageView) object; convertView.add(view);// 把移除的对象 添加到缓存集合中 container.removeView(view); } @Override public Object instantiateItem(ViewGroup container, int position) { ImageView view; if(convertView.size()>0){ view=convertView.remove(0); }else{ view= new ImageView(UiUtils.getContext()); } bitmapUtils.display(view, HttpHelper.URL + "image?name=" + datas.get(index)); container.addView(view); // 加载的view对象 return view; // 返回的对象 } }
内存的优化
- 回收已经使用的资源,比如游标cursor 、I/O、Bitmap(close并且引用置为null)
- 合理的使用缓存,比如图片是很耗内存的,使用lru缓存图片和压缩
- 合理设置变量的作用范围
- 节制的使用服务,后台任务运行完,即使它不执行任何操作,服务也会一直运行,这些是十分消耗内存的,可以用intentservice
- 当界面不可见时释放内存,在activity的onTrimMemory方法里与ui的相关资源,在onstop里释放与组件相关的资源
- 合理的使用多进程,如果后台任务和前台界面是相互独立在,可以在组件标签下写process,这样这个组建就在另一个进程里了。而服务的话更倾向于开启自己所依赖的进城,而那个进程可能很多东西都不需要,比如ui
- 使用线程池、对象池
- Bitmap对象在不使用时,应该先调用recycle()释放内存,然后才它设置为null。
代码优化
这部分就是是细微的优化,但是细微多了也就内存节约了
任何一个Java类,包括内部类、匿名类,都要占用大概500字节的内存空间。
任何一个类的实例要消耗12-16字节的内存开支,因此频繁创建实例也是会一定程序上影响内存的,所以要避免创建不必要的对象
- 如果有一个需要拼接的字符串,那么可以优先考虑使用StringBuffer或者StringBuilder来进行拼接,而不是加号连接符,因为使用加号连接符会创建多余的对象,拼接的字符串越长,加号连接符的性能越低
- 尽量使用基本数据类来代替封装数据类型,int比Integer要更加高效,其它数据类型也是一样
使用静态
- 使用枚举通常会比使用静态常量要消耗两倍以上的内存,在Android开发当中应当尽可能地不使用枚举。
- 如果你并不需要访问一个对象中的某些字段,只是想调用它的某个方法来去完成一项通用的功能,那么可以将这个方法设置成静态方法,这会让调用的速度提升15%-20%,同时也不用为了调用这个方法而去专门创建对象了,这样还满足了上面的一条原则。另外这也是一种好的编程习惯,因为我们可以放心地调用静态方法,而不用担心调用这个方法后是否会改变对象的状态(静态方法内无法访问非静态字段)
对常量使用static final修饰符
使用增强型for循环语法
多使用系统封装好的API,比如:indexOf(),System.arraycopy()
性能优化:尽量使用drawable对象保存图片而不是bitmap
drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png");