关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、人工智能等,希望大家多多支持。
目录
- 一、导读
- 二、概览
- 三、相关概念
- 四、主动管理应用内存
-
- [4.1 内存监控](#4.1 内存监控)
-
- 通过命令行查看内存占用情况
- [通过Android Studio的Profiler工具](#通过Android Studio的Profiler工具)
- 通过代码
- [4.2 释放内存](#4.2 释放内存)
-
- [ComponentCallbacks2 接口](#ComponentCallbacks2 接口)
- application中复写onTrimMemory()、onLowMemory()等方法
- [4.3 查看应该使用多少内存](#4.3 查看应该使用多少内存)
- [4.4 写在后面](#4.4 写在后面)
-
- [java 内存分配、内存回收算法](#java 内存分配、内存回收算法)
- [android dalvik 与 art区别](#android dalvik 与 art区别)
- [4.5 相关工具](#4.5 相关工具)
- 内存压力测试
- [五、 推荐阅读](#五、 推荐阅读)
一、导读
我们继续总结学习基础知识,温故知新。
本文主要描述Android中的内存管理及java中的内存管理相关知识点。
二、概览
Android内存优化是性能优化中很重要的一部分,比如内存溢出(OOM),这些都跟内存息息相关,所以,我们要掌握一些相关知识。
Android 运行时 (ART) 和 Dalvik 虚拟机使用分页和内存映射 来管理内存。
这意味着应用修改的任何内存,无论修改的方式是分配新对象还是轻触内存映射的页面,都会一直驻留在 RAM 中,并且无法换出。
若要从应用中释放内存,只能释放应用保留的对象引用,使内存可供垃圾回收器回收。
这种情况有一个例外:对于任何未经修改的内存映射文件(如代码),如果系统想要在其他位置使用其内存,可将其从 RAM 中换出。
Android 设备包含三种不同类型的内存:RAM、zRAM 和存储器。
- RAM 是最快的内存类型,但其大小通常有限。
- zRAM 是用于交换空间的 RAM 分区。所有数据在放入 zRAM 时都会进行压缩,然后在从 zRAM 向外复制时进行解压缩。这部分 RAM 会随着页面进出 zRAM 而增大或缩小。
- storage 中包含所有持久性数据(例如文件系统等),以及为所有应用、库和平台添加的对象代码。存储器比另外两种内存的容量大得多。
待申请的内存大于系统分配给应用的剩余内存
三、相关概念
3.1 垃圾回收
ART 或 Dalvik 虚拟机之类的受管内存环境会跟踪每次内存分配。一旦确定程序不再使用某块内存,它就会将该内存重新释放到堆中,无需程序员进行任何干预。
垃圾回收有两个目标:一是查找将来无法访问的数据对象,二是回收这些对象使用的资源。
我们在之前的文章Jvm堆分类中介绍过,堆内存分为三部分: 新生代,老年代,永久代(元空间)。
在Android 的内存堆亦是分代的,堆的每一代对相应对象可占用的内存量都有其自身的专用上限。每当一代开始填满时,系统便会执行垃圾回收事件以释放内存。
垃圾回收的持续时间取决于它回收的是哪一代对象以及每一代有多少个活动对象。
尽管垃圾回收速度非常快,但仍会影响应用的性能。通常情况下,您无法从代码中控制何时发生垃圾回收事件。系统有一套专门确定何时执行垃圾回收的标准。
当条件满足时,系统会停止执行进程并开始垃圾回收。如果在动画或音乐播放等密集型处理循环过程中发生垃圾回收,可能会增加处理时间,
进而可能会导致应用中的代码执行超出建议的 16 毫秒阈值,无法实现高效、流畅的帧渲染。
3.2 应用内存的分配与回收
每个应用Dalvik 堆大小可以根据需要增长,但不能超过系统为每个应用定义的上限。不同设备的确切堆大小上限取决于设备的总体可用 RAM 大小 ,
我们可以通过代码来检查可用空间
java
ActivityManager.getMemoryClass() 查询当前应用的Heap Size
逻辑上讲的Heap Size和实际物理意义上使用的内存大小是不对等的,Proportional Set Size(PSS)记录了应用程序自身占用以及与其他进程进行共享的内存。
Android系统并不会对Heap中空闲内存区域做碎片整理。系统仅仅会在新的内存分配之前判断Heap的尾端剩余空间是否足够,如果空间不够会触发GC操作,从而腾出更多空闲的内存空间
在Android中存在大量的共享内存,如Zygote进程,静态数据等
3.3 切换应用
当用户在应用之间切换时,Android 会将非前台应用保留在缓存中。非前台应用就是指用户看不到或未运行前台服务(如音乐播放)的应用。
例如,当用户首次启动某个应用时,系统会为其创建一个进程;但是当用户离开此应用时,该进程不会退出。系统会将该进程保留在缓存中。如果用户稍后返回该应用,系统会重复使用该进程,从而加快应用切换速度。
如果您的应用具有缓存的进程且保留了目前不需要的资源,那么即使用户未使用您的应用,它也会影响系统的整体性能。当系统资源(如内存)不足时,
它将会终止缓存中的进程。系统还会考虑终止占用最多内存的进程以释放 RAM。
3.4 内存不足管理
Android 有两种处理内存不足情况的主要机制:内核交换守护程序和低内存终止守护程序。
内核交换守护程序
内核交换守护程序 (kswapd) 是 Linux 内核的一部分,用于将已使用内存转换为可用内存。当设备上的可用内存不足时,该守护程序将变为活动状态。Linux 内核设有可用内存上下限阈值。
当可用内存降至下限阈值以下时,kswapd 开始回收内存。当可用内存达到上限阈值时,kswapd 停止回收内存。
低内存终止守护程序
很多时候,内核交换守护程序(kswapd) 不能为系统释放足够的内存。在这种情况下,系统会使用 onTrimMemory() 通知应用内存不足,应该减少其分配量。
如果这还不够,内核会开始终止进程以释放内存。它会使用低内存终止守护程序 (LMK) 来执行此操作。
LMK 使用一个名为 oom_adj_score 的"内存不足"分值来确定正在运行的进程的优先级,以此决定要终止的进程。最高得分的进程最先被终止。后台应用最先被终止,系统进程最后被终止。
四、主动管理应用内存
RAM 是一项宝贵资源,虽然 Android 运行时 (ART) 和 Dalvik 虚拟机都会定期执行垃圾回收任务,但这并不意味着可以忽略应用分配和释放内存的位置和时间。
您仍然需要避免引入内存泄漏问题(通常因在静态成员变量中保留对象引用而引起),并在适当时间(如生命周期回调所定义)释放所有 Reference 对象。
OOM有两个场景,一是内存真正不足,一是可用内存不足
4.1 内存监控
为了更好的使用内存,我们自己要对内存进行监控,内存监控的主要指标为:内存占用、OOM。我们可以用多种方式来查看内存占用情况:
通过命令行查看内存占用情况
adb shell dumpsys meminfo -a com.xxx
通过Android Studio的Profiler工具
通过代码
具体可参考 Memory Analyzer(MAT)分析内存 中hprof 文件准备,能获取到内存文件。
4.2 释放内存
在应用中,我们可以通过多种方式来处理系统内存不足
ComponentCallbacks2 接口
在 Activity 类(或其他地方)中实现 ComponentCallbacks2 接口,借助所提供的 onTrimMemory() 回调方法,然后释放对象
例如,您可以实现 onTrimMemory() 回调以响应与内存相关的不同事件,如下所示:
java
import android.content.ComponentCallbacks2;
// Other import statements ...
public class MainActivity extends AppCompatActivity
implements ComponentCallbacks2 {
// Other activity code ...
/**
* Release memory when the UI becomes hidden or when system resources become low.
* @param level the memory-related event that was raised.
*/
public void onTrimMemory(int level) {
// Determine which lifecycle or system event was raised.
switch (level) {
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
/*
Release any UI objects that currently hold memory.
The user interface has moved to the background.
*/
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
/*
Release any memory that your app doesn't need to run.
The device is running low on memory while the app is running.
The event raised indicates the severity of the memory-related event.
If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will
begin killing background processes.
*/
break;
case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
/*
Release as much memory as the process can.
The app is on the LRU list and the system is running low on memory.
The event raised indicates where the app sits within the LRU list.
If the event is TRIM_MEMORY_COMPLETE, the process will be one of
the first to be terminated.
*/
break;
default:
/*
Release any non-critical data structures.
The app received an unrecognized memory level value
from the system. Treat this as a generic low-memory message.
*/
break;
}
}
}
application中复写onTrimMemory()、onLowMemory()等方法
4.3 查看应该使用多少内存
通过调用 getMemoryInfo() 向系统查询,其中会提供与设备当前的内存状态有关的信息,包括可用内存、总内存和内存阈值(如果内存用量达到此数值,系统就会开始终止进程)。
ActivityManager.MemoryInfo 对象还会公开一个简单的布尔值 lowMemory,您可以根据此值确定设备是否内存不足
java
// Get a MemoryInfo object for the device's current memory status.
private ActivityManager.MemoryInfo getAvailableMemory() {
ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
return memoryInfo;
}
4.4 写在后面
java 内存分配、内存回收算法
对于java的内存管理,我们要清楚的知道java的内存分配区域、以及内存回收算法,
掌握几种垃圾回收算法及思想、适配场景及优化演进的过程,常用的有:
标记-清除算法
- 标记出所有需要回收的对象
- 统一回收所有被标记的对象
要对每个区域进行标记,效率相对要低,而且会产生大量不连续的碎片,
复制算法
- 将整个内存划分为大小相等的两块,比如左右两边
- 一块内存(例如左边)用完后复制存活对象到另一块(例如右边)
- 再清理另一块内存(例如左边),使之连续
这样比较浪费内存,因为有一块区域始终是空的,准备用于复制用
标记-整理算法
- 对每个区域进行标记,标记出所有需要回收的对象
- 将存活的对象移动到一起
- 清理其余要回收的对象
分代收集算法
- 结合多种收集算法
android dalvik 与 art区别
android 是基于java语音的,但是仍然有自己的独特的改造,dalvik的垃圾回收算法是固定的,每个手机在系统确定好后就不会变化了,
而art的垃圾回收算法是在运行期间可以变化的,
4.5 相关工具
内存压力测试
压力应用测试 (stressapptest) 是一种内存接口测试,有助于创建真实的高负载场景,以测试应用在内存和硬件方面存在的各种限制。
由于能够定义时间和内存限制,因此通过该测试,您可以编写插桩,以便验证哪些现实情况会出现内存耗用量高的问题。
例如,使用以下命令集推送数据文件系统中的静态库,使其可执行,并在 990 MB 的情况下运行压力测试 20 秒:
java
adb push stressapptest /data/local/tmp/
adb shell chmod 777 /data/local/tmp/stressapptest
adb shell /data/local/tmp/stressapptest -s 20 -M 990
可自行学习。