Android-内存泄漏问题多多,怎么优化?(1)

虚拟机并不是永远地要求对象的年龄必须达到了-XX:MaxTenuringThreshold才能晋升到老年代,如果在Survivor空间中相同年龄所有对象的大小的总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象就可以直接进入老年代,无须等到-XX:MaxTenuringThreshold中要求的年龄。

  • 空间分配担保

在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的;如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那么这时就需要进行一次Full GC。

1.3 JVM的类加载机制

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)验证(Verification)准备(Preparation)解析(Resolution)初始化(Initialization)使用(Using)卸载(Unloading)7个阶段,其中,验证、准备、解析3个部分统称为连接(Linking)。类的生命周期如下图所示:

在虚拟机中,我们常说的类的加载过程是指加载(Loading)验证(Verification)准备(Preparation)解析(Resolution)初始化(Initialization)这五个阶段,它们的具体作用为:

  • 加载

加载过程是将二进制字节流(Class字节码文件)通过类加载器加载到内存并实例化Class对象的过程(加载到方法区内)。这个过程独立于虚拟机之外,并且二进制流可以从不同的环境内获取或者由其他文件生成。

  • 验证

验证Class文件的字节流是否符合虚拟机的要求,以免造成虚拟机出现异常。包括:文件格式验证元数据验证字节码验证符号引用验证

  • 准备

为静态变量(被final关键字修饰)分配内存空间、赋值和设置类变量初始化(自动初始化)。

  • 解析

将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄以及调用点限定符7类符号引用进行。

  • 初始化

执行类构造器<clinit>方法的过程,变量的声明初始化就在这个阶段进行。

虚拟机类加载的时机?1)遇到new、getstatic、putstatic或者invokestatic 这四条字节码指令的时候,且该类没有进行初始化则进行该类的初始化;
2)使用反射机制的时候;
3)初始化类的父类;
4)初始化虚拟机要执行主类;
5)使用动态语言特性的时候;总之,当对一个类进行主动引用的时候就会进行初始化操作,而进行被动引用的时候便不会触发类初始化操作,比如通过子类引用父类静态字段时子类不会被初始化。

常见内存泄漏与优化
2.1 内存泄漏

当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被垃圾收集器回收,结果它们就一直存在于内存中(通常指Java堆内存),占用有效空间,永远无法被删除。随着内存不断泄漏,堆中的可用空间就不断变小,这意味着为了执行常用的程序,垃圾清理需要启动的次数越来越多,非常严重的话会直接造成应用程序报OOM异常。优化/避免内存泄漏原则:

  • 涉及到使用Context时,尽量使用Application的Context;
  • 对于非静态内部类、匿名内部类,需将其独立出来或者改为静态类;
  • 在静态内部类中持有外部类(非静态)的对象引用,使用弱引用来处理;
  • 不再使用的资源(对象),需显示释放资源(对象置为null),如果是集合需要清空;
  • 保持对对象生命周期的敏感,尤其注意单例、静态对象、全局性集合等的生命周期;
2.2 常见内存泄漏与优化

(1) 单例造成的内存泄漏

  • 案例

/** 工具类,单例模式

  • @Auther: Jiangdg
  • @Date: 2019/10/8 17:23
  • @Description:
    */
    public class CommonUtils {
    private static CommonUtils instance;
    private Context mCtx;

private CommonUtils(Context context){
this.mCtx = context;
}

public static CommonUtils getInstance(Context context) {
if(instance == null) {
instance = new CommonUtils(context);
}
return instance;
}
}

/**使用单例模式时造成内存泄漏
*

  • @Auther: Jiangdg
  • @Date: 2019/10/8 17:24
  • @Description:
    */
    public class SingleActivity extends AppCompatActivity {
    private CommonUtils mUtils;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

mUtils = CommonUtils.getInstance(this);
}
}

  • 分析与优化

在上述示例中,当SingleActivity实例化Commontils对象完毕后,Commontils将持有SingleActivity对象的引用,而由于单例模式的静态特性,Commontils对象的生命周期将于应用进程的一致,这就会导致在应用未退出的情况下,如果SingleActivity对象已经不再需要了,而Commontils对象该持有该对象的引用就会使得GC无法对其进行正常回收,从而导致了内存泄漏。优化:对于需要传入Context参数的情况,尽量使用Application的Context,因为它会伴随着应用进程的存在而存在。

public class SingleActivity extends AppCompatActivity {
private CommonUtils mUtils;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 造成内存泄漏
//mUtils = CommonUtils.getInstance(this);

mUtils = CommonUtils.getInstance(this.getApplicationContext());
}
}

(2) Handler造成的内存泄漏

  • 案例

/** 使用Handler造成内存泄漏

  • @Auther: Jiangdg
  • @Date: 2019/10/8 17:55
  • @Description:
    */
    public class HandlerActivity extends AppCompatActivity {

// 匿名内部类
private Handler mUIHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

new Thread(new Runnable() {
@Override
public void run() {
// 处理耗时任务
// …

mUIHandler.sendEmptyMessage(0x00);
}
});
}
}

  • 分析与优化

在Android应用启动时,应用程序的主线程会为其自动创建一个Looper对象和与之关联的MessageQueue,当主线程实例化一个Handler对象后,它就自动与主线程的MessageQueue关联起来,所有发送到MessageQueueMessage(消息)都会持有Handler的引用。由于主线程的Looper对象会随着应用进程一直存在的且Java类中的非静态内部类和匿名内部类默认持有外部类的引用,假如HandlerActivity提前出栈不使用了,但MessageQueue中仍然还有未处理的MessageLooper就会不断地从MessageQueue取出消息交给Handler来处理,就会导致Handler对象一直持有HandlerActivity对象的引用,从而出现HandlerActivity对象无法被GC正常回收,进而造成内存泄漏。优化:将Handler类独立出来,或者使用静态内部类,因为静态内部类不持有外部类的引用。

public class HandlerActivity extends AppCompatActivity {

// 匿名内部类默认持有HandlerActivity的引用
// 造成内存泄漏
// private Handler mUIHandler = new Handler() {
// @Override
// public void handleMessage(Message msg) {
// super.handleMessage(msg);
// }
// };

// 优化,使用静态内部类
// 假如要持有HandlerActivity,以便在UIHandler中访问其成员变量或成员方法
// 需要使用弱引用处理
private UIHandler mUIHandler;
static class UIHandler extends Handler {
private WeakReference mWfActivity;

public UIHandler(HandlerActivity activity) {
mWfActivity = new WeakReference<>(activity);
}

@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

mUIHandler = new UIHandler(this);
}
}

Java中四种引用关系:

  • 强引用用来描述永远不会被垃圾收集器回收掉的对象,类似"Object obj = new Object"
  • 软引用用来描述一些还有用但并非必须的对象,由SoftReference类实现。被软引用关联着的对象会在系统将要发生OOM之前,垃圾收集器才会回收掉这些对象。
  • 弱引用用来描述非必须的对象,比软引用更弱一些,由WeakReference类实现。被弱引用的对象只能生产到下一次垃圾收集发生之前,无论当前内存是否足够。
  • 虚引用最弱的引种引用关系,由PhantomReference类实现。一个对象是否有虚引用的存在,完全不会对其生存时间产生影响,也无法通过虚引用来获取该对象实例。为一个对象设置虚引用关联的唯一目的是能在这个对象被垃圾收集器回收时收到一个系统通知

(3) 线程(非静态内部类或匿名内部类)造成的内存泄漏

  • 案例

/** 使用线程造成的内存泄漏

  • @Auther: Jiangdg
  • @Date: 2019/10/9 10:04
  • @Description:
    */
    public class ThreadActivity extends AppCompatActivity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 开启一个子线程
new Thread(new MyRunnable()).start();
// 开启一个异步任务
new MyAsyncTask(this).execute();
}

class MyRunnable implements Runnable {

@Override
public void run() {

}
}

class MyAsyncTask extends AsyncTask {
private Context mCtx;

public MyAsyncTask(Context context) {
this.mCtx = context;
}

@Override
protected Object doInBackground(Object[] objects) {
return null;
}
}
}

  • 分析与优化

在之前的分析中可知,Java类中的非静态内部类和匿名内部类默认持有外部类的引用。对于上述示例中的MyRunnableMyAsyncTask来说,它们是一个非静态内部类,将默认持有ThreadActivity对象的引用。假如子线程的任务在ThreadActivity销毁之前还未完成,就会导致ThreadActivity无法被GC正常回收,造成内存泄漏。优化:将MyRunnable和MyAsyncTask独立出来,或使用静态内部类,因为静态内部类不持有外部类的引用。

public class ThreadActivity extends AppCompatActivity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 开启一个子线程
new Thread(new MyRunnable()).start();
// 开启一个异步任务
// 优化:使用Application的Context
new MyAsyncTask(this.getApplicationContext()).execute();
}

// 优化:使用静态内部类
static class MyRunnable implements Runnable {

@Override
public void run() {

}
}

// 优化:使用静态内部类
// 如果需要传入Context,使用Application的Context
static class MyAsyncTask extends AsyncTask {
private Context mCtx;

public MyAsyncTask(Context context) {
this.mCtx = context;
}

@Override
protected Object doInBackground(Object[] objects) {
return null;
}
}
}

(4) 静态实例造成的内存泄漏

  • 案例

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

  • @Auther: Jiangdg
  • @Date: 2019/10/9 10:43
  • @Description:
    */
    public class StaticInstanceActivity extends AppCompatActivity {
    private static SomeResources mSomeResources;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

if(mSomeResources == null) {
mSomeResources = new SomeResources(this);
}
}

class SomeResources {
private Context mCtx;

public SomeResources(Context context) {
this.mCtx = context;
}
}
}

  • 分析与优化

在上述案例中,演示了防止StaticInstanceActivity重建,比如横竖屏切换,导致反复创建SomeResources实例的问题,这里使用了static修饰关键字将SomeResources实例声明了静态实例,以确保该实例始终存在的是同一个,且它的生命周期与应用相同。然而,由于SomeResources是一个非静态内部类,其对象默认持有外部类StaticInstanceActivity的引用,就会导SomeResources的对象一直持有该引用,造成内存泄漏。优化:使用单例模式实现SomeResources,或者将其改成静态内部类。如果需要传入Context参数,必须使用Application的Context。

public class StaticInstanceActivity extends AppCompatActivity {
private static SomeResources mSomeResources;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

if(mSomeResources == null) {
// 优化,使用Application的Context
mSomeResources = new SomeResources(this.getApplicationContext());
}
}

// 优化:使用静态内部类
static class SomeResources {
private Context mCtx;

public SomeResources(Context context) {
this.mCtx = context;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

这里我特地整理了一份《Android开发核心知识点笔记》,里面就包含了自定义View相关的内容

除了这份笔记,还给大家分享 Android学习PDF+架构视频+面试文档+源码笔记,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料这几块的内容。非常适合近期有面试和想在技术道路上继续精进的朋友。

分享上面这些资源,希望可以帮助到大家提升进阶,如果你觉得还算有用的话,不妨把它们推荐给你的朋友~

喜欢本文的话,给我点个小赞、评论区留言或者转发支持一下呗~

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

fb0-1712202233089)]

除了这份笔记,还给大家分享 Android学习PDF+架构视频+面试文档+源码笔记,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料这几块的内容。非常适合近期有面试和想在技术道路上继续精进的朋友。

[外链图片转存中…(img-iazFTfNZ-1712202233089)]

分享上面这些资源,希望可以帮助到大家提升进阶,如果你觉得还算有用的话,不妨把它们推荐给你的朋友~

喜欢本文的话,给我点个小赞、评论区留言或者转发支持一下呗~

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值