百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

Android技术分享|Android 中部分内存泄漏示例及解决方案

suiw9 2024-11-05 12:35 33 浏览 0 评论

简单介绍内存泄漏&内存抖动

内存泄漏:

Memory leak, 是一种资源泄漏,主因是计算机程序对存储器配置管理失当,失去对一段已分配内存空间的控制,造成程序继续占用已经不再使用的内存空间,或是存储器所存储之对象无法透过执行代码而访问,令内存资源空耗。

简单来说,内存泄漏是指无法正确回收已经不再使用的内存。


举例:

请注意以下的例子是虚构的

在此例中的应用程序是一个简单软件的一小部分,用来控制电梯的运作。<br>此部分软件当乘客在电梯内按下一楼层的按钮时运行。

当按下按钮时:

要求使用存储器,用作记住目的楼层


把目的楼层的数字储存到存储器中

电梯是否已到达目的楼层?

如是,没有任何事需要做:程序完成

否则:

等待直至电梯停止

到达指定楼层

释放刚才用作记住目的楼层的存储器


此程序有一处会造成存储器泄漏:如果在电梯所在楼层按下该层的按钮(即上述程序的第4步),程序将触发判断条件而结束运行,但存储器仍一直被占用而没有被释放。这种情况发生得越多,泄漏的存储器也越多。


这个小错误不会造成即时影响。因为人不会经常在电梯所在楼层按下同一层的按钮。而且在通常情况下,电梯应有足够的存储器以应付上百次、上千次类似的情况。不过,电梯最后仍有可能消耗完所有存储器。这可能需要数个月或是数年,所以在简单的测试下这个问题不会被发现。


而这个例子导致的后果会是不那么令人愉快。至少,电梯不会再理会前往其他楼层的要求。更严重的是,如果程序需要存储器去开启电梯门,那可能有人被困电梯内,因为电梯没有足够的存储器去开启电梯门。


存储器泄漏只会在程序运行的时间内持续。例如:关闭电梯的电源时,程序终止运行。当电源再度开启,程序会再次运行而存储器会重置,而这种缓慢的泄漏则会从头开始再次发生。


内存抖动

源自Android文档中的Memory churn一词,中文翻译为内存抖动。

指快速频繁的创建对象从而产生的性能问题。

引用Android文档原文:

垃圾回收事件通常不会影响应用的性能。不过,如果在短时间内发生许多垃圾回收事件,就可能会快速耗尽帧时间。系统花在垃圾回收上的时间越多,能够花在呈现或流式传输音频等其他任务上的时间就越少。


通常,“内存抖动”可能会导致出现大量的垃圾回收事件。实际上,内存抖动可以说明在给定时间内出现的已分配临时对象的数量。


例如,您可以在 `for` 循环中分配多个临时对象。或者,您也可以在视图的 `onDraw()` 函数中创建新的 `Paint` 或 `Bitmap` 对象。在这两种情况下,应用都会快速创建大量对象。这些操作可以快速消耗新生代 (young generation) 区域中的所有可用内存,从而迫使垃圾回收事件发生。


内存泄漏(Memory leak)的产生和避免方式

Java内存泄漏的根本原因是长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏。

尽管短生命周期对象已经不再需要,但因为长生命周期依旧持有它的引用,故不能被回收而导致内存泄漏。


几种引起内存泄漏的问题:

静态集合类引起的内存泄漏

HashMapArrayList等集合以静态形式声明时,这些静态对象的生命周期与应用程序一致。他们所引用的对象也无法被释放,因为它们也被集合引用着。

private static HashMap<String, Object> a = new HashMap();
public static void main(String args[]) {
for (int i = 0; i < 1000; i++) {
Object tO = new Object();
a.put("0", tO);
tO = null;
}
}


如果仅仅释放引用本身(tO = null),ArrayList依然在引用该对象,GC无法回收。


监听器

在Java应用中,通常会用到很多监听器,一般通过addXXXXListener()实现。但释放对象时通常会忘记删除监听器,从而增加内存泄漏的风险。


各种连接

如数据库连接、网络连接(Socket)和I/O连接。忘记显式调用close()方法引起的内存泄漏。


内部类和外部模块的引用

内部类的引用是很容易被遗忘的一种,一旦没有释放可能会导致一系列后续对象无法释放。此外还要小心外部模块不经意的引用,内部类是否提供相应的操作去除外部引用。


单例模式

由于单例的静态特性,使其生命周期与应用的生命周期一样长,一旦使用不恰当极易造成内存泄漏。如果单利持有外部引用,需要注意提供释放方式,否则当外部对象无法被正常回收时,会进而导致内存泄漏。


常见的内存泄漏处理方式:

集合类泄漏

如集合的使用范围超过逻辑代码的范围,需要格外注意删除机制是否完善可靠。比如由静态属性static指向的集合。


单利泄漏

以下为简单逻辑代码,只为举例说明内存泄漏问题,不保证单利模式的可靠性。

public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance == null) {
instance = new AppManager(context);
}
return instance;
}
}


AppManager创建时需要传入一个Context,这个Context的生命周期长短至关重要。

1. 如果传入的是ApplicationContext,因为Application的生命周期等同于应用的生命周期,所以没有任何问题。

2. 如果传入的是ActivityContext,则需要考虑这个Activity是否在整个生命周期都不会被回收了,如果不是,则会造成内存泄漏。


非静态内部类创建静态实例造成的内存泄漏

public class MyActivity extends AppCompatActivity {
private static MyInnerClass mInnerClass = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
if (mInnerClass == null) {
mInnerClass = new MyInnerClass();
}
}
class MyInnerClass {
...
}
}


内部类持有外部类引用,而static声明的对象声明周期通常会比Activity长。即使关闭这个页面,由于mInnerClass为静态的,并且持有MyActivity的引用,导致无法回收此页面从而引起内存泄漏。

应该将该内部类单独封装为一个单例来使用。


匿名内部类/异步线程

public class MyActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
new Thread(new Runnable() {
@Override
public void run() {
...
}
}).start();
}
}


Runnable都使用了匿名内部类,将持有MyActivity的引用。如果任务在Activity销毁前未完成,将导致Activity的内存无法被回收,从而造成内存泄漏。

解决方法:将Runnable独立出来或使用静态内部类,可以避免因持有外部对象导致的内存泄漏。


Handler造成的内存泄漏

public class SampleActivity extends AppCompatActivity {
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
...
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
...
}
}, 300000);
finish();
}
}


Handler属于TLS(Thread Local Storage)变量,生命周期与Activity是不一致的,容易导致持有的对象无法正确被释放

当Android应用程序启动时,该应用程序的主线程会自动创建一个Looper对象和与之关联的MessageQueue。

当主线程中实例化一个Handler对象后,它就会自动与主线程Looper的MessageQueue关联起来。所有发送到MessageQueue的Messag都会持有Handler的引用,所以Looper会据此回调Handle的handleMessage()方法来处理消息。只要MessageQueue中有未处理的Message,Looper就会不断的从中取出并交给Handler处理。

另外,主线程的Looper对象会伴随该应用程序的整个生命周期。

在Java中,非静态内部类和匿名类内部类都会潜在持有它们所属的外部类的引用,但是静态内部类却不会。

当该 Activity 被finish()掉时,延迟执行任务的 Message 还会继续存在于主线程中,它持有该 Activity 的 Handler 引用,所以此时finish()掉的 Activity 就不会被回收了从而造成内存泄漏(因 Handler 为非静态内部类,它会持有外部类的引用,在这里就是指 SampleActivity)。

解决方法:在 Activity 中避免使用非静态内部类,比如上面我们将 Handler 声明为静态的,则其存活期跟 Activity 的生命周期就无关了。同时通过弱引用的方式引入 Activity,避免直接将 Activity 作为 context 传进去,见如下代码:

public class SampleActivity extends AppCompatActivity {
private static class MyHandler extends Handler {
private final WeakReference<SampleActivity> mActivity;
public MyHandler(SampleActivity activity) {
mActivity = new WeakReference<SampleActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
SampleActivity activity = mActivity.get();
if (activity != null) {
...
}
}
}
private final MyHandler mHandler = new MyHandler(this);
private static final Runnable mRunnable = new Runnable() {
@Override
public void run() { ... }
}
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mHandler.postDelayed(mRunnable, 300000);
finish();
}
}


避免不必要的静态成员变量

对于BroadcastReceiver、ContentObserver、File、Cursor、Stream、Bitmap等资源的使用,应在Activity销毁前及时关闭或注销。

不使用WebView对象时,应调用`destroy()`方法销毁。

相关推荐

俄罗斯的 HTTPS 也要被废了?(俄罗斯网站关闭)

发布该推文的ScottHelme是一名黑客,SecurityHeaders和ReportUri的创始人、Pluralsight作者、BBC常驻黑客。他表示,CAs现在似乎正在停止为俄罗斯域名颁发...

如何强制所有流量使用 HTTPS一网上用户

如何强制所有流量使用HTTPS一网上用户使用.htaccess强制流量到https的最常见方法可能是使用.htaccess重定向请求。.htaccess是一个简单的文本文件,简称为“.h...

https和http的区别(https和http有何区别)

“HTTPS和HTTP都是数据传输的应用层协议,区别在于HTTPS比HTTP安全”。区别在哪里,我们接着往下看:...

快码住!带你十分钟搞懂HTTP与HTTPS协议及请求的区别

什么是协议?网络协议是计算机之间为了实现网络通信从而达成的一种“约定”或“规则”,正是因为这个“规则”的存在,不同厂商的生产设备、及不同操作系统组成的计算机之间,才可以实现通信。简单来说,计算机与网络...

简述HTTPS工作原理(简述https原理,以及与http的区别)

https是在http协议的基础上加了一层SSL(由网景公司开发),加密由ssl实现,它的目的是为用户提供对网站服务器的身份认证(需要CA),以至于保护交换数据的隐私和完整性,原理如图示。1、客户端发...

21、HTTPS 有几次握手和挥手?HTTPS 的原理什么是(高薪 常问)

HTTPS是3次握手和4次挥手,和HTTP是一样的。HTTPS的原理...

一次安全可靠的通信——HTTPS原理

为什么HTTPS协议就比HTTP安全呢?一次安全可靠的通信应该包含什么东西呢,这篇文章我会尝试讲清楚这些细节。Alice与Bob的通信...

为什么有的网站没有使用https(为什么有的网站点不开)

有的网站没有使用HTTPS的原因可能涉及多个方面,以下是.com、.top域名的一些见解:服务器性能限制:HTTPS使用公钥加密和私钥解密技术,这要求服务器具备足够的计算能力来处理加解密操作。如果服务...

HTTPS是什么?加密原理和证书。SSL/TLS握手过程

秘钥的产生过程非对称加密...

图解HTTPS「转」(图解http 完整版 彩色版 pdf)

我们都知道HTTPS能够加密信息,以免敏感信息被第三方获取。所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用HTTPS协议。...

HTTP 和 HTTPS 有何不同?一文带你全面了解

随着互联网时代的高速发展,Web服务器和客户端之间的安全通信需求也越来越高。HTTP和HTTPS是两种广泛使用的Web通信协议。本文将介绍HTTP和HTTPS的区别,并探讨为什么HTTPS已成为We...

HTTP与HTTPS的区别,详细介绍(http与https有什么区别)

HTTP与HTTPS介绍超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的...

一文让你轻松掌握 HTTPS(https详解)

一文让你轻松掌握HTTPS原文作者:UC国际研发泽原写在最前:欢迎你来到“UC国际技术”公众号,我们将为大家提供与客户端、服务端、算法、测试、数据、前端等相关的高质量技术文章,不限于原创与翻译。...

如何在Spring Boot应用程序上启用HTTPS?

HTTPS是HTTP的安全版本,旨在提供传输层安全性(TLS)[安全套接字层(SSL)的后继产品],这是地址栏中的挂锁图标,用于在Web服务器和浏览器之间建立加密连接。HTTPS加密每个数据包以安全方...

一文彻底搞明白Http以及Https(http0)

早期以信息发布为主的Web1.0时代,HTTP已可以满足绝大部分需要。证书费用、服务器的计算资源都比较昂贵,作为HTTP安全扩展的HTTPS,通常只应用在登录、交易等少数环境中。但随着越来越多的重要...

取消回复欢迎 发表评论: