最版网站建设案例,网站服务器响应时间过长,网站素材 图标,成都网站建设方案本次分享主要分为五个部分内容#xff0c;第一部分内容是 5W2H 分析内存优化#xff0c;第二部分内容是内存管理机制#xff0c;第三部分内容是内存优化 SOP#xff0c;第四部分内容是 内存优化指导原则#xff0c; 最后一部分内容是总结与展望。 如果学完内存优化的基础论…本次分享主要分为五个部分内容第一部分内容是 5W2H 分析内存优化第二部分内容是内存管理机制第三部分内容是内存优化 SOP第四部分内容是 内存优化指导原则 最后一部分内容是总结与展望。 如果学完内存优化的基础论、工具论、方法论和实战论那么任何人做内存优化都可以拿到结果。
二、5W2H 分析内存优化
首先我们说说我们的第一部分内容,5W2H 分析内存优化,5W2H 分析内存优化提出了 7 个高价值问题
内存优化定义内存优化原因内存优化归因内存优化维度内存优化时机内存优化价值内存痛点定位
内存优化定义
Android 内存优化是指优化 Android 应用程序的内存使用以减少可用内存的消耗提高应用程序的性能和可靠性。Android 内存优化可以通过减少内存使用量减少对资源的消耗以及提高内存利用率来实现。
内存优化原因
安卓系统对每个应用程序都有一定的内存限制当应用程序的内存超过了上限就会出现 OOM (Out of Memory)也就是 App的异常退出。
因此要改善系统的运行效率、改善用户体验、降低系统资源占用、延长电池寿命、降低系统故障的危险。
Android通过内存优化可以减少系统内存使用让系统更加流畅运行更快减少系统Crash提升用户体验。 How: 内存优化归因
关于应用内存分析需要重点关注四个阶段
应用停留在闪屏页面内存固定值应用的MainActivity到HomeActivty内存波动值应用运行十分钟后回归到HomeActivty内存波动值应用内存使用量分配值汇总 Android 给每个应用进程分配的内存都是非常有限的那么为什么不能把图片下载下来都放到磁盘中呢 因为放在内存中展示会更“快”快的原因两点
硬件快内存本身读取、存入速度快。复用快解码成果有效保存复用时直接使用解码后对象而不是再做一次图像解码。 那么问题来了什么是解码呢? Android 系统要在屏幕上展示图片的时候只默认“像素缓冲”而这也是大多数操作系统的特征。jpgpng 等图片格式是把“像素缓冲”使用不同的手段压缩后的结果。
不同格式的图片在设备上展示必须经过一次解码执行速度会受图片压缩比、尺寸等因素影响。
Who: 内存优化维度
对于 Android 内存优化可以细分为 RAM 和 ROM 两个维度
1.2.1 RAM 优化
主要是降低运行时内存RAM 优化目的有以下三个
防止应用发生 OOM。降低应用由于内存过大被 LMK 机制杀死的概率。避免不合理使用内存导致 GC 次数增多从而导致应用发生卡顿。
1.2.2 ROM 优化
减少程序占用的 ROM并进行 APK精简。其目标是减少应用程序的占用防止由于 ROM空间限制而导致程序的安装失败。
When: 内存优化时机
手机不使用 PC 的 DDR 内存采用的是 LP DDR RAM也就是“低功率的两倍数据率存储器”。其计算规则如下所示
LP DDR 系列的带宽时钟频率 ✖️ 内存总线位数/8
LP DDR41600MHZ✖️64/8✖️ 双倍速率26GB/s。 那么内存占用是否越少越好 如果当系统内存充足的时候那么我建议你多用一些内存获得更好的性能。
如果系统内存不足的时候那么我建议你可以做到“用时分配及时释放”。
How Much: 内存优化价值
做好内存优化将带来以下三点好处
第一点好处是减少 OOM提高应用稳定性。
第二点好处是减少卡顿提高应用流畅度。
第三点好处是减少内存占用提高应用后台运行时的存活率。
Where: 内存痛点定位
那么内存痛点定位主要是有哪几类呢内存痛点问题通常来说可以细分为如下三类:
第一内存抖动。 第二内存泄漏。 第三内存溢出。
下面带大家来了解下内存抖动、内存泄漏和内存溢出。 1.3.1 内存抖动
1.3.1.4.1 内存抖动定义
内存波动图形呈锯齿状、GC 导致卡顿。内存抖动在 Dalvik 虚拟机上更明显因为 ART 虚拟机内存管理、回收策略做了优化所以内存分配、GC 效率提升了 5~10 倍内存抖动发生概率小。 当内存频繁分配和回收导致内存不稳定出现内存抖动内存抖动通常表现为频繁 GC、内存曲线呈锯齿状。
并且内存抖动的危害严重会导致页面卡顿甚至 OOM。
1.3.1.4.2 OOM 原因 那么为什么内存抖动会导致 OOM 主要原因有如下两点
第一频繁创建对象导致内存不足及不连续碎片;
public class MainActivity extends AppCompatActivity {private Button mButton;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mButton (Button) findViewById(R.id.button);mButton.setOnClickListener(new View.OnClickListener() {Overridepublic void onClick(View view) {for (int i 0; i 100000; i) {// 频繁创建大量的对象byte[] data new byte[1024 * 1024];}}});}}在这段代码中每次点击按钮时都会创建 100,000 个大约为 1MB 的数组如果内存不够用则可能导致 OOM 错误。请注意实际应用中应避免这种不负责任的内存使用行为。
第二不连续的内存片无法被分配导致 OOM;
public class MainActivity extends AppCompatActivity {private Button mButton;private ArrayListbyte[] mDataList;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mButton (Button) findViewById(R.id.button);mButton.setOnClickListener(new View.OnClickListener() {Overridepublic void onClick(View view) {mDataList new ArrayList();for (int i 0; i 100000; i) {// 频繁创建大量的对象byte[] data new byte[1024 * 1024];mDataList.add(data);}}});}
}在这段代码中每次点击按钮时都会创建大量的 1MB 大小的数组并将它们添加到 mDataList 中。由于内存是不连续的因此在较大的数组中分配这些不连续的内存片可能导致 OOM 错误。请注意实际应用中应避免这种不负责任的内存使用行为。
1.3.1.4.3 内存抖动解决
这里假设有这样一个场景点击按钮使用 Handler 发送空消息Handler 的 handleMessage 方法接收到消息后会导致内存抖动
for 循环创建 100 个容量为 10w的 string[]数组在 30ms 后继续发送空消息。使用 MemoryProfiler 结合代码可找到内存抖动出现的地方。查看循环或频繁调用的地方即可。
public class MainActivity extends AppCompatActivity {private Button mButton;private Handler mHandler;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mButton (Button) findViewById(R.id.button);mButton.setOnClickListener(new View.OnClickListener() {Overridepublic void onClick(View view) {mHandler.sendEmptyMessage(0);}});mHandler new Handler() {Overridepublic void handleMessage(Message msg) {for (int i 0; i 100; i) {String[] arr new String[100000];}mHandler.sendEmptyMessageDelayed(0, 30);}};}}请注意这个代码中的消息循环可能会导致内存泄漏因此您需要在适当的时候删除消息。
1.3.1.4.4 内存抖动常见案例
下面列举一些导致内存抖动的常见案例如下所示 1.3.1.4.1 字符串使用加号拼接 实际开发中我们不应该使用字符串使用加号进行拼接而应该使用StringBuilder来替代。 初始化时设置容量减少StringBuilder的扩容。
public class Main {public static void main(String[] args) {// 使用加号拼接字符串String str ;long startTime System.currentTimeMillis();for (int i 0; i 100000; i) {str str hello;}System.out.println(使用加号拼接字符串的内存使用量 (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024) MB);System.out.println(使用加号拼接字符串的时间 (System.currentTimeMillis() - startTime) ms);// 使用StringBuilderStringBuilder sb new StringBuilder(5);startTime System.currentTimeMillis();for (int i 0; i 100000; i) {sb.append(hello);}System.out.println(使用StringBuilder的内存使用量 (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024) MB);System.out.println(使用StringBuilder的时间 (System.currentTimeMillis() - startTime) ms);}}输出结果: 使用加号拼接字符串的内存使用量75 MB 使用加号拼接字符串的时间4561 ms 使用StringBuilder的内存使用量77 MB 使用StringBuilder的时间4 ms 1.3.1.4.2 资源复用
使用全局缓存池避免频繁申请和释放的对象。
public class ObjectPool {private static ObjectPool instance null;private HashMapString, Object pool new HashMap();private ObjectPool() {}public static ObjectPool getInstance() {if (instance null) {instance new ObjectPool();}return instance;}public void addObject(String key, Object object) {pool.put(key, object);}public Object getObject(String key) {return pool.get(key);}public void removeObject(String key) {pool.remove(key);}
}该代码使用单例模式创建了一个 ObjectPool 类并实现了添加、获取和删除对象的方法。
当应用程序需要使用某个对象时可以通过调用 ObjectPool.getInstance().getObject(key) 方法从缓存池中获取该对象。
当不再需要该对象时可以调用 removeObject(key) 方法将其从缓存池中删除。
但使用后手动释放对象池中的对象(removeObject 这个 key)。
1.3.1.4.3 减少不合理的对象创建
onDraw 中创建的对象尽量进行复用
public class CustomView extends View {private Paint paint;private Rect rect;public CustomView(Context context) {super(context);}Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 重复创建对象导致内存抖动paint new Paint();rect new Rect();paint.setColor(Color.RED);paint.setStyle(Paint.Style.FILL);rect.set(0, 0, getWidth(), getHeight());canvas.drawRect(rect, paint);}}public class MainActivity extends AppCompatActivity {Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 重复创建对象导致内存抖动setContentView(new CustomView(this));}
}上面的代码中在CustomView的onDraw方法和MainActivity的onCreate方法中每次都重新创建了Paint和Rect对象这会导致内存波动因为系统并不能回收之前创建的对象。
为了避免这种情况我们可以将Paint和Rect对象声明为类变量并在构造方法中初始化以保证只创建一次
public class CustomView extends View {private Paint paint;private Rect rect;public CustomView(Context context) {super(context);// 初始化对象paint new Paint();rect new Rect();}Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);paint.setColor(Color.RED);paint.setStyle(Paint.Style.FILL);rect.set(0, 0, getWidth(), getHeight());canvas.drawRect(rect, paint);}
}public class MainActivity extends AppCompatActivity {Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(new CustomView(this));}
}每次创建局部变量时内存都会分配给它但在循环结束后它们不会被立即回收。这将导致内存的不断增加最终导致内存抖动。
避免在循环中不断创建局部变量
//----------------------------错误示例---------------------------for(int i0;i 100000;i){Bitmap bitmapBitmapFactory.decodeResource(getResources(),R.drawable.large_image);}//----------------------------正确示例---------------------------Bitmap bitmap;for(int i0;i 100000;i){bitmapBitmapFactory.decodeResource(getResources(),R.drawable.large_image);bitmap.recycle();}在这个例子中每次循环都会创建一个 Bitmap 对象并将其赋值给局部变量 bitmap。但是循环结束后 Bitmap 对象不会被立即回收因此内存不断增加。
1.3.1.4.4 使用合理的数据结构
使用 SparseArray 类族、ArrayMap 来替代 HashMap。 public class Main {public static void main(String[] args) {int N 100000;
// Create a SparseArraySparseArrayInteger sparseArray new SparseArray();for (int i 0; i N; i) {sparseArray.put(i, i);}System.out.println(SparseArray size: sparseArray.size());System.gc();long memorySparseArray Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
// Create an ArrayMapArrayMapInteger, Integer arrayMap new ArrayMap();for (int i 0; i N; i) {arrayMap.put(i, i);}System.out.println(ArrayMap size: arrayMap.size());System.gc();long memoryArrayMap Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
// Create a HashMapHashMapInteger, Integer hashMap new HashMap();for (int i 0; i N; i) {hashMap.put(i, i);}System.out.println(HashMap size: hashMap.size());System.gc();long memoryHashMap Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();System.out.println(Memory usage:);System.out.println(SparseArray: memorySparseArray / 1024.0 KB);System.out.println(ArrayMap: memoryArrayMap / 1024.0 KB);System.out.println(HashMap: memoryHashMap / 1024.0 KB);}
}1.3.4 内存泄漏
Android 系统虚拟机的垃圾回收是通过虚拟机 GC 机制来实现的。GC 会选择一些还存活的对象作为内存遍历的根节点 GC Roots通过对 GC Roots 的可达性来判断是否需要回收。
内存泄漏是在当前应用周期内不再使用的对象被 GC Roots 引用导致不能回收使实际可使用内存变小。
对象被持有导致无法释放或不能按照对象正常的生命周期进行释放内存泄漏导致可用内存减少和频繁 GC从而导致内存溢出App 卡顿。
public class MainActivity extends AppCompatActivity {private ListBitmap bitmaps new ArrayList();Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
// 不断加载图片并加入到List中while (true) {Bitmap bitmap BitmapFactory.decodeResource(getResources(), R.drawable.large_image);bitmaps.add(bitmap);}}
}在上面的代码中每次加载图片并加入到List中都不会释放内存因为List引用了这些图片导致图片无法释放最终造成内存溢出。为了避免内存溢出你可以考虑使用低内存占用的图片格式或者在不需要使用图片时主动调用recycle方法释放图片的内存。
1.3.4 内存溢出
OOMOOM 时会导致程序异常。Android 设备出厂以后java 虚拟机对单个应用的最大内存分配就确定下来了超出值就会 OOM。
单个应用可用的最大内存对应于 /system/build.prop 文件中的 dalvik.vm.heap growth limit。
此外除了因内存泄漏累积到一定程度导致 OOM 的情况以外也有一次性申请很多内存比如说一次创建大的数组或者是载入大的文件如图片的时候会导致 OOM。而且实际情况下很多 OOM 就是因图片处理不当而产生的。
public class MainActivity extends AppCompatActivity {private ImageView imageView;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);imageView findViewById(R.id.image_view);
// 试图创建大的数组int[] largeArray new int[Integer.MAX_VALUE];
// 或者试图载入大的图片Bitmap largeBitmap BitmapFactory.decodeResource(getResources(), R.drawable.large_image);imageView.setImageBitmap(largeBitmap);}
}三、内存管理机制
3.1 ARTDalvik 虚拟机
ART 和 Dalvik 虚拟机使用分页和内存映射来管理内存。ART 和 Dalvik 虚拟机有什么区别呢?
Dalvik 是 Android 系统首次推出的虚拟机它是一个字节码解释器把 Java 字节码转换为机器码执行。由于它的设计历史和硬件限制它的性能较差但是可以很好地支持多个 Android 设备。
而 ART 则是 Android 4.4KitKat发布后推出的一种新的 Java 虚拟机它把 Java 字节码编译成机器码在安装应用时一次性编译因此不需要在运行时解释字节码提高了性能。ART 的编译技术带来了更快的应用启动速度和更低的内存消耗。
因此ART 相比 Dalvik在性能和稳定性方面有了很大的提升但是由于 ART 把字节码编译成机器码因此空间占用更大对于一些低内存的设备来说可能不太适用。
说到这两种虚拟机我们不得不提到 LMKLow Memory killer
3.2 LMK 内存管理机制
LMKLow Memory Killer是 Android 系统内存管理机制中的一部分LMK 是用来在内存不足时释放系统中不必要的进程以保证系统的正常运行。
LMK 机制的底层原理是利用内核 OOMOut-of-Memory机制来管理内存。当系统内存不足时内核会根据各进程的优先级将内存分配给重要的进程同时会结束一些不重要的进程以避免系统崩溃。
LMK 机制的使用场景包括
系统内存不足当系统内存不足时LMK 机制会帮助系统管理内存以保证系统正常运行。内存泄漏当应用存在内存泄漏时LMK 机制会将泄漏的内存释放掉以保证系统正常运行。进程优化LMK 机制可以帮助系统管理进程以确保系统资源的合理利用。
在系统内存紧张的情况下LMK 机制可以通过结束不重要的进程来释放内存以保证系统的正常运行。但是如果不当使用它也可能导致应用程序的不稳定。因此开发者需要合理设计应用程序避免内存泄露。
下面先从 Java 的内存分配开始说起。
3.3 Java 内存分配
Java 的内存分配区域分为如下五部分 3.4 Java 内存回收算法 3.4.1 标记清除算法
标记清除算法是最早的内存回收算法其工作原理是标记出不再使用的对象并将其回收。
标记清除算法步骤 标记所有存活的对象。统一回收所有未被标记的对象。
标记清除算法优点
实现比较简单。
标记清除算法缺点 标记、清除效率不高。 产生大量内存碎片。
3.4.2 复制算法
复制算法是一种将内存分为两个区域的算法其中一个区域用于存储活动对象另一个区域用于存储不再使用的对象。
复制算法步骤 将内存划分为大小相等的两块。一块内存用完之后复制存活对象到另一块。清理另一块内存。
复制算法优点
实现简单运行高效每次仅需遍历标记一半的内存区域。
复制算法缺点
会浪费一半的空间代价大。
3.4.3 标记整理算法
标记整理算法是标记清除算法和复制算法的结合其工作原理是先标记出不再使用的对象再整理内存使得活动对象的内存分配连续
标记整理算法步骤 标记过程与 标记-清除算法 一样。存活对象往一端进行移动。清理其余内存。
标记整理算法优点
避免标记清除导致的内存碎片。避免复制算法的空间浪费。
标记整理算法缺点
时间开销标记整理算法需要进行两次扫描一次标记活动对象一次整理内存这增加了时间开销。空间开销由于标记整理算法需要为活动对象留出足够的空间因此必须移动内存中的一些对象这会增加空间开销。内存碎片标记整理算法在整理内存时可能会产生内存碎片使得未使用的内存碎片不能被有效利用。速度慢相对于其他垃圾回收算法标记整理算法的速度较慢因此不适合需要高效内存管理的场景。效率不稳定标记整理算法效率受到内存使用情况的影响如果内存使用情况不均衡效率会不稳定。
3.4.4 分代收集算法
分代回收算法是一种将内存分为几个代的算法并对每个代进行不同的回收策略
分代收集算法步骤 分配新的对象新创建的对象分配在新生代中因为大多数新创建的对象都很快失效并且删除它们的成本很低。垃圾回收新生代中的垃圾对象被回收并且回收算法只涉及到新生代的一小部分。如果一个对象存活到一定时间它将被移动到老年代。老年代回收在老年代中回收算法进行全面的垃圾回收以确保可以回收所有垃圾对象。整理内存回收后内存被整理以确保连续的内存空间可以分配给新对象。
主流的虚拟机一般用的比较多的是分代收集算法。
分代收集算法优点
减少垃圾回收的时间通过将新生代和老年代分开分代收集算法可以减少垃圾回收的时间因为新生代中的垃圾对象被回收的频率较高。减少内存碎片因为新生代的垃圾回收频率较高分代收集算法可以防止内存碎片的产生。提高内存利用率分代收集算法可以有效地回收垃圾对象提高内存的利用率。减少内存消耗分代收集算法可以减少对内存的消耗因为它仅需要涉及小的内存区域而不是整个 Java 堆。提高系统性能分代收集算法可以提高系统性能因为它可以缩短垃圾回收的时间提高内存利用率减少内存消耗。
分代收集算法缺点
复杂性分代收集算法相对于其他垃圾回收算法来说更复杂需要更多的内存空间来管理垃圾回收。内存分配不均衡分代收集算法可能导致内存分配不均衡这可能导致新生代内存不足老年代内存过多。垃圾对象转移次数分代收集算法需要移动垃圾对象这可能导致更多的计算开销。时间开销分代收集算法需要更长的时间来管理垃圾回收这可能导致系统性能下降。停顿时间分代收集算法可能导致长时间的停顿这可能影响系统的实时性。
3.4.5 内存回收算法使用推荐
在Java中两种常用的内存回收算法分别是新生代回收算法和老年代回收算法。
新生代回收算法推荐场景
对象生命周期短适用于那些生命周期短的对象因为它们在很短的时间内就会被回收。大量生成对象对于大量生成对象的场景新生代回收算法可以有效地减少回收时间。
老年代回收算法推荐场景
对象生命周期长适用于生命周期长的对象因为它们不会很快被回收。内存数据稳定对于内存数据稳定的场景老年代回收算法可以提高内存效率。
请注意这是基于Java的默认内存回收算法即垃圾回收器的推荐使用场景。您可以通过配置JVM参数来更改这些默认设置以适应您的特定需求。
3.5 Java 内存管理
Android 中的内存是弹性分配的分配值与最大值受具体设备影响。
对于 OOM 场景其实可以细分为如下两种 可用被分配的内存不足指系统已经分配了足够的内存但是由于程序或者其他应用程序的需求系统中的可用被分配的内存不足以支持当前的运行。内存真正不足指系统中内存总量不足以支持程序的运行即系统总内存实际上不够用。
因此在解决内存不足的问题时需要首先判断是可用被分配的内存不足还是内存真正不足并根据相应情况采取适当的措施。
如果是可用被分配的内存不足可以通过调整程序的内存配置或者关闭其他应用程序来解决问题。
如果是内存真正不足则需要通过升级内存或者更换计算机等方式来解决问题。
3.6 Java 引用类型
JVM 场景的引用类型有四种,分别是强引用、软引用、软引用和虚引用 强引用、软引用、软引用和虚引用的本质区别可以参考如下表:
引用类型GC 回收时间用途生存时间强引用永不对象的一般状态JVM 停止运行时软引用内存不足时对象缓存内存不足时终止弱引用GC对象缓存GC 后终止虚引用未知未知未知
强引用
强引用概念
强引用是 Java 中最常见的引用类型当对象具有强引用时它永远不会被垃圾回收。只有在程序结束或者手动将对象设置为 null 时才会释放强引用。
强引用案例
public class StrongReferenceExample {public static void main(String[] args) {ArrayListString data new ArrayList();data.add(Hello);data.add(World);// 创建强引用ArrayListString strongReference data;System.out.println(Data before garbage collection: strongReference);// 断开 data 引用使其可以被回收data null;System.gc();System.out.println(Data after garbage collection: strongReference);}}输出结果: Data before garbage collection: [Hello, World] Data after garbage collection: [Hello, World] 在代码中我们创建了一个 ArrayList 对象 data并通过赋值语句将它的引用赋给了变量 strongReference此时strongReference 和 data 将指向同一个对象。
在之后的代码中我们断开了 data 的引用让其变成可回收对象但因为 strongReference 仍然保持着对该对象的强引用所以该对象在 GC 后仍然不会被回收。
弱引用
弱引用概念
一种用于追踪对象的引用不会对对象的生命周期造成影响。在内存管理方面弱引用不被认为是对象的“有效引用”。
因此如果一个对象只被弱引用指向那么在垃圾回收的时候这个对象可能会被回收掉。
弱引用常被用来在内存敏感的应用中实现对象缓存。在这种情况下弱引用可以让缓存的对象在内存不足时被回收从而避免内存泄漏。
弱引用案例
public class WeakReferenceExample {public static void main(String[] args) {String data new String(Hello);// 创建弱引用WeakReferenceString weakReference new WeakReference(data);System.out.println(Data before garbage collection: weakReference.get());// 断开 data 引用使其可以被回收data null;System.gc();System.out.println(Data after garbage collection: weakReference.get());}}输出结果: Data before garbage collection: Hello Data after garbage collection: null 在代码中我们创建了一个字符串对象 data并通过创建 WeakReference 对象并将 data 作为参数来创建弱引用。
在之后的代码中我们断开了 data 的引用让其变成可回收对象但因为 weakReference 仅持有对该对象的弱引用所以当 JVM 进行 GC 时该对象可能会被回收。
可以通过 weakReference.get 方法来检查对象是否被回收。
如果对象已被回收则 weakReference.get() 返回 null。
软引用
软引用概念
软引用是比强引用更容易被回收的引用类型。当 Java 堆内存不足时软引用可能会被回收以腾出内存空间。如果内存充足则软引用可以继续存在。
软引用案例
public class SoftReferenceExample {public static void main(String[] args) {Object referent new Object();SoftReferenceObject softReference new SoftReference(referent);referent null;System.gc();// 软引用可以在内存不足时被回收System.out.println(softReference.get());}}输出结果: 情况1: java.lang.Object2f92e0f4 情况2: null 这段代码创建了一个 Object 的实例并使用它作为 SoftReference 的引用对象。
然后它将该实例设置为 null并试图强制进行垃圾回收。如果内存不足软引用会被回收并且可以从 softReference 获取的对象将为 null。
虚引用
虚引用概念
虚引用是 Java 中最弱的引用类型对于虚引用对象只存在于垃圾回收的最后阶段在这个阶段对象将被回收而无论内存是否充足。虚引用主要用于监测对象被回收的状态而不是用于缓存对象。
虚引用案例
public class PhantomReferenceExample {public static void main(String[] args) {Object referent new Object();ReferenceQueueObject referenceQueue new ReferenceQueue();PhantomReferenceObject phantomReference new PhantomReference(referent, referenceQueue);referent null;System.gc();// 虚引用在回收前不会被加入引用队列但在回收时会被加入引用队列System.out.println(referenceQueue.poll() phantomReference);}}输出结果: false 这段代码创建了一个 Object 的实例并使用它作为 PhantomReference 的引用对象。
然后它将该实例设置为 null并试图强制进行垃圾回收。如果垃圾回收发生虚引用会被加入引用队列从而可以从引用队列中获取。
四、内存优化 SOP 分析现状
如果发现 APP 在内存方面可能存在很大的问题第一方面的原因是线上的 OOM 率比较高。
第二方面的原因是经常会看到在 Android Studio 的 Profiler 工具中内存的抖动比较频繁。
确认问题
这是一个初步的现状然后在知道了初步的现状之后进行了问题的确认经过一系列的调研以及深入研究最终发现项目中存在以下几点大问题比如说内存抖动、内存溢出、内存泄漏还有 Bitmap 粗犷使用。
问题优化
如果想解决内存抖动Memory Profiler 会呈现了锯齿张图形然后我们分析到具体代码存在的问题频繁被调用的方法中出现了日志字符串的拼接就能解决内存泄漏或内存溢出。
体验提升
为了不增加业务工作量使用一些工具类或 ARTHook 大图检测方案没有任何的侵入性。同时将技术进行团队分享团队的工作效率上会有本质提升。
对内存优化工具如 Profiler Memory、MAT 的使用可以针对一系列不同问题的情况写一系列解决方案文档整个团队成员的内存优化意识会更强。
五、内存优化指导原则 万事俱备水滴石穿
做内存优化首先应该学习 Google 内存方面的文档如 Memory Profiler、MAT 等工具的使用当在工程遇到内存问题才能对问题进行排查定位。而不是一开始并没有分析项目代码导致内存高占用问题就依据自己看的几篇企业博客不管业务背景瞎猫碰耗子做内存优化。
结合业务优化内存
如果不结合业务背景直接对APP运行阶段进行内存上报然后内存消耗进行内存监控,那么内存监控一旦不到位,比如存在使用多个图片库因为图片库内存缓存不公用的应用内存占用效率不会有质的飞跃。因此技术优化必须结合业务。
解决方案系统科学
在做内存优化的过程中Android业务端除了要做优化工作Android业务端还得负责数据采集上报数据上报到 APM后台后无论是Bug追踪人员或者Crash追踪人员对问题回码定位都提供好的依据。
内存劣化Hook魔改
大图片检测方案大家可能想到去是继承ImageView然后重写ImageView的onDraw方法实现。但是在推广的过程中因为耦合度过高业务同学很难认可ImageView之前写一次为什么要重复造轮子呢? 替换成本非常高。所以我们可以考虑使用类似ARTHook这样的Hook方案。
六、总结与展望
内存优化、启动优化、卡顿优化、包体积优化是 Android 性能优化四驾马车而内存优化又是四驾马车最难驾驭的一驾如果你掌握了这项基础技能那么你将超过绝对多数的 Android 开发
内存优化 · 基础论 · 初识 Android 内存优化我们讲解了五部分内容第一部分内容是 5W2H 分析内存优化第二部分内容是内存管理机制第三部分内容是内存优化 SOP第四部分内容是内存优化指导原则最后一部分内容是总结与展望。
Android 知识点归整
Android 性能调优系列https://0a.fit/dNHYY Android 车载学习指南https://0a.fit/jdVoy Android Framework核心知识点笔记https://0a.fit/acnLL Android 音视频学习笔记https://0a.fit/BzPVh Jetpack全家桶含Composehttps://0a.fit/GQJSl Kotlin 入门到精进https://0a.fit/kdfWR Flutter 基础到进阶实战https://0a.fit/xvcHV Android 八大知识体系https://0a.fit/mieWJ Android 中高级面试题锦https://0a.fit/YXwVq
后续如有新知识点将会持续更新尽请期待……