移动学习 AndroidStudio内存优化分析—alloc文件分析

本文针对Android应用中的内存问题进行了深入分析,包括Netty线程内存占用、数据库查询引起的内存泄漏修复,以及通过改进日期格式化类减少内存消耗的方法。

通过Androidstudio分析移动学习不做任何操作时的内存分配及占用情况

1、点击“start Allocation Tracking"5-10秒后再次点击,生成.allc文件:


2、用group by Method的方式看线程 6814:


3、是netty线程在接收read消息时站的内存,主要是newHeapBuffer()构造函数初始化时占用内存,查了一下:

Netty包含三种ByteBuf:

  1. HEAP BUFFERS:
  2. The most used type is the ByteBuf that stores its data in the heap space of the JVM. This is done by storing it in a backing array. This type is fast to allocate and also de-allocate when you re not using a pool. It also offers a way to directly access the backing array, which may make it easier to interact with legacy code .
  3. DIRECT BUFFERS
  4. Another ByteBuf implementation is the direct one. Direct means that it allocates the memory directly, which is outside the heap . You won't see its memory usage in your heap space. You must take this into account when calculating the maximum amount of memory your application will use and how to limit it, as the max heap size won t be enough. Direct buffers on the other side are optimal when it s time to transfer data over a socket. In fact, if you use a nondirect buffer, the JVM will make a copy of your buffer to a direct buffer internally before sending it over the socket.
  5. The down side of direct buffers is that they re more expensive to allocate and de-allocate compared to heap buffers. This is one of the reasons why Netty supports pooling, which makes this problem disappear. Another possible down side can be that you re no longer able to access the data via the backing array, so you ll need to make a copy of the data if it needs to work with legacy code that requires this.
  6. COMPOSITE BUFFERS
  7. The last ByteBuf implementation you may be confronted with is the CompositeByteBuf. This does exactly what its name says; it allows you to compose different ByteBuf instances and provides aview over them. The good thing is you can also add and remove them on-the-fly, so it s kind of like a List.
  8. For example, a message could be composed of two parts: header and body. In a modularized application, the two parts could be produced by different modules and assembled later when the message is sent out. Also, you may use the same body all the time and just change the header. So it would make sense here to not allocate a new buffer every time.
  9. This would be a perfect fit for a CompositeByteBuf asno memory copy will be needed and the same API could be used

4、HeapBuffer是Netty用来进行data存储的主要类,暂时管不了

5、线程 633,发现在mainLoop()中有两处内存泄露:


6、是查询数据库是,cursor的内存泄露,原来的cursor写法:

Cursor cursor = mDatabase.rawQuery("select courseId, courseName, courseTimeId, createTime, leaveEnd, leaveId, leaveReason, leaverId, leaveStart, leaveStatus, orgName, typeId, typeName, userName from QingjiaTable where leaverId = ?", new String[]{userid});
while (cursor.moveToNext()) {
	QingjiaModel qm = new QingjiaModel();
	....
	qm.setUserName(cursor.getString(0...13));
	qmList.add(qm);
}
cursor.close();

7现改为:

Cursor cursor =null;
try{
	cursor = mDatabase.rawQuery("select courseId, courseName, courseTimeId, createTime, leaveEnd, leaveId, leaveReason, leaverId, leaveStart, leaveStatus, orgName, typeId, typeName, userName from QingjiaTable where leaverId = ?", new String[]{userid});
	while (cursor != null && cursor.moveToNext()) {
		QingjiaModel qm = new QingjiaModel();
		qm.setUserName(cursor.getString(0...13));
		qmList.add(qm);
	}
}finally{
	if(cursor != null){
		cursor.close();		
	}
}

8、再次运行程序,完美!

9、看线程6766,有点傻眼,有一半都是关于simpleDateFomate类的方法,挨个看吧,先看run():505



11、主要都是service中的dateFormat和StringBuffer,而StringBuffer用的不多,内存和分配次数也没占多少,全都是DateFormat

12、线程6766中,除run()505外基本都是DateFormat函数分配的内存太多,要人命啊。找了找service中的代码:

class TimerRunnable implements RunFunction {
	@SuppressWarnings("deprecation")
	@Override
	public void timerRun() {// 主业务逻辑
		Log.i(TAG, "智能教学管理系统:后台运行,当前运行时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
		.........

13、每10秒执行一次的时候都会调用一次,而在相关业务逻辑的计算时都是new出来的。好可怕

14、找到了一篇文章:https://www.bbsmax.com/A/kvJ3eb3ndg/ simpleDateFormat是非线程安全的,在多并发时会有问题,照着博主的代码改一下:

public class ThreadLocalDateUtil {
    private static final String date_format = "yyyy-MM-dd HH:mm:ss";
    private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>();  
    public static DateFormat getDateFormat()
    {
        DateFormat df = threadLocal.get();
        if(df==null){
            df = new SimpleDateFormat(date_format);
            threadLocal.set(df);
        }
        return df;
    }  
 
    public static String formatDate(Date date) throws ParseException {
        return getDateFormat().format(date);
    }
 
    public static Date parse(String strDate) throws ParseException {
        return getDateFormat().parse(strDate);
    }
}

15、再次运行程序 ,不完美。

parse()方法经常返回空,造成接下来程序出错。

而且内存开销没有减少,还是40-45M左右,干脆改成这个:

public class ThreadLocalDateUtil {
    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static String format(Date date){
        synchronized(sdf){
            return sdf.format(date);
        }

    }

    public static Date parse(String strDate){
        try{
            synchronized(sdf){
                return sdf.parse(strDate);
            }
        }catch (ParseException e){
            e.printStackTrace();
        }
        return null;
    }
}
16、再次运行程序,完美,不会出现parse()返回空的情况

并且内存瞬间减小10M,稳定在30M左右

而且线程中也没有其他占内存项,就是parse()方法占点内存开销。

比之前好太多,开心:




Android Studio 中查看内存泄漏并不直接依赖于传统的日志文件(如 `logcat` 中的文本日志),而是通过其内置的性能分析工具(如 **Memory Profiler**)来实时监测和分析内存使用情况。不过,如果需要查看与内存泄漏相关的日志信息,可以通过以下几种方式结合工具进行分析: ### 使用 Memory Profiler 查看内存分配 Android Studio 提供了 **Memory Profiler** 工具,它可以帮助开发者识别内存泄漏和优化内存使用。该工具可以显示 Java 堆内存的使用情况,并记录内存分配,帮助定位内存泄漏。 #### 步骤如下: 1. 在 Android Studio 中运行你的应用。 2. 点击底部工具栏的 **Profiler** 标签。 3. 在设备和应用进程中选择你要分析的应用进程。 4. 在 Profiler 界面中,点击 **Memory** 部分。 5. 可以点击 **Dump Java heap** 按钮来获取当前堆内存的快照。这个快照会列出所有存活的对象,并按类名排序,方便你查找可能的内存泄漏对象[^3]。 在堆转储中,如果发现某个 `Activity` 或 `Context` 对象没有被释放,且引用链较长,那么很可能是内存泄漏的线索。此时可以查看 **Reference Tree** 来分析对象的引用路径,判断是哪个地方持有了该对象的引用,导致其无法被回收[^1]。 ### 使用 Logcat 查看与内存相关的日志 虽然 `logcat` 不会直接显示“内存泄漏”的信息,但你可以通过过滤关键字来查看与内存相关的系统日志。例如,当系统检测到内存不足时,可能会输出相关警告信息。 #### 常见的过滤关键词包括: - `GC_`: 表示垃圾回收事件,如 `GC_CONCURRENT`, `GC_FOR_ALLOC` 等。 - `dalvikvm`: 与 Dalvik 虚拟机相关的日志信息。 - `ActivityManager`: 包含与 Activity 生命周期和内存管理相关的信息。 你可以使用如下命令来过滤日志: ```bash adb logcat -s "dalvikvm:V ActivityManager:V" ``` 这将帮助你查看与内存管理相关的日志条目,辅助分析内存行为。 ### 使用 Android Studio 的 Heap Dump 分析内存泄漏 当你在 Memory Profiler 中导出堆转储(Heap Dump)后,Android Studio 会自动打开 **Heap Dump Analyzer**,它会列出所有类实例,并展示它们的引用链。你可以通过查看某个可疑对象的引用链,判断它是否被不必要的对象所持有,从而导致无法被回收[^1]。 ### 示例:查找 TestActivity 的内存泄漏 假设你在 Reference Tree 中看到有 3 个 `TestActivity` 实例未被释放,你可以点击每个实例查看其引用路径。如果发现某个静态变量或单例对象持有 `TestActivity` 的引用,那这就是内存泄漏的原因之一。例如: ```java public class LeakManager { private static Context context; public static void setContext(Context ctx) { context = ctx; // 泄漏了 TestActivity 的上下文 } } ``` 在这种情况下,`LeakManager.setContext(testActivity)` 将导致 `TestActivity` 无法被回收,即使它已经 finish()。 ### 总结步骤: 1. 打开 Android Studio 的 **Profiler** 工具。 2. 进入 **Memory** 页面并点击 **Dump Java heap**。 3. 在堆转储页面中查找可疑对象(如多个未释放的 Activity)。 4. 查看引用树(Reference Tree)分析泄漏路径。 5. 结合 `logcat` 过滤内存相关日志辅助分析。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值