【小木箱生长营】内存优化系列文章:
内存优化 · 东西论 · 常见的 Android 内存优化东西和结构
内存优化 · 办法论 · 揭开内存优化神秘面纱
内存优化 · 实战论 · 内存优化实践与运用
Tips: 重视微信大众号小木箱生长营,回复”内存优化”可免费取得内存优化思想导图
一、序文
Hello,我是小木箱,欢迎来到小木箱生长营系列教程,今日将共享内存优化 · 根底论 · 初识 Android 内存优化。
本次共享首要分为五个部分内容,榜首部分内容是 5W2H 剖析内存优化,第二部分内容是内存办理机制,第三部分内容是内存优化 SOP,第四部分内容是 内存优化辅导准则, 终究一部分内容是总结与展望。
假如学完小木箱内存优化的根底论、东西论、办法论和实战论,那么任何人做内存优化都能够拿到成果。
二、5W2H 剖析内存优化
首要咱们说说咱们的榜首部分内容,5W2H 剖析内存优化,5W2H 剖析内存优化提出了 7 个高价值问题
-
What: 内存优化界说
-
Why: 内存优化原因
-
How: 内存优化归因
-
Who: 内存优化维度
-
When: 内存优化机遇
-
How Much: 内存优化价值
-
Where: 内存痛点定位
What: 内存优化界说
Android 内存优化是指优化 Android 运用程序的内存运用,以削减可用内存的耗费,进步运用程序的功能和可靠性。Android 内存优化能够经过削减内存运用量,削减对资源的耗费,以及进步内存运用率来完成。
Why: 内存优化原因
安卓体系对每个运用程序都有必定的内存约束,当运用程序的内存超越了上限,就会呈现 OOM (Out of Memory),也便是 App的反常退出。
因而,要改进体系的运转功率、改进用户体会、下降体系资源占用、延伸电池寿数、下降体系故障的危险。
Android经过内存优化,能够削减体系内存运用,让体系愈加流通,运转更快,削减体系Crash,进步用户体会。
How: 内存优化归因
关于运用内存剖析,需求重点重视四个阶段
-
运用停留在闪屏页面内存固定值
-
运用的MainActivity到HomeActivty内存动摇值
-
运用运转十分钟后回归到HomeActivty内存动摇值
-
运用内存运用量分配值汇总
Android 给每个运用进程分配的内存都是十分有限的,那么,为什么不能把图片下载下来都放到磁盘中呢?
由于放在内存中,展现会更“快”,快的原因两点:
-
硬件快:内存自身读取、存入速度快。
-
复用快:解码效果有用保存,复用时,直接运用解码后方针,而不是再做一次图画解码。
那么,问题来了,什么是解码呢?
Android 体系要在屏幕上展现图片的时分只默许“像素缓冲”,而这也是大多数操作体系的特征。jpg,png 等图片格局,是把“像素缓冲”运用不同的手段压缩后的成果。
不同格局的图片,在设备上展现,有必要经过一次解码,执行速度会受图片压缩比、尺度等要素影响。
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 DDR4=1600MHZ✖️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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button) findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public 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 ArrayList<byte[]> mDataList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button) findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public 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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button) findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mHandler.sendEmptyMessage(0);
}
});
mHandler = new Handler() {
@Override
public 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");
// 运用StringBuilder
StringBuilder 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 HashMap<String, 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);
}
@Override
protected 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 {
@Override
protected 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();
}
@Override
protected 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 {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new CustomView(this));
}
}
每次创立局部变量时,内存都会分配给它,但在循环完毕后,它们不会被立即收回。这将导致内存的不断添加,终究导致内存颤动。
防止在循环中不断创立局部变量
//----------------------------过错示例---------------------------
for(int i=0;i< 100000;i++){
Bitmap bitmap=BitmapFactory.decodeResource(getResources(),R.drawable.large_image);
}
//----------------------------正确示例---------------------------
Bitmap bitmap;
for(int i=0;i< 100000;i++){
bitmap=BitmapFactory.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 SparseArray
SparseArray<Integer> 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 ArrayMap
ArrayMap<Integer, 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 HashMap
HashMap<Integer, 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 List<Bitmap> bitmaps = new ArrayList<>();
@Override
protected 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 内存溢出
OOM,OOM 时会导致程序反常。Android 设备出厂今后,java 虚拟机对单个运用的最大内存分配就承认下来了,超出值就会 OOM。
单个运用可用的最大内存对应于 /system/build.prop 文件中的 dalvik.vm.heap growth limit。
此外,除了因内存走漏累积到必定程度导致 OOM 的状况以外,也有一次性请求许多内存,比如说一次创立大的数组或许是载入大的文件如图片的时分会导致 OOM。而且,实践状况下许多 OOM 便是因图片处理不妥而产生的。
public class MainActivity extends AppCompatActivity {
private ImageView imageView;
@Override
protected 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 ART&Dalvik 虚拟机
ART 和 Dalvik 虚拟机运用分页和内存映射来办理内存。ART 和 Dalvik 虚拟机有什么区别呢?
Dalvik 是 Android 体系初次推出的虚拟机,它是一个字节码解说器,把 Java 字节码转换为机器码执行。由于它的设计历史和硬件约束,它的功能较差,可是能够很好地支撑多个 Android 设备。
而 ART 则是 Android 4.4(KitKat)发布后推出的一种新的 Java 虚拟机,它把 Java 字节码编译成机器码,在装置运用时一次性编译,因而不需求在运转时解说字节码,进步了功能。ART 的编译技术带来了更快的运用发动速度和更低的内存耗费。
因而,ART 比较 Dalvik,在功能和安稳性方面有了很大的进步,可是由于 ART 把字节码编译成机器码,因而空间占用更大,关于一些低内存的设备来说或许不太适用。
说到这两种虚拟机咱们不得不提到 LMK(Low Memory killer)
3.2 LMK 内存办理机制
LMK(Low Memory Killer)是 Android 体系内存办理机制中的一部分,LMK 是用来在内存缺乏时开释体系中不必要的进程,以保证体系的正常运转。
LMK 机制的底层原理是运用内核 OOM(Out-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) {
ArrayList<String> data = new ArrayList<>();
data.add("Hello");
data.add("World");
// 创立强引证
ArrayList<String> 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");
// 创立弱引证
WeakReference<String> 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();
SoftReference<Object> softReference = new SoftReference<>(referent);
referent = null;
System.gc();
// 软引证能够在内存缺乏时被收回
System.out.println(softReference.get());
}
}
输出成果:
状况1: java.lang.Object@2f92e0f4
状况2: null
这段代码创立了一个 Object
的实例,并运用它作为 SoftReference
的引证方针。
然后,它将该实例设置为 null
,并企图强制进行废物收回。假如内存缺乏,软引证会被收回,而且能够从 softReference
获取的方针将为 null
。
虚引证
虚引证概念
虚引证是 Java 中最弱的引证类型,关于虚引证,方针只存在于废物收回的终究阶段,在这个阶段,方针将被收回,而不管内存是否充足。虚引证首要用于监测方针被收回的状况,而不是用于缓存方针。
虚引证事例
public class PhantomReferenceExample {
public static void main(String[] args) {
Object referent = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
PhantomReference<Object> 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 功能优化系列: Java 内存优化篇
-
抖音 Android 功能优化系列:Java OOM 优化之 NativeBitmap 计划
-
拯救 OOM!字节自研 Android 虚拟机内存办理优化黑科技 mSponge
-
腾讯游戏学院专家:UE 手游研发中,如何做好 Android 内存优化?
-
深化探究 Android 内存优化(炼狱等级-上)
-
深化探究 Android 内存优化(炼狱等级-下)
-
微信 Android 终端内存优化实践
-
Android 内存走漏自动化链路剖析组件
-
内存优化-4GB 内存时代,再谈内存优化