内存优化基础论初识Android内存优化
【小木箱成长营】内存优化系列文章:
内存优化工具论常见的Android内存优化工具和框架
内存优化方法论揭开内存优化神秘面纱
内存优化实战论内存优化实践与应用
Tips:关注微信公众号小木箱成长营,回复内存优化可免费获得内存优化思维导图一、序言
Hello,我是小木箱,欢迎来到小木箱成长营系列教程,今天将分享内存优化基础论初识Android内存优化。
本次分享主要分为五个部分内容,第一部分内容是5W2H分析内存优化,第二部分内容是内存管理机制,第三部分内容是内存优化SOP,第四部分内容是内存优化指导原则,最后一部分内容是总结与展望。
如果学完小木箱内存优化的基础论、工具论、方法论和实战论,那么任何人做内存优化都可以拿到结果。二、5W2H分析内存优化
首先我们说说我们的第一部分内容,5W2H分析内存优化,5W2H分析内存优化提出了7个高价值问题What:内存优化定义Why:内存优化原因How:内存优化归因Who:内存优化维度When:内存优化时机HowMuch:内存优化价值Where:内存痛点定位What:内存优化定义
Android内存优化是指优化Android应用程序的内存使用,以减少可用内存的消耗,提高应用程序的性能和可靠性。Android内存优化可以通过减少内存使用量,减少对资源的消耗,以及提高内存利用率来实现。Why:内存优化原因
安卓系统对每个应用程序都有一定的内存限制,当应用程序的内存超过了上限,就会出现OOM(OutofMemory),也就是App的异常退出。
因此,要改善系统的运行效率、改善用户体验、降低系统资源占用、延长电池寿命、降低系统故障的危险。
Android通过内存优化,可以减少系统内存使用,让系统更加流畅,运行更快,减少系统Crash,提升用户体验。
How:内存优化归因
关于应用内存分析,需要重点关注四个阶段应用停留在闪屏页面内存固定值应用的MainActivity到HomeActivty内存波动值应用运行十分钟后回归到HomeActivty内存波动值应用内存使用量分配值汇总
Android给每个应用进程分配的内存都是非常有限的,那么,为什么不能把图片下载下来都放到磁盘中呢?
因为放在内存中,展示会更快,快的原因两点:硬件快:内存本身读取、存入速度快。复用快:解码成果有效保存,复用时,直接使用解码后对象,而不是再做一次图像解码。
那么,问题来了,什么是解码呢?
Android系统要在屏幕上展示图片的时候只默认像素缓冲,而这也是大多数操作系统的特征。jpg,png等图片格式,是把像素缓冲使用不同的手段压缩后的结果。
不同格式的图片,在设备上展示,必须经过一次解码,执行速度会受图片压缩比、尺寸等因素影响。Who:内存优化维度
对于Android内存优化可以细分为RAM和ROM两个维度:1。2。1RAM优化
主要是降低运行时内存,RAM优化目的有以下三个:防止应用发生OOM。降低应用由于内存过大被LMK机制杀死的概率。避免不合理使用内存导致GC次数增多,从而导致应用发生卡顿。1。2。2ROM优化
减少程序占用的ROM,并进行APK精简。其目标是减少应用程序的占用,防止由于ROM空间限制而导致程序的安装失败。When:内存优化时机
手机不使用PC的DDR内存,采用的是LPDDRRAM,也就是低功率的两倍数据率存储器。其计算规则如下所示:
LPDDR系列的带宽时钟频率内存总线位数8
LPDDR41600MHZ648双倍速率26GBs。
那么内存占用是否越少越好?
如果当系统内存充足的时候,那么小木箱建议你多用一些内存获得更好的性能。
如果系统内存不足的时候,那么小木箱建议你可以做到用时分配,及时释放。HowMuch:内存优化价值
做好内存优化将带来以下三点好处:
第一点好处是减少OOM,提高应用稳定性。
第二点好处是减少卡顿,提高应用流畅度。
第三点好处是减少内存占用,提高应用后台运行时的存活率。Where:内存痛点定位
那么,内存痛点定位主要是有哪几类呢?内存痛点问题通常来说,可以细分为如下三类:
第一,内存抖动。
第二,内存泄漏。
第三,内存溢出。
下面,小木箱带大家来了解下内存抖动、内存泄漏和内存溢出。
1。3。1内存抖动1。3。1。4。1内存抖动定义
内存波动图形呈锯齿状、GC导致卡顿。内存抖动在Dalvik虚拟机上更明显,因为ART虚拟机内存管理、回收策略做了优化,所以内存分配、GC效率提升了510倍,内存抖动发生概率小。
当内存频繁分配和回收导致内存不稳定,出现内存抖动,内存抖动通常表现为频繁GC、内存曲线呈锯齿状。
并且,内存抖动的危害严重,会导致页面卡顿,甚至OOM。1。3。1。4。2OOM原因
那么,为什么内存抖动会导致OOM?
主要原因有如下两点:
第一,频繁创建对象,导致内存不足及不连续碎片;publicclassMainActivityextendsAppCompatActivity{privateButtonmButton;OverrideprotectedvoidonCreate(BundlesavedInstanceState){super。onCreate(savedInstanceState);setContentView(R。layout。activitymain);mButton(Button)findViewById(R。id。button);mButton。setOnClickListener(newView。OnClickListener(){OverridepublicvoidonClick(Viewview){for(inti0;i100000;i){频繁创建大量的对象byte〔〕datanewbyte〔10241024〕;}}});}}
在这段代码中,每次点击按钮时都会创建100,000个大约为1MB的数组,如果内存不够用,则可能导致OOM错误。请注意,实际应用中应避免这种不负责任的内存使用行为。
第二,不连续的内存片无法被分配,导致OOM;publicclassMainActivityextendsAppCompatActivity{privateButtonmButton;privateArrayListbyte〔〕mDataList;OverrideprotectedvoidonCreate(BundlesavedInstanceState){super。onCreate(savedInstanceState);setContentView(R。layout。activitymain);mButton(Button)findViewById(R。id。button);mButton。setOnClickListener(newView。OnClickListener(){OverridepublicvoidonClick(Viewview){mDataListnewArrayList();for(inti0;i100000;i){频繁创建大量的对象byte〔〕datanewbyte〔10241024〕;mDataList。add(data);}}});}}
在这段代码中,每次点击按钮时都会创建大量的1MB大小的数组,并将它们添加到mDataList中。由于内存是不连续的,因此在较大的数组中分配这些不连续的内存片可能导致OOM错误。请注意,实际应用中应避免这种不负责任的内存使用行为。1。3。1。4。3内存抖动解决
这里假设有这样一个场景:点击按钮使用Handler发送空消息,Handler的handleMessage方法接收到消息后会导致内存抖动
for循环创建100个容量为10w的string〔〕数组在30ms后继续发送空消息。使用MemoryProfiler结合代码可找到内存抖动出现的地方。查看循环或频繁调用的地方即可。publicclassMainActivityextendsAppCompatActivity{privateButtonmButton;privateHandlermHandler;OverrideprotectedvoidonCreate(BundlesavedInstanceState){super。onCreate(savedInstanceState);setContentView(R。layout。activitymain);mButton(Button)findViewById(R。id。button);mButton。setOnClickListener(newView。OnClickListener(){OverridepublicvoidonClick(Viewview){mHandler。sendEmptyMessage(0);}});mHandlernewHandler(){OverridepublicvoidhandleMessage(Messagemsg){for(inti0;i100;i){String〔〕arrnewString〔100000〕;}mHandler。sendEmptyMessageDelayed(0,30);}};}}
请注意,这个代码中的消息循环可能会导致内存泄漏,因此您需要在适当的时候删除消息。1。3。1。4。4内存抖动常见案例
下面列举一些导致内存抖动的常见案例,如下所示:
1。3。1。4。1字符串使用加号拼接实际开发中我们不应该使用字符串使用加号进行拼接,而应该使用StringBuilder来替代。初始化时设置容量,减少StringBuilder的扩容。publicclassMain{publicstaticvoidmain(String〔〕args){使用加号拼接字符串Stringstr;longstartTimeSystem。currentTimeMillis();for(inti0;i100000;i){strstrhello;}System。out。println(使用加号拼接字符串的内存使用量:(Runtime。getRuntime()。totalMemory()Runtime。getRuntime()。freeMemory())(10241024)MB);System。out。println(使用加号拼接字符串的时间:(System。currentTimeMillis()startTime)ms);使用StringBuilderStringBuildersbnewStringBuilder(5);startTimeSystem。currentTimeMillis();for(inti0;i100000;i){sb。append(hello);}System。out。println(使用StringBuilder的内存使用量:(Runtime。getRuntime()。totalMemory()Runtime。getRuntime()。freeMemory())(10241024)MB);System。out。println(使用StringBuilder的时间:(System。currentTimeMillis()startTime)ms);}}输出结果:
使用加号拼接字符串的内存使用量:75MB
使用加号拼接字符串的时间:4561ms
使用StringBuilder的内存使用量:77MB
使用StringBuilder的时间:4ms1。3。1。4。2资源复用
使用全局缓存池,避免频繁申请和释放的对象。publicclassObjectPool{privatestaticObjectPoolinstancenull;privateHashMapString,ObjectpoolnewHashMap();privateObjectPool(){}publicstaticObjectPoolgetInstance(){if(instancenull){instancenewObjectPool();}returninstance;}publicvoidaddObject(Stringkey,Objectobject){pool。put(key,object);}publicObjectgetObject(Stringkey){returnpool。get(key);}publicvoidremoveObject(Stringkey){pool。remove(key);}}
该代码使用单例模式创建了一个ObjectPool类,并实现了添加、获取和删除对象的方法。
当应用程序需要使用某个对象时,可以通过调用ObjectPool。getInstance()。getObject(key)方法从缓存池中获取该对象。
当不再需要该对象时,可以调用removeObject(key)方法将其从缓存池中删除。
但使用后,手动释放对象池中的对象(removeObject这个key)。1。3。1。4。3减少不合理的对象创建onDraw中创建的对象尽量进行复用publicclassCustomViewextendsView{privatePaintpaint;privateRectrect;publicCustomView(Contextcontext){super(context);}OverrideprotectedvoidonDraw(Canvascanvas){super。onDraw(canvas);重复创建对象,导致内存抖动paintnewPaint();rectnewRect();paint。setColor(Color。RED);paint。setStyle(Paint。Style。FILL);rect。set(0,0,getWidth(),getHeight());canvas。drawRect(rect,paint);}}publicclassMainActivityextendsAppCompatActivity{OverrideprotectedvoidonCreate(BundlesavedInstanceState){super。onCreate(savedInstanceState);重复创建对象,导致内存抖动setContentView(newCustomView(this));}}
上面的代码中,在CustomView的onDraw方法和MainActivity的onCreate方法中,每次都重新创建了Paint和Rect对象,这会导致内存波动,因为系统并不能回收之前创建的对象。
为了避免这种情况,我们可以将Paint和Rect对象声明为类变量,并在构造方法中初始化,以保证只创建一次:publicclassCustomViewextendsView{privatePaintpaint;privateRectrect;publicCustomView(Contextcontext){super(context);初始化对象paintnewPaint();rectnewRect();}OverrideprotectedvoidonDraw(Canvascanvas){super。onDraw(canvas);paint。setColor(Color。RED);paint。setStyle(Paint。Style。FILL);rect。set(0,0,getWidth(),getHeight());canvas。drawRect(rect,paint);}}publicclassMainActivityextendsAppCompatActivity{OverrideprotectedvoidonCreate(BundlesavedInstanceState){super。onCreate(savedInstanceState);setContentView(newCustomView(this));}}
每次创建局部变量时,内存都会分配给它,但在循环结束后,它们不会被立即回收。这将导致内存的不断增加,最终导致内存抖动。避免在循环中不断创建局部变量错误示例for(inti0;i100000;i){BitmapbitmapBitmapFactory。decodeResource(getResources(),R。drawable。largeimage);}正确示例Bitmapbitmap;for(inti0;i100000;i){bitmapBitmapFactory。decodeResource(getResources(),R。drawable。largeimage);bitmap。recycle();}
在这个例子中,每次循环都会创建一个Bitmap对象,并将其赋值给局部变量bitmap。但是,循环结束后,Bitmap对象不会被立即回收,因此内存不断增加。1。3。1。4。4使用合理的数据结构
使用SparseArray类族、ArrayMap来替代HashMap。publicclassMain{publicstaticvoidmain(String〔〕args){intN100000;CreateaSparseArraySparseArrayIntegersparseArraynewSparseArray();for(inti0;iN;i){sparseArray。put(i,i);}System。out。println(SparseArraysize:sparseArray。size());System。gc();longmemorySparseArrayRuntime。getRuntime()。totalMemory()Runtime。getRuntime()。freeMemory();CreateanArrayMapArrayMapInteger,IntegerarrayMapnewArrayMap();for(inti0;iN;i){arrayMap。put(i,i);}System。out。println(ArrayMapsize:arrayMap。size());System。gc();longmemoryArrayMapRuntime。getRuntime()。totalMemory()Runtime。getRuntime()。freeMemory();CreateaHashMapHashMapInteger,IntegerhashMapnewHashMap();for(inti0;iN;i){hashMap。put(i,i);}System。out。println(HashMapsize:hashMap。size());System。gc();longmemoryHashMapRuntime。getRuntime()。totalMemory()Runtime。getRuntime()。freeMemory();System。out。println(Memoryusage:);System。out。println(SparseArray:memorySparseArray1024。0KB);System。out。println(ArrayMap:memoryArrayMap1024。0KB);System。out。println(HashMap:memoryHashMap1024。0KB);}}1。3。4内存泄漏
Android系统虚拟机的垃圾回收是通过虚拟机GC机制来实现的。GC会选择一些还存活的对象作为内存遍历的根节点GCRoots,通过对GCRoots的可达性来判断是否需要回收。
内存泄漏是在当前应用周期内不再使用的对象被GCRoots引用,导致不能回收,使实际可使用内存变小。
对象被持有导致无法释放或不能按照对象正常的生命周期进行释放,内存泄漏导致可用内存减少和频繁GC,从而导致内存溢出,App卡顿。publicclassMainActivityextendsAppCompatActivity{privateListBitmapbitmapsnewArrayList();OverrideprotectedvoidonCreate(BundlesavedInstanceState){super。onCreate(savedInstanceState);setContentView(R。layout。activitymain);不断加载图片并加入到List中while(true){BitmapbitmapBitmapFactory。decodeResource(getResources(),R。drawable。largeimage);bitmaps。add(bitmap);}}}
在上面的代码中,每次加载图片并加入到List中都不会释放内存,因为List引用了这些图片,导致图片无法释放,最终造成内存溢出。为了避免内存溢出,你可以考虑使用低内存占用的图片格式,或者在不需要使用图片时主动调用recycle方法释放图片的内存。1。3。4内存溢出
OOM,OOM时会导致程序异常。Android设备出厂以后,java虚拟机对单个应用的最大内存分配就确定下来了,超出值就会OOM。
单个应用可用的最大内存对应于systembuild。prop文件中的dalvik。vm。heapgrowthlimit。
此外,除了因内存泄漏累积到一定程度导致OOM的情况以外,也有一次性申请很多内存,比如说一次创建大的数组或者是载入大的文件如图片的时候会导致OOM。而且,实际情况下很多OOM就是因图片处理不当而产生的。publicclassMainActivityextendsAppCompatActivity{privateImageViewimageView;OverrideprotectedvoidonCreate(BundlesavedInstanceState){super。onCreate(savedInstanceState);setContentView(R。layout。activitymain);imageViewfindViewById(R。id。imageview);试图创建大的数组int〔〕largeArraynewint〔Integer。MAXVALUE〕;或者试图载入大的图片BitmaplargeBitmapBitmapFactory。decodeResource(getResources(),R。drawable。largeimage);imageView。setImageBitmap(largeBitmap);}}三、内存管理机制3。1ARTDalvik虚拟机
ART和Dalvik虚拟机使用分页和内存映射来管理内存。ART和Dalvik虚拟机有什么区别呢?
Dalvik是Android系统首次推出的虚拟机,它是一个字节码解释器,把Java字节码转换为机器码执行。由于它的设计历史和硬件限制,它的性能较差,但是可以很好地支持多个Android设备。
而ART则是Android4。4(KitKat)发布后推出的一种新的Java虚拟机,它把Java字节码编译成机器码,在安装应用时一次性编译,因此不需要在运行时解释字节码,提高了性能。ART的编译技术带来了更快的应用启动速度和更低的内存消耗。
因此,ART相比Dalvik,在性能和稳定性方面有了很大的提升,但是由于ART把字节码编译成机器码,因此空间占用更大,对于一些低内存的设备来说可能不太适用。
说到这两种虚拟机我们不得不提到LMK(LowMemorykiller)3。2LMK内存管理机制
LMK(LowMemoryKiller)是Android系统内存管理机制中的一部分,LMK是用来在内存不足时释放系统中不必要的进程,以保证系统的正常运行。
LMK机制的底层原理是利用内核OOM(OutofMemory)机制来管理内存。当系统内存不足时,内核会根据各进程的优先级将内存分配给重要的进程,同时会结束一些不重要的进程,以避免系统崩溃。
LMK机制的使用场景包括:系统内存不足:当系统内存不足时,LMK机制会帮助系统管理内存,以保证系统正常运行。内存泄漏:当应用存在内存泄漏时,LMK机制会将泄漏的内存释放掉,以保证系统正常运行。进程优化:LMK机制可以帮助系统管理进程,以确保系统资源的合理利用。
在系统内存紧张的情况下,LMK机制可以通过结束不重要的进程来释放内存,以保证系统的正常运行。但是,如果不当使用,它也可能导致应用程序的不稳定。因此,开发者需要合理设计应用程序,避免内存泄露。
下面先从Java的内存分配开始说起。3。3Java内存分配
Java的内存分配区域分为如下五部分:
3。4Java内存回收算法
3。4。1标记清除算法
标记清除算法是最早的内存回收算法,其工作原理是标记出不再使用的对象并将其回收。标记清除算法步骤
标记所有存活的对象。统一回收所有未被标记的对象。标记清除算法优点
实现比较简单。标记清除算法缺点标记、清除效率不高。产生大量内存碎片。3。4。2复制算法
复制算法是一种将内存分为两个区域的算法,其中一个区域用于存储活动对象,另一个区域用于存储不再使用的对象。复制算法步骤
将内存划分为大小相等的两块。一块内存用完之后复制存活对象到另一块。清理另一块内存。复制算法优点
实现简单,运行高效,每次仅需遍历标记一半的内存区域。复制算法缺点
会浪费一半的空间,代价大。3。4。3标记整理算法
标记整理算法是标记清除算法和复制算法的结合,其工作原理是先标记出不再使用的对象,再整理内存使得活动对象的内存分配连续标记整理算法步骤
标记过程与标记清除算法一样。存活对象往一端进行移动。清理其余内存。标记整理算法优点避免标记清除导致的内存碎片。避免复制算法的空间浪费。标记整理算法缺点时间开销:标记整理算法需要进行两次扫描,一次标记活动对象,一次整理内存,这增加了时间开销。空间开销:由于标记整理算法需要为活动对象留出足够的空间,因此必须移动内存中的一些对象,这会增加空间开销。内存碎片:标记整理算法在整理内存时可能会产生内存碎片,使得未使用的内存碎片不能被有效利用。速度慢:相对于其他垃圾回收算法,标记整理算法的速度较慢,因此不适合需要高效内存管理的场景。效率不稳定:标记整理算法效率受到内存使用情况的影响,如果内存使用情况不均衡,效率会不稳定。3。4。4分代收集算法
分代回收算法是一种将内存分为几个代的算法,并对每个代进行不同的回收策略分代收集算法步骤
分配新的对象:新创建的对象分配在新生代中,因为大多数新创建的对象都很快失效,并且删除它们的成本很低。垃圾回收:新生代中的垃圾对象被回收,并且回收算法只涉及到新生代的一小部分。如果一个对象存活到一定时间,它将被移动到老年代。老年代回收:在老年代中,回收算法进行全面的垃圾回收,以确保可以回收所有垃圾对象。整理内存:回收后,内存被整理,以确保连续的内存空间可以分配给新对象。
主流的虚拟机一般用的比较多的是分代收集算法。分代收集算法优点减少垃圾回收的时间:通过将新生代和老年代分开,分代收集算法可以减少垃圾回收的时间,因为新生代中的垃圾对象被回收的频率较高。减少内存碎片:因为新生代的垃圾回收频率较高,分代收集算法可以防止内存碎片的产生。提高内存利用率:分代收集算法可以有效地回收垃圾对象,提高内存的利用率。减少内存消耗:分代收集算法可以减少对内存的消耗,因为它仅需要涉及小的内存区域,而不是整个Java堆。提高系统性能:分代收集算法可以提高系统性能,因为它可以缩短垃圾回收的时间,提高内存利用率,减少内存消耗。分代收集算法缺点复杂性:分代收集算法相对于其他垃圾回收算法来说更复杂,需要更多的内存空间来管理垃圾回收。内存分配不均衡:分代收集算法可能导致内存分配不均衡,这可能导致新生代内存不足,老年代内存过多。垃圾对象转移次数:分代收集算法需要移动垃圾对象,这可能导致更多的计算开销。时间开销:分代收集算法需要更长的时间来管理垃圾回收,这可能导致系统性能下降。停顿时间:分代收集算法可能导致长时间的停顿,这可能影响系统的实时性。3。4。5内存回算法使用推荐
在Java中,两种常用的内存回收算法分别是新生代回收算法和老年代回收算法。
新生代回收算法推荐场景:对象生命周期短:适用于那些生命周期短的对象,因为它们在很短的时间内就会被回收。大量生成对象:对于大量生成对象的场景,新生代回收算法可以有效地减少回收时间。
老年代回收算法推荐场景:对象生命周期长:适用于生命周期长的对象,因为它们不会很快被回收。内存数据稳定:对于内存数据稳定的场景,老年代回收算法可以提高内存效率。
请注意,这是基于Java的默认内存回收算法(即垃圾回收器)的推荐使用场景。您可以通过配置JVM参数来更改这些默认设置,以适应您的特定需求。3。5Java内存管理
Android中的内存是弹性分配的,分配值与最大值受具体设备影响。
对于OOM场景其实可以细分为如下两种:
可用(被分配的)内存不足:指系统已经分配了足够的内存,但是由于程序或者其他应用程序的需求,系统中的可用(被分配的)内存不足以支持当前的运行。内存真正不足:指系统中内存总量不足以支持程序的运行,即系统总内存实际上不够用。
因此,在解决内存不足的问题时,需要首先判断是可用(被分配的)内存不足还是内存真正不足,并根据相应情况采取适当的措施。
如果是可用(被分配的)内存不足,可以通过调整程序的内存配置或者关闭其他应用程序来解决问题。
如果是内存真正不足,则需要通过升级内存或者更换计算机等方式来解决问题。3。6Java引用类型
JVM场景的引用类型有四种,分别是强引用、软引用、软引用和虚引用
强引用、软引用、软引用和虚引用的本质区别可以参考如下表:
引用类型GC回收时间用途生存时间强引用永不对象的一般状态JVM停止运行时软引用内存不足时对象缓存内存不足时终止弱引用GC对象缓存GC后终止虚引用未知未知未知强引用强引用概念
强引用是Java中最常见的引用类型,当对象具有强引用时,它永远不会被垃圾回收。只有在程序结束或者手动将对象设置为null时,才会释放强引用。强引用案例publicclassStrongReferenceExample{publicstaticvoidmain(String〔〕args){ArrayListStringdatanewArrayList();data。add(Hello);data。add(World);创建强引用ArrayListStringstrongReferencedata;System。out。println(Databeforegarbagecollection:strongReference);断开data引用,使其可以被回收datanull;System。gc();System。out。println(Dataaftergarbagecollection:strongReference);}}输出结果:
Databeforegarbagecollection:〔Hello,World〕
Dataaftergarbagecollection:〔Hello,World〕
在代码中,我们创建了一个ArrayList对象data,并通过赋值语句将它的引用赋给了变量strongReference,此时,strongReference和data将指向同一个对象。
在之后的代码中,我们断开了data的引用,让其变成可回收对象,但因为strongReference仍然保持着对该对象的强引用,所以该对象在GC后仍然不会被回收。弱引用弱引用概念
一种用于追踪对象的引用,不会对对象的生命周期造成影响。在内存管理方面,弱引用不被认为是对象的有效引用。
因此,如果一个对象只被弱引用指向,那么在垃圾回收的时候,这个对象可能会被回收掉。
弱引用常被用来在内存敏感的应用中实现对象缓存。在这种情况下,弱引用可以让缓存的对象在内存不足时被回收,从而避免内存泄漏。弱引用案例publicclassWeakReferenceExample{publicstaticvoidmain(String〔〕args){StringdatanewString(Hello);创建弱引用WeakReferenceStringweakReferencenewWeakReference(data);System。out。println(Databeforegarbagecollection:weakReference。get());断开data引用,使其可以被回收datanull;System。gc();System。out。println(Dataaftergarbagecollection:weakReference。get());}}输出结果:
Databeforegarbagecollection:Hello
Dataaftergarbagecollection:null
在代码中,我们创建了一个字符串对象data,并通过创建WeakReference对象并将data作为参数来创建弱引用。
在之后的代码中,我们断开了data的引用,让其变成可回收对象,但因为weakReference仅持有对该对象的弱引用,所以当JVM进行GC时该对象可能会被回收。
可以通过weakReference。get方法来检查对象是否被回收。
如果对象已被回收,则weakReference。get()返回null。软引用软引用概念
软引用是比强引用更容易被回收的引用类型。当Java堆内存不足时,软引用可能会被回收,以腾出内存空间。如果内存充足,则软引用可以继续存在。软引用案例publicclassSoftReferenceExample{publicstaticvoidmain(String〔〕args){ObjectreferentnewObject();SoftReferenceObjectsoftReferencenewSoftReference(referent);referentnull;System。gc();软引用可以在内存不足时被回收System。out。println(softReference。get());}}输出结果:
java。lang。Object2f92e0f4
这段代码创建了一个Object的实例,并使用它作为SoftReference的引用对象。
然后,它将该实例设置为null,并试图强制进行垃圾回收。如果内存不足,软引用会被回收,并且可以从softReference获取的对象将为null。虚引用虚引用概念
虚引用是Java中最弱的引用类型,对于虚引用,对象只存在于垃圾回收的最后阶段,在这个阶段,对象将被回收,而无论内存是否充足。虚引用主要用于监测对象被回收的状态,而不是用于缓存对象。虚引用案例publicclassPhantomReferenceExample{publicstaticvoidmain(String〔〕args){ObjectreferentnewObject();ReferenceQueueObjectreferenceQueuenewReferenceQueue();PhantomReferenceObjectphantomReferencenewPhantomReference(referent,referenceQueue);referentnull;System。gc();虚引用在回收前不会被加入引用队列,但在回收时会被加入引用队列System。out。println(referenceQueue。poll()phantomReference);}}输出结果:
false
这段代码创建了一个Object的实例,并使用它作为PhantomReference的引用对象。
然后,它将该实例设置为null,并试图强制进行垃圾回收。如果垃圾回收发生,虚引用会被加入引用队列,从而可以从引用队列中获取。四、内存优化SOP
分析现状
如果发现APP在内存方面可能存在很大的问题,第一方面的原因是线上的OOM率比较高。
第二方面的原因是经常会看到在AndroidStudio的Profiler工具中内存的抖动比较频繁。确认问题
这是一个初步的现状,然后在知道了初步的现状之后,进行了问题的确认,经过一系列的调研以及深入研究,最终发现项目中存在以下几点大问题,比如说:内存抖动、内存溢出、内存泄漏,还有Bitmap粗犷使用。问题优化
如果想解决内存抖动,MemoryProfiler会呈现了锯齿张图形,然后我们分析到具体代码存在的问题(频繁被调用的方法中出现了日志字符串的拼接),就能解决内存泄漏或内存溢出。体验提升
为了不增加业务工作量,使用一些工具类或ARTHook大图检测方案,没有任何的侵入性。同时,将技术进行团队分享,团队的工作效率上会有本质提升。
对内存优化工具如ProfilerMemory、MAT的使用,可以针对一系列不同问题的情况,写一系列解决方案文档,整个团队成员的内存优化意识会更强。五、内存优化指导原则
万事俱备水滴石穿
做内存优化首先应该学习Google内存方面的文档,如MemoryProfiler、MAT等工具的使用,当在工程遇到内存问题,才能对问题进行排查定位。而不是一开始并没有分析项目代码导致内存高占用问题,就依据自己看的几篇企业博客,不管业务背景,瞎猫碰耗子做内存优化。结合业务优化内存
如果不结合业务背景,直接对APP运行阶段进行内存上报然后内存消耗进行内存监控,那么内存监控一旦不到位,比如存在使用多个图片库,因为图片库内存缓存不公用的,应用内存占用效率不会有质的飞跃。因此技术优化必须结合业务。解决方案系统科学
在做内存优化的过程中,Android业务端除了要做优化工作,Android业务端还得负责数据采集上报,数据上报到APM后台后,无论是Bug追踪人员或者Crash追踪人员,对问题回码定位都提供好的依据。内存劣化Hook魔改
大图片检测方案,大家可能想到去是继承ImageView,然后重写ImageView的onDraw方法实现。但是,在推广的过程中,因为耦合度过高,业务同学很难认可,ImageView之前写一次,为什么要重复造轮子呢?替换成本非常高。所以我们可以考虑使用类似ARTHook这样的Hook方案。六、总结与展望
内存优化、启动优化、卡顿优化、包体积优化是Android性能优化四驾马车,而内存优化又是四驾马车最难驾驭的一驾,如果你掌握了这项基础技能,那么你将超过绝对多数的Android开发
内存优化基础论初识Android内存优化我们讲解了五部分内容,第一部分内容是5W2H分析内存优化,第二部分内容是内存管理机制,第三部分内容是内存优化SOP,第四部分内容是内存优化指导原则,最后一部分内容是总结与展望。
下一节,小木箱将带大家深入学习内存优化工具论常见的内存优化工具和框架。
我是小木箱,如果大家对我的文章感兴趣,那么欢迎关注小木箱的公众号小木箱成长营。小木箱成长营,一个专注移动端分享的互联网成长社区。
参考资料抖音Android性能优化系列:Java内存优化篇抖音Android性能优化系列:JavaOOM优化之NativeBitmap方案拯救OOM!字节自研Android虚拟机内存管理优化黑科技mSponge腾讯游戏学院专家:UE手游研发中,如何做好Android内存优化?深入探索Android内存优化(炼狱级别上)深入探索Android内存优化(炼狱级别下)微信Android终端内存优化实践Android内存泄露自动化链路分析组件内存优化4GB内存时代,再谈内存优化
沂河源开启乡村文化振兴新模式!桃花岛文化艺术乡村系列活动举办淄博日报淄博晚报博览新闻记者丁兆云金风送爽,丹桂飘香。9月16日至17日,由桃花岛艺术乡村文化旅游发展有限公司桃花岛文化艺术乡村研究院共同举办的桃花岛文化艺术乡村系列活动在沂源县鲁
行走河南读懂中国濮阳大手笔勾画旅游新业态发展蓝图城市高楼林立,每日忙碌不停,你有多久没有停下脚步彻底放松回归自然了?正是这句广告语,让河南濮阳的冯女士决定着眼于家乡美景,来一次说走就走的露营。露营地点选在了濮阳市郊区的一家智慧农
65岁的李东生难救TCL文丨周俊出品丨牛刀财经(niudaocaijing)现年65岁的李东生,不得不面临一个新的挑战。今年上半年,TCL科技创始人李东生在2021年度业绩沟通会上表示,显示面板下行周期已
杨紫的9幅美照,每幅都是珍藏款1。爱情是春天花朵的芬芳,爱情是夏日高照的艳阳,爱情是秋季丹桂的飘香,爱情是冬夜雪花的飞扬。美好的爱情,愿你早日拥有!2。刘海久了以后,突然撩起来上街会特别没有安全感,总觉得别人都
2022,应该抱一抱!以前我们认为,长大后会有更多的爱与自由,可后来才发现,我们需要做的是和解还是满怀信心的希望,我们不要为眼前的黑暗所迷惑,你要相信自己,配得上世间一切的美好,星河滚烫这世界对你不够温
LG希望其LTPOOLED面板能通过苹果审查,以尽快完成首批供应目标此前有报道称,随着定位高端的iPhone14Pro和iPhone14ProMax需求量不断增加,苹果有意加大三星的订单量,为这两款机型生产更多的LTPOOLED面板。为了满足苹果订
65岁的李东生难救TCL文丨周俊出品丨牛刀财经(niudaocaijing)现年65岁的李东生,不得不面临一个新的挑战。今年上半年,TCL科技创始人李东生在2021年度业绩沟通会上表示,显示面板下行周期已
杨紫的9幅美照,每幅都是珍藏款1。爱情是春天花朵的芬芳,爱情是夏日高照的艳阳,爱情是秋季丹桂的飘香,爱情是冬夜雪花的飞扬。美好的爱情,愿你早日拥有!2。刘海久了以后,突然撩起来上街会特别没有安全感,总觉得别人都
2022,应该抱一抱!以前我们认为,长大后会有更多的爱与自由,可后来才发现,我们需要做的是和解还是满怀信心的希望,我们不要为眼前的黑暗所迷惑,你要相信自己,配得上世间一切的美好,星河滚烫这世界对你不够温
LG希望其LTPOOLED面板能通过苹果审查,以尽快完成首批供应目标此前有报道称,随着定位高端的iPhone14Pro和iPhone14ProMax需求量不断增加,苹果有意加大三星的订单量,为这两款机型生产更多的LTPOOLED面板。为了满足苹果订
文案总有一句话,字字戳心,懂你却又让你泪流满面1我爱你,爱了整整一个曾经,从无知到成熟,从冲动到沉静。感谢你,曾赐我一场空欢喜。2感情这种东西真的奇怪,刚开始是喜欢到不行,后来只剩下不行。3每个人都想,苦了,有人疼。久了,有人