在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用函数来释放内存,但也随之带来了内存泄漏的可能,而且每台设备配置不一,分配内存大小也不一样
首先看Android中的ActivityManager,这个类可以得到“设备配置的属性”,"进程信息","任务信息",“服务”,“正在运行的程序”
ActivityManager.MemoryInfo : 系统内存使用情况的信息 ,可以通过getMemoryInfo(ActivityManager.MemoryInfo). 来获得该类对象
ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
ActivityManager.ProcessErrorStateInfo: 错误状态的进程
ActivityManager.RecentTaskInfo:近期用户打开的任务信息
ActivityManager.RunningAppProcessInfo: 正在运行的应用程序进程
ActivityManager.RunningServiceInfo: 正在运行的服务信息
ActivityManager.RunningTaskInfo: 正在运行的任务信息
ActivityManager类中的方法:
public ConfigurationInfo getDeviceConfigurationInfo ()
获得当前设备的配置信息
public int getLargeMemoryClass ()
如果在AndroidManifest.xml中的
getLargeMemoryClass()获取系统分配的最大堆内存,单位为M
ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
int largeMenClass = activityManager.getLargeMemoryClass();//单位为M
public int getLauncherLargeIconDensity ()
ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(info);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("系统剩余内存:"+(info.availMem >> 10)+"k")
.append("\n")
.append("系统是否处于低内存运行:"+info.lowMemory)
.append("\n")
.append("当系统剩余内存低于"+info.threshold+"时就看成低内存运行");
availMem:表示系统剩余内存
hreshold:它表示当系统剩余内存低于多少时就看成低内存运行
public static void getMyMemoryState (ActivityManager.RunningAppProcessInfo outState)
获取系统内存状态信息,并将信息存入参数 outState中,但不是全部信息
public MemoryInfo[] getProcessMemoryInfo (int[] pids)
获取每个进程(ID)使用的内存信息MemoryInfo,一一对应,返回一个内存信息集合MemoryInfo[]
参数: 进程的pid信息集合
int[] pid = {18752};
Debug.MemoryInfo[] infos = activityManager.getProcessMemoryInfo(pid);
StringBuilder stringBuilder = new StringBuilder();
for (Debug.MemoryInfo info : infos){
stringBuilder.append(info.toString()+"\n");
}
dalvikPrivateDirty:The private dirty pages used by dalvik。
dalvikPss: The proportional set size for dalvik.
dalvikSharedDirty: The shared dirty pages used by dalvik.
nativePrivateDirty:The private dirty pages used by the native heap.
nativePss: The proportional set size for the native heap.
nativeSharedDirty: The shared dirty pages used by the native heap.
otherPrivateDirty: The private dirty pages used by everything else.
otherPss: The proportional set size for everything else.
otherSharedDirty: The shared dirty pages used by everything else.
dalvik: 是指dalvik所使用的内存
native:是被native堆使用的内存,应该指使用C\C++在堆上分配的内存。
other: 是指除dalvik和native使用的内存。但是具体是指什么呢?至少包括在C\C++分配的非堆内存,比如分配在栈上的内存。
public List
返回一个处于错误状态的进程列表,如果没有错误状态的进程,返回Null
public List
获得最近开启的任务
参数: maxNum: 返回的最大数量
flags: 返回的类型May be any combination of RECENT_WITH_EXCLUDED and RECENT_IGNORE_UNAVAILABLE.
public List
返回正在运行的应用程序进程列表
public PendingIntent getRunningServiceControlPanel (ComponentName service)
返回一个PendingIntent ,给一个组件名,如果有与这个名字匹配的Service则返回,否则返回Null
public List
获取系统里正在运行的服务, maxNum 为要获取服务的最大数量,一般为20或者50
public List
获取系统里正在运行的任务, maxNum 为要获取服务的最大数量
public void killBackgroundProcesses (String packageName)
立即杀掉给定包名的进程,释放进程占用的资源(内存等) 【只有用户进程可以kill】
adb shell:进入android底层Linux系统命令
ps:查看系统里面进程的命令
adb shell dumpsys meminfo +包名:将得到该包下进程使用的内存的信息
程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的,三种存储策略使用的内存空间主要分别是静态存储区(也称方法区)、堆区和栈区。
静态存储区(方法区):内存在程序编译的时候就已经分配好,这块内存在程序整个运行期间都存在。它主要存放静态数据、全局static数据和常量。
栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
堆区:亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存(Java则依赖垃圾回收器)。
动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。 但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉。
堆和栈的区别:
在函数中(说明是局部变量)定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。
当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。
堆内存用于存放所有由new创建的对象(内容包括该对象其中的所有成员变量)和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。
在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。
堆是不连续的内存区域(因为系统是用链表来存储空闲内存地址,自然不是连续的),堆大小受限于计算机系统中有效的虚拟内存(32bit系统理论上是4G),所以堆的空间比较灵活,比较大。
栈是一块连续的内存区域,大小是操作系统预定好的,windows下栈大小是2M(也有是1M,在编译时确定,VC中可设置)。
对于堆,频繁的new/delete会造成大量内存碎片,使程序效率降低。对于栈,它是先进后出的队列,进出一一对应,不产生碎片,运行效率稳定高。
在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用函数来释放内存,但它只能回收无用并且不再被其它对象引用的那些对象所占用的空间。
Java的内存垃圾回收机制是从程序的主要运行对象(如静态对象/寄存器/栈上指向的堆内存对象等)开始检查引用链,当遍历一遍后得到上述这些无法回收的对象和他们所引用的对象链,组成无法回收的对象集合,而其他孤立对象(集)就作为垃圾回收。
GC为了能够正确释放对象,必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。
在Java中,这些无用的对象都由GC负责回收,因此程序员不需要考虑这部分的内存泄露。
虽然,我们有几个函数可以访问GC,例如运行GC的函数System.gc(),但是根据Java语言规范定义,该函数不保证JVM的垃圾收集器一定会执行。因为不同的JVM实现者可能使用不同的算法管理GC。
通常GC的线程的优先级别较低。JVM调用GC的策略也有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但通常来说,我们不需要关心这些。
但是我们仍然可以去监听系统的GC过程,以此来分析我们应用程序当前的内存状态。那么怎样才能去监听系统的GC过程呢?
其实非常简单,系统每进行一次GC操作时,都会在LogCat中打印一条日志,我们只要去分析这条日志就可以了,日志的基本格式如下所示:
首先第一部分GC_Reason,这个是触发这次GC操作的
D/dalvikvm: , ,
在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术。
软/弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
利用这个队列可以得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软/弱引用。
假设我们的应用会用到大量的默认图片,比如应用中有默认的头像,默认游戏图标等等,这些图片很多地方会用到。
如果每次都去读取图片,由于读取文件需要硬件操作,速度较慢,会导致性能较低。所以我们考虑将图片缓存起来,需要的时候直接从内存中读取。但是,由于图片占用内存空间比较大,缓存很多图片需要很多的内存,就可能比较容易发生OutOfMemory异常。这时,我们可以考虑使用软/弱引用技术来避免这个问题发生。
相关内存泄漏情况
比如经常进出某个activity,在该activity需要处理一个耗时的操作,一般耗时工作都交给一个线程来处理,代码如下:
public class TwoActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_two);
ThreadClass threadClass = new ThreadClass();
threadClass.start();
}
private class ThreadClass extends Thread {
@Override
public void run() {
super.run();
while (true){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
上面代码存在activity内存泄漏,因为是ThreadClass是一个匿名内部类,正常来说该activity退出时就应该被回收了,但由于ThreadClass持有该activity引用,内存回收器(CG)不能回收掉,所以反复进出该activity很容量造成内存泄漏,下面一个内存分析工具(Android Device Monitor)来分析这种现象:
没进入该activity之前,内存情况
反复进入上面activity内存情况
那怎么处理呢? 可以将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例
public class ThreeActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_three);
ThreadClass threadClass = new ThreadClass();
threadClass.start();
}
public static class ThreadClass extends Thread {
@Override
public void run() {
super.run();
while (true){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
这样虽然避免了Activity内存泄露,但是这个线程却发生了内存泄露。在Java中线程是垃圾回收机制的根源,也就是说,在运行系统中DVM虚拟机总会使硬件持有所有运行状态的进程的引用,结果导致处于运行状态的线程将永远不会被回收。因此,你必须为你的后台线程实现销毁逻辑!下面是一种解决办法:
public class ThreeActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_three);
ThreadClass threadClass = new ThreadClass();
threadClass.start();
}
public static class ThreadClass extends Thread {
private boolean mRunning = false;
@Override
public void run() {
super.run();
mRunning = true;
while (mRunning){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void close() {
mRunning = false;
}
}
}
我们在Activity退出时,可以在 onDestroy()方法中显示调用mThread.close();以此来结束该线程,这就避免了线程的内存泄漏问题
Android的单例模式非常受开发者的喜爱,不过使用的不恰当的话也会造成内存泄漏。
因为单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。
public class Single {
private static Single instance;
private Context mContext;
public Single(Context context){
this.mContext = context;
}
public static Single getInstance(Context context){
if (instance == null){
instance = new Single(context);
}
return instance;
}
}
这里需要传入的是上下文Context,所以这个Context的生命周期的长短非常重要
1)、传入的是Application的Context:这将没有任何问题,因为单例的生命周期和Application的一样长;
2)、传入的是Activity的Context:当这个Context所对应的Activity退出时,由于该Context和Activity的生命周期一样长(Activity间接继承于Context),所以当前Activity退出时它的内存并不会被回收,因为单例对象持有该Activity的引用。
Handler的使用造成的内存泄漏问题应该说最为常见了,平时在处理网络任务或者封装一些请求回调等api都应该会借助Handler来处理,对于Handler的使用代码编写一不规范即有可能造成内存泄漏,如下示例:
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
mImageView.setImageBitmap(mBitmap);
}
}
上面是一段简单的Handler的使用。当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法:
handler.postDelayed(this, 2000); //每两秒实现一次的定时器操作
该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。
static class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
mImageView.setImageBitmap(mBitmap);
}
}
但其实没这么简单。使用了以上代码之后,你会发现,由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference):
static class MyHandler extends Handler {
WeakReference mActivityReference;
MyHandler(Activity activity) {
mActivityReference= new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
final Activity activity = mActivityReference.get();
if (activity != null) {
mImageView.setImageBitmap(mBitmap);
}
}
}
将代码改为以上形式之后,就算完成了。
资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。因为有些资源性对象,比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null.在我们的程序退出时一定要确保我们的资源性对象已经关闭。
程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险。
Cursor cursor = getContentResolver().query(uri...);
if (cursor.moveToNext()) {
... ...
}
修正示例代码:
Cursor cursor = null;
try {
cursor = getContentResolver().query(uri...);
if (cursor != null &&cursor.moveToNext()) {
... ...
}
} finally {
if (cursor != null) {
try {
cursor.close();
} catch (Exception e) {
//ignore this
}
}
}
在ListView的BaseAdapter中提供了getView()方法中使用缓存机制,这个平时开发中用都比较多,相信大家都比较熟悉了,就不在总结了
内存抖动是因为大量的对象被创建又在短时间内马上被释放
瞬间产生大量的对象会严重占用Young Generation的内存区域,当达到阀值,剩余空间不够的时候,也会触发GC。即使每次分配的对象占用了很少的内存,但是他们叠加在一起会增加Heap的压力,从而触发更多其他类型的GC。这个操作有可能会影响到帧率,并使得用户感知到性能问题
解决上面的问题有简洁直观方法,如果你在Memory Monitor里面查看到短时间发生了多次内存的涨跌,这意味着很有可能发生了内存抖动。
同时我们还可以通过Allocation Tracker来查看在短时间内,同一个栈中不断进出的相同对象。这是内存抖动的典型信号之一。
当你大致定位问题之后,接下去的问题修复也就显得相对直接简单了。例如,你需要避免在for循环里面分配对象占用内存,需要尝试把对象的创建移到循环体之外,自定义View中的onDraw方法也需要引起注意,每次屏幕发生绘制以及动画执行过程中,onDraw方法都会被调用到,
避免在onDraw方法里面执行复杂的操作,避免创建对象。对于那些无法避免需要创建对象的情况,我们可以考虑对象池模型,通过对象池来解决频繁创建与销毁的问题,但是这里需要注意结束使用之后,需要手动释放对象池中的对象。
Android开发过程中,在 Activity的生命周期里协调耗时任务可能会很困难,你一不小心就会导致内存泄漏问题。下面是一些小提示,能帮助你预防内存泄漏问题的发生:
1、合理使用static:
每一个非静态内部类实例都会持有一个外部类的引用,若该引用是Activity 的引用,那么该Activity在被销毁时将无法被回收。如果你的静态内部类需要一个相关Activity的引用以确保功能能够正常运行,那么你得确保你在对象中使用的是一个Activity的弱引用,否则你的Activity将会发生意外的内存泄漏。但是要注意,当此类在全局多处用到时在这样干,因为static声明变量的生命周期其实是和APP的生命周期一样的,有点类似与Application。如果大量的使用的话,就会占据内存空间不释放,积少成多也会造成内存的不断开销,直至挂掉。static的合理使用一般用来修饰基本数据类型或者轻量级对象,尽量避免修复集合或者大对象,常用作修饰全局配置项、工具类方法、内部类。
2、善用SoftReference/WeakReference/LruCache
Java、Android中有没有这样一种机制呢,当内存吃紧或者GC扫过的情况下,就能及时把一些内存占用给释放掉,从而分配给需要分配的地方。答案是肯定的,java为我们提供了两个解决方案。如果对内存的开销比较关注的APP,可以考虑使用WeakReference,当GC回收扫过这块内存区域时就会回收;如果不是那么关注的话,可以使用SoftReference,它会在内存申请不足的情况下自动释放,同样也能解决OOM问题。同时Android自3.0以后也推出了LruCache类,使用LRU算法就释放内存,一样的能解决OOM,如果兼容3.0一下的版本,请导入v4包。关于第二条的无关引用的问题,我们传参可以考虑使用WeakReference包装一下。
3、谨慎handler
在处理异步操作的时候,handler + thread是个不错的选择。但是相信在使用handler的时候,大家都会遇到警告的情形,这个就是lint为开发者的提醒。handler运行于UI线程,不断处理来自MessageQueue的消息,如果handler还有消息需要处理但是Activity页面已经结束的情况下,Activity的引用其实并不会被回收,这就造成了内存泄漏。解决方案,一是在Activity的onDestroy方法中调handler.removeCallbacksAndMessages(null);取消所有的消息的处理,包括待处理的消息;二是声明handler的内部类为static。
4、不要总想着Java 的垃圾回收机制会帮你解决所有内存回收问题
就像上面的示例,我们以为垃圾回收机制会帮我们将不需要使用的内存回收,例如:我们需要结束一个Activity,那么它的实例和相关的线程都该被回收。但现实并不会像我们剧本那样走。Java线程会一直存活,直到他们都被显式关闭,抑或是其进程被Android系统杀死。所以,为你的后台线程实现销毁逻辑是你在使用线程时必须时刻铭记的细节,此外,你在设计销毁逻辑时要根据Activity的生命周期去设计,避免出现Bug。
5、使用更优的数据结构
参考博文:http://m.blog.csdn.net/article/details?id=51579080