问题布景
假设咱们有一个运用,它的功用是在一个TextView上显现一个计数器,每隔一秒钟就更新一次计数器的值。为了完成这个功用,咱们运用了一个Handler来发送空音讯,并在接收到音讯时更新计数器的值,并再次发送空音讯,构成一个循环。一起,为了模仿一些杂乱的事务逻辑,咱们在循环中创立了很多的数组目标。以下是咱们的代码:
public class MainActivity extends AppCompatActivity {
// 界说一个TextView来显现计数器的值
private TextView textView;
// 界说一个计数器变量
private int counter = 0;
// 界说一个Handler目标
private Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
// 更新计数器的值
counter++;
// 在TextView上显现计数器的值
textView.setText(String.valueOf(counter));
// 模仿一些杂乱的事务逻辑,创立很多的数组目标
for (int i = 0; i < 1000; i++) {
int[] array = new int[1000];
array[0] = i;
}
// 再次发送空音讯,构成一个循环
handler.sendEmptyMessage(0);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化TextView
textView = findViewById(R.id.textView);
// 发送空音讯,发动循环
handler.sendEmptyMessage(0);
}
}
这段代码看起来没有什么问题,可是当咱们运行这个运用时,咱们会发现运用的内存运用情况十分不稳定,内存曲线呈现出明显的锯齿状,GC事情也十分频频。这便是典型的内存颤动的现象。
问题定位
要定位这个问题,咱们能够运用Memory Profiler来调查运用的内存运用情况。Memory Profiler是Android Studio中的一个工具,它能够实时显现运用的内存运用情况,包括内存分配、收回、走漏等。经过Memory Profiler,咱们能够发现运用存在明显的内存动摇和GC频率。
以下是一个运用Memory Profiler调查运用内存运用情况的示例图:
从示例图中能够看出:
- Memory Usage:这个图表显现了运用在不一起间点的内存运用量,以及不同类型的内存(如Java Heap、Native Heap、Graphics等)。从图表中能够看出,运用的Java Heap内存运用量呈现出明显的动摇性,上下起伏,构成锯齿状。这说明运用存在很多短期存在的目标,导致内存分配和收回不平衡。
- GC Events:这个图表显现了运用在不一起间点产生的GC事情,以及GC事情的类型(如GC Alloc、GC Concurrent等)。从图表中能够看出,运用产生了十分频频的GC事情,几乎每隔一秒钟就会产生一次GC事情。这说明运用的GC压力十分大,GC需要耗费更多的CPU资源,并且影响运用线程的履行。
从这里从头输出,我会继续为你介绍内存颤动的处理实战。接下来,我将从以下几个方面给你展示怎么运用Memory Profiler和代码排查来剖析和处理一个详细的内存颤动问题:
问题剖析
要剖析这个问题,咱们能够运用Memory Profiler来捕获堆转储,并依据目标分配盯梢来找出导致内存颤动的代码方位和原因。堆转储是一种保存运用在某个时刻点的内存快照的文件,它能够用来检查运用中存在的一切目标,以及它们的类型、巨细、数量等。目标分配盯梢是一种记录运用在一段时刻内创立的一切目标,以及它们的类型、巨细、数量、调用栈等的功用。经过堆转储和目标分配盯梢,咱们能够发现运用中产生内存颤动的代码方位和原因。
以下是运用Memory Profiler捕获堆转储和目标分配盯梢的示例图:
从示例图中能够看出:
- Heap Dump:这个功用能够让咱们捕获运用在某个时刻点的堆转储文件,并检查其间的内容。从堆转储文件中,咱们能够看到运用中存在的一切类和实例,以及它们的类型、巨细、数量等。经过比照不一起间点的堆转储文件,咱们能够发现哪些类和实例是短期存在的,导致内存动摇。
- Allocation Tracking:这个功用能够让咱们记录运用在一段时刻内创立的一切目标,并检查其间的内容。从目标分配盯梢中,咱们能够看到运用中创立的一切目标,以及它们的类型、巨细、数量、调用栈等。经过剖析目标分配盯梢,咱们能够找出哪些代码方位是创立了很多目标,导致GC频频。
运用这两个功用,咱们能够定位到导致内存颤动的代码方位和原因。经过剖析,咱们发现:
- 在堆转储文件中,咱们发现有很多的int[]数组目标存在于Java Heap中,它们占用了大部分的内存空间,并且在不一起间点的堆转储文件中,它们的数量和巨细都有明显的变化。这说明这些数组目标是短期存在的,导致内存动摇。
- 在目标分配盯梢中,咱们发现有很多的int[]数组目标被创立,它们的类型、巨细、数量都是一致的。经过检查它们的调用栈,咱们发现它们都是在MainActivity的handleMessage办法中创立的。这说明这个办法是创立了很多数组目标,导致GC频频。 接下来,我将从以下几个方面给你展示怎么运用Memory Profiler和代码排查来剖析和处理一个详细的内存颤动问题:
问题处理
要处理这个问题,咱们能够修正代码逻辑,防止在循环中创立很多数组,或许运用目标池来复用数组目标,从而削减内存分配和收回。以下是咱们的优化方案:
- 防止在循环中创立很多数组:假如咱们不需要在循环中创立很多数组,咱们能够将数组的创立放在循环之外,或许运用静态变量或许成员变量来保存数组。这样就能够防止每次循环都创立一个新的数组目标,削减内存分配和收回。例如:
public class MainActivity extends AppCompatActivity {
// 界说一个TextView来显现计数器的值
private TextView textView;
// 界说一个计数器变量
private int counter = 0;
// 界说一个Handler目标
private Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
// 更新计数器的值
counter++;
// 在TextView上显现计数器的值
textView.setText(String.valueOf(counter));
// 模仿一些杂乱的事务逻辑,运用一个静态变量来保存数组目标,防止在循环中创立很多数组目标
for (int i = 0; i < 1000; i++) {
array[0] = i;
}
// 再次发送空音讯,构成一个循环
handler.sendEmptyMessage(0);
}
};
// 界说一个静态变量,用来保存数组目标
private static int[] array = new int[1000];
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化TextView
textView = findViewById(R.id.textView);
// 发送空音讯,发动循环
handler.sendEmptyMessage(0);
}
}
- 运用目标池来复用数组目标:假如咱们需要在循环中创立很多数组,咱们能够运用目标池来办理和复用数组目标,而不是每次都创立和销毁数组目标。当需要一个数组时,能够从目标池中获取一个闲暇的数组,运用完毕后,能够将数组归还到目标池中,等待下次运用。这样就能够防止频频的内存分配和收回,削减GC的压力。例如:
public class MainActivity extends AppCompatActivity {
// 界说一个TextView来显现计数器的值
private TextView textView;
// 界说一个计数器变量
private int counter = 0;
// 界说一个Handler目标
private Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
// 更新计数器的值
counter++;
// 在TextView上显现计数器的值
textView.setText(String.valueOf(counter));
// 模仿一些杂乱的事务逻辑,运用一个目标池来办理和复用数组目标,防止在循环中创立很多数组目标
for (int i = 0; i < 1000; i++) {
// 从目标池中获取一个闲暇的数组目标
int[] array = arrayPool.getArray();
array[0] = i;
// 将数组目标归还到目标池中
arrayPool.returnArray(array);
}
// 再次发送空音讯,构成一个循环
handler.sendEmptyMessage(0);
}
};
// 界说一个目标池,用来办理和复用数组目标
private ArrayPool arrayPool;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化TextView
textView = findViewById(R.id.textView);
// 初始化目标池
arrayPool = new ArrayPool(1000, 1000);
// 发送空音讯,发动循环
handler.sendEmptyMessage(0);
}
}