这种内存条降频使用还有没有使用价值

分析Android内存泄漏的几种可能
投稿:daisy
字体:[ ] 类型:转载 时间:
Java内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。本文详细罗列了Android内存泄漏的八种可能,有需要的可以参考下。
内存泄漏简单地说就是申请了一块内存空间,使用完毕后没有释放掉。它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃。由程序申请的一块内存,且没有任何一个指针指向它,那么这块内存就泄露了。
从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。
Java是垃圾回收语言的一种,其优点是开发者无需特意管理内存分配,降低了应用由于局部故障(segmentation fault)导致崩溃,同时防止未释放的内存把堆栈(heap)挤爆的可能,所以写出来的代码更为安全。
不幸的是,在Java中仍存在很多容易导致内存泄漏的逻辑可能(logical leak)。如果不小心,你的Android应用很容易浪费掉未释放的内存,最终导致内存用光的错误抛出(out-of-memory,OOM)
一般内存泄漏(traditional memory leak)的原因是:当该对象的所有引用都已经释放了,对象仍未被释放。(译者注:Cursor忘记关闭等)
逻辑内存泄漏(logical memory leak)的原因是:当应用不再需要这个对象,当仍未释放该对象的所有引用。
如果持有对象的强引用,垃圾回收器是无法在内存中回收这个对象。
在Android开发中,最容易引发的内存泄漏问题的是Context。比如Activity的Context,就包含大量的内存引用,例如View Hierarchies和其他资源。一旦泄漏了Context,也意味泄漏它指向的所有对象。Android机器内存有限,太多的内存泄漏容易导致OOM。
检测逻辑内存泄漏需要主观判断,特别是对象的生命周期并不清晰。幸运的是,Activity有着明确的生命周期,很容易发现泄漏的原因。Activity.onDestroy()被视为Activity生命的结束,程序上来看,它应该被销毁了,或者Android系统需要回收这些内存(译者注:当内存不够时,Android会回收看不见的Activity)。
如果这个方法执行完,在堆栈中仍存在持有该Activity的强引用,垃圾回收器就无法把它标记成已回收的内存,而我们本来目的就是要回收它!
结果就是Activity存活在它的生命周期之外。
Activity是重量级对象,应该让Android系统来处理它。然而,逻辑内存泄漏总是在不经意间发生。(译者注:曾经试过一个Activity导致20M内存泄漏)。在Android中,导致潜在内存泄漏的陷阱不外乎两种:
全局进程(process-global)的static变量。这个无视应用的状态,持有Activity的强引用的怪物。
活在Activity生命周期之外的线程。没有清空对Activity的强引用。
检查一下你有没有遇到下列的情况。
Static Activities
在类中定义了静态Activity变量,把当前运行的Activity实例赋值于这个静态变量。
如果这个静态变量在Activity生命周期结束后没有清空,就导致内存泄漏。因为static变量是贯穿这个应用的生命周期的,所以被泄漏的Activity就会一直存在于应用的进程中,不会被垃圾回收器回收。
void setStaticActivity() {
activity =
View saButton = findViewById(R.id.sa_button);
saButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
setStaticActivity();
nextActivity();
Memory Leak 1 – Static Activity
Static Views
类似的情况会发生在单例模式中,如果Activity经常被用到,那么在内存中保存一个实例是很实用的。正如之前所述,强制延长Activity的生命周期是相当危险而且不必要的,无论如何都不能这样做。
特殊情况:如果一个View初始化耗费大量资源,而且在一个Activity生命周期内保持不变,那可以把它变成static,加载到视图树上(View Hierachy),像这样,当Activity被销毁时,应当释放资源。(译者注:示例代码中并没有释放内存,把这个static view置null即可,但是还是不建议用这个static view的方法)
void setStaticView() {
view = findViewById(R.id.sv_button);
View svButton = findViewById(R.id.sv_button);
svButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
setStaticView();
nextActivity();
Memory Leak 2 – Static View
Inner Classes
继续,假设Activity中有个内部类,这样做可以提高可读性和封装性。将如我们创建一个内部类,而且持有一个静态变量的引用,恭喜,内存泄漏就离你不远了(译者注:销毁的时候置空,嗯)。
private static O
void createInnerClass() {
class InnerClass {
inner = new InnerClass();
View icButton = findViewById(R.id.ic_button);
icButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
createInnerClass();
nextActivity();
Memory Leak 3 – Inner Class
内部类的优势之一就是可以访问外部类,不幸的是,导致内存泄漏的原因,就是内部类持有外部类实例的强引用。
Anonymous Classes
相似地,匿名类也维护了外部类的引用。所以内存泄漏很容易发生,当你在Activity中定义了匿名的AsyncTsk
。当异步任务在后台执行耗时任务期间,Activity不幸被销毁了(译者注:用户退出,系统回收),这个被AsyncTask持有的Activity实例就不会被垃圾回收器回收,直到异步任务结束。
void startAsyncTask() {
new AsyncTask&Void, Void, Void&() {
@Override protected Void doInBackground(Void... params) {
while(true);
}.execute();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View aicButton = findViewById(R.id.at_button);
aicButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
startAsyncTask();
nextActivity();
Memory Leak 4 – AsyncTask
同样道理,定义匿名的Runnable,用匿名类Handler执行。Runnable内部类会持有外部类的隐式引用,被传递到Handler的消息队列MessageQueue中,在Message消息没有被处理之前,Activity实例不会被销毁了,于是导致内存泄漏。
void createHandler() {
new Handler() {
@Override public void handleMessage(Message message) {
super.handleMessage(message);
}.postDelayed(new Runnable() {
@Override public void run() {
while(true);
}, Long.MAX_VALUE && 1);
View hButton = findViewById(R.id.h_button);
hButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
createHandler();
nextActivity();
Memory Leak 5 – Handler
我们再次通过Thread和TimerTask来展现内存泄漏。
void spawnThread() {
new Thread() {
@Override public void run() {
while(true);
}.start();
View tButton = findViewById(R.id.t_button);
tButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
spawnThread();
nextActivity();
Memory Leak 6 – Thread
只要是匿名类的实例,不管是不是在工作线程,都会持有Activity的引用,导致内存泄漏。
oid scheduleTimer() {
new Timer().schedule(new TimerTask() {
public void run() {
while(true);
}, Long.MAX_VALUE && 1);
View ttButton = findViewById(R.id.tt_button);
ttButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
scheduleTimer();
nextActivity();
Memory Leak 7 – TimerTask
Sensor Manager
最后,通过Context.getSystemService(int name)可以获取系统服务。这些服务工作在各自的进程中,帮助应用处理后台任务,处理硬件交互。如果需要使用这些服务,可以注册监听器,这会导致服务持有了Context的引用,如果在Activity销毁的时候没有注销这些监听器,会导致内存泄漏。
void registerListener() {
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
View smButton = findViewById(R.id.sm_button);
smButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
registerListener();
nextActivity();
Memory Leak 8 – Sensor Manager
看过那么多会导致内存泄漏的例子,容易导致吃光手机的内存使垃圾回收处理更为频发,甚至最坏的情况会导致OOM。垃圾回收的操作是很昂贵的开销,会导致肉眼可见的卡顿。所以,实例化的时候注意持有的引用链,并经常进行内存泄漏检查。本文的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。希望对大家有所帮助。
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具Android 内存泄露简介、典型情景及检测解决
什么是内存泄露?
的垃圾回收采用的是根搜索算法。GC会从根节点(GC Roots)开始对heap进行遍历。到最后,部分没有直接或者间接引用到GC Roots的就是需要回收的垃圾,会被GC回收掉。内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。无用的对象占据着内存空间,导致不能及时回收这个对象所占用的内存。内存泄露积累超过Dalvik堆大小,就会发生OOM(OutOfMemory)。
内存泄露的经典场景
非静态内部类的静态实例
由于内部类默认持有外部类的引用,而静态实例属于类。所以,当外部类被销毁时,内部类仍然持有外部类的引用,致使外部类无法被GC回收。因此造成内存泄露。
private static Leak mL
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
mLeak = new Leak();
class Leak {
错误栗子说明:static关键字修饰mLeak属性,将mLeak存在静态区中,而Leak为内部类,默认持有外部类的引用。当Activity销毁时,mLeak紧紧抱住Activity的大腿深情告白:&MLGB!劳资就是不放你走!&。斗不过mLeak属性的GC,自然不敢回收二手娘们Activity。因此造成内存泄露。
不正确的Handler
错误代码示例:
private MyHandler mMyH
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
mMyHandler = new MyHandler();
mMyHandler.sendMessageDelayed(new Message(), 10 * 1000);
class MyHandler extends Handler {
public void handleMessage(Message msg) {
super.handleMessage(msg);
正确写法如下:
private MyHandler mMyH
static class MyHandler extends Handler {
WeakReference mActivityW
MyHandler(Activity act) {
mActivityWeak = new WeakReference(act);
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (mActivityWeak.get() != null) {
// doSomething
我们知道在handler.sendMessage(msg)时,msg.target会指向handler,msg会插入MessageQueue。此为下面讲解的基础,对这部分不太熟悉的同学可以参考
MyHandler为内部类,默认持有外部类的引用。当Activity销毁时,如果MessageQueue中仍有未处理的消息,那么mMyHandler示例将继续存在。而mMyHandler持有Activity的引用。故Activity无法被GC回收。
static关键字修饰MyHandler类,使MyHandler不持有外部类的引用。使用WeakReference保证当
activity销毁后,不耽误gc回收activity占用的内存空间,同时在没被销毁前,可以引用activity。
管它正确错误都让它正确
通过上面的分析,可以得出结论:Handler造成内存泄露时,是因为MessageQueue中还有待处理的Message,那我们在Activity#onDestroy()中移除所有的消息不完事了嘛。反正Activity都销毁了,MessageQueue中的msg也就什么存在的意义了,可以移除。代码如下:
protected void onDestroy() {
super.onDestroy();
// 移除所有的callback和msg
mMyHandler.removeCallbacksAndMessages(null);
静态变量引起内存泄露
这里以单例模式引起Context泄露为例
public class Singleton {
private static S
private Singleton(Context context){
public static Singleton getInstance(Context context){
if (instance == null){
synchronized (Singleton.class){
if (instance == null){
instance = new Singleton(context);
在调用Singleton#getInstance()方法如果传入了Activity。如果instance没有释放,那么这个Activity将一直存在。因此造成内存泄露。
将new Singleton(context)改为new Singleton(context.getApplicationContext())即可,这样便和传入的Activity没撒关系了。该释放释放、该回家回家。
当使用Cursor、File、Socket等资源时往往都使用了缓冲。在不需要的时候应该及时关闭它们,收回所占的内存空间。 Bitmap不用就recycle掉。注意调用recycle后并不意味着立马recycle,只是告诉虚拟机:小子,该干活咯! ListView一定要使用ConvertView和ViewHolder BraodcastReceiver注册完事,不用时也要反注册
内存泄露的检测
打开DDMS视图 选中Devices下某个具体的应用程序 选中Devices下第二个小绿点Update Heap 不断运行程序并点击Cause GC 关注data Object行、Toal Size列 耍你的APP去吧,如果发现Toal Size越来越大,很可能有内存泄露的发生
MAT(Memory Analyzer Tool)工具
导出.hprof文件
打开DDMS视图 选中Devices下某个具体的应用程序 选中Devices下第二个小绿点Update Heap 点击Cause GC 点击Dump HPROF file 切换到MAT页卡,默认如下图所示
最显眼的就是饼图了,里面列出了每种类型的数据所占大小。和红色箭头所指的Dominator有的一拼,然而这并没有什么卵用。我们的重点在Histogram。没撒说的,点击它。默认图如下
vcq906a4w8rH1eK49tH519O1xKGjPC9wPg0KPHA+PGltZyBhbHQ9"currect" src="/uploadfile/Collfiles/17.png" title="\" />
内存泄露Demo
这里以非静态内部类的静态实例为例,Demo只有两个Activity,MainActivity中只有一个按钮,点击跳转到SecondActivity。
public class SecondActivity extends Activity {
private static Leak mL
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
mLeak = new Leak();
class Leak {
查找内存泄露
启动APP,点击进入SecondActivity,然后按back键返回到MainActivity。打开.hprof文件。查找我们的包名com.dyk.memoryleak。
可以看到,虽然我们结束了SecondActivity,但是SecondActivity仍然存在,内存泄露无疑。
1.右键SecondActivity,选择List Objects&-&with incoming references
结果如下图:
2.右键com.dyk.memoryleak.SecondActivity,选择Path to GC&-&with all references
结果如下图:
可以看到是因为mLeak属性的引用导致SecondActivity无法回收。既然找到了内存泄露的原因,通过上文的介绍,相信改起来难度应该不是很大的。
3.再次进入SecondActivity。由于上次创建的SecondActivity还没有被回收,可以预期到此时应该存在两个SecondActivity实例。
阅读到最后~
(window.slotbydup=window.slotbydup || []).push({
id: '2467140',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467141',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467142',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467143',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467148',
container: s,
size: '1000,90',
display: 'inlay-fix'1989人阅读
github项目解析(14)
转载请标明出处:
(一)什么是内存泄露
Java内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。无用的对象占据着内存空间,使得实际可使用内存变小,形象地说法就是内存泄漏了。
(二)什么是leakcanary
LeakCanary 是一个square开源的在debug版本中检测内存泄漏的java库;
其github地址:
(三)如何使用leakcanary检测内存泄露
在leakcanary的github地址中已经对如何使用做了相关的说明,这里简单介绍一下:
1)在android studio的build.gradle中引用leakcanary
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1'
2)自定义Application,并在onCreate方法中执行以下代码:
public class ExampleApplication extends Application {
public RefWatcher refWatcher = null;
public void onCreate() {
super.onCreate();
refWatcher = LeakCanary.install(this);
3)在activity或者是fragment中的onCreate方法中执行:
RefWatcher refWatcher = ExampleApplication.getRefWatcher(this);
refWatcher.watch(this);
这样我们就可以接受检测该Activity中是否存在内存泄露的问题了(当然也可以检测Fragment)
4)当发现有内存泄漏时,Leakcanary就会弹出一个通知栏消息告诉你哪里存在内存泄露的情况。
5)点击该通知栏消息,显示内存泄露详情;
可以看到LoginPhoneActivity中存在内存泄露的情况,继续往下看,可以定位到Config.currentContext这里,下面就是具体问题具体分析了;
(四)leakcanary的实现原理
在github的实现原理中有这样的一段话:
RefWatcher.watch() creates a KeyedWeakReference to the watched object.
Later, in a background thread, it checks if the reference has been cleared and if not it triggers a GC.
If the reference is still not cleared, it then dumps the heap into a .hprof file stored on the app file system.
HeapAnalyzerService is started in a separate process and HeapAnalyzer parses the heap dump using HAHA.
HeapAnalyzer finds the KeyedWeakReference in the heap dump thanks to a unique reference key and locates the leaking reference.
HeapAnalyzer computes the shortest strong reference path to the GC Roots to determine if there is a leak, and then builds the chain of references causing the leak.
The result is passed back to DisplayLeakService in the app process, and the leak notification is shown.
英文水平不太好不在这里献丑了,不懂得童鞋可以自己理解一下;
另外对github项目,开源项目解析感兴趣的同学可以参考我的:
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:643398次
积分:7695
积分:7695
排名:第2125名
原创:125篇
转载:14篇
评论:446条
文章:15篇
阅读:24321
文章:15篇
阅读:54454
文章:26篇
阅读:176898
文章:31篇
阅读:201592
(2)(6)(5)(2)(12)(18)(15)(11)(8)(11)(14)(4)(3)(2)(4)(5)(2)(5)(1)(2)(1)(4)(3)}

我要回帖

更多关于 内存条降频使用危害 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信