内存优化,常用到的工具有:DDMS、MAT、LeakCanary、LeakInspector
1、JVM
对于JVM的知识,在《JVM》中已经介绍了许多,就不在此介绍过多。
真正想要内存优化,就是从堆内存优化。
2、强软弱虚
(1)虚引用 PhantomReference
[fæntəm]
虚引用不会影响对象的生命周期,但是可以让程序员知道,该对象什么时候被回收了。
public class PhantomReference<T> extends Reference<T> {
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
throw new RuntimeException("Stub!");
}
public T get() {
throw new RuntimeException("Stub!");
}
}
在PhantomReference
当中,存在一个ReferenceQueue
队列,被回收的对象均存在这个队列中。
ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object object = new Object();
PhantomReference reference = new PhantomReference(object,queue);
System.out.println("object=="+object);
System.out.println("reference=="+reference.get());
System.gc();
System.out.println("queue=="+queue.poll());
结果:
object==java.lang.Object@5b80350b
//使用虚引用,get不到信息的
reference==null
queue==null
当将对象置为空之后,进行垃圾回收,被回收的对象信息将会保存在ReferenceQueue
队列中(注意不是将对象存放在队列中,而是对象的信息)。
object = null;
System.out.println("object=="+object);
System.out.println("reference=="+reference.get());
System.gc();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("queue=="+queue.poll());
结果:
object==null
reference==null
queue==java.lang.ref.PhantomReference@5b80350b
(2)弱引用
弱引用在之前MVP架构中,为了防止内存泄漏,用WeakReference
来引用View对象。
ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object object = new Object();
WeakReference<Object> reference = new WeakReference<>(object,queue);
//获取虚引用对象使用
System.out.println("reference:"+reference.get());
System.out.println("queue:"+queue.poll());
结果:
//可以get到信息
reference:java.lang.Object@5b80350b
queue:null
当启动垃圾回收器的时候。
object = null;
//获取虚引用对象使用
System.out.println("reference:"+reference.get());
System.out.println("queue:"+queue.poll());
System.gc();
Thread.sleep(3000);
System.out.println("reference:"+reference.get());
System.out.println("queue:"+queue.poll());
reference:java.lang.Object@5b80350b
queue:null
reference:null
queue:java.lang.ref.WeakReference@5d6f64b1
所以,使用弱引用就是,只要垃圾回收器监测到了,就会被拿来回收。
弱引用和虚引用的区别:
弱引用和虚引用都是需要配合Reference队列来使用,将对象放入队列中,对于虚引用来说,放在PhantomReference
中的对象是get不到的,所以它无法影响对象的生命周期,但是可以知道对象是什么时候被回收的;而弱引用在WeakReference中是可以被get到的,所以对弱引用对象的生命周期会造成影响,当gc检测到弱引用对象的时候,会被立刻回收(前提是已经执行了finalize方法)。
所以软、弱、虚引用的适用场景:
软引用:处理后台信息,软引用是内存不足才会被回收,所以在处理后台信息时,如果系统内存不足的时候,就选择清理这些后台保存信息;如果内存充足,则可以保存这些信息。
弱引用:GC一来就会被回收;
虚引用:跟踪GC。
3、内存泄漏优化
这里会使用到的工具:Profiler、MAT
(1)Profiler工具
点击Profiler工具,就可以看到当打开APP的时候,内存动态变化的趋势。
其中有一些参数需要注意:
(1)Total Count
:该实例的总个数
(2)Shallow Size
:该实例在内存中的字节数。
在分析一块内存之后,将这个分析结果导出,在内存文件中右击,选择“Export
”导出。
将导出的文件,放到MAT工具中去分析。
(2)MAT工具
安装步骤:打开Eclipse
软件,点击“Help
”—“Install New SoftWare
”,点击Add,将MAT依赖的地址添加进入http://download.eclipse.org/mat/1.6/update-site/
点击Next安装完成。
之前在Profiler中生成的快照内存文件是hprof
格式的文件,需要使用下面这个工具,将其转换为MAT可以使用的文件
去到保存hprof文件的文件夹下,使用命令行工具:
hprof-conv -z 原文件名 mat文件名
导入hprof文件,就可以分析内存。
点击直方图,可以查看每个对象在内存 中的个数。
某个对象,点击右键查询与GcRoot引用的对象,就可以查看被谁引用。
通过直方图可以查看到,对象相互之间的引用关系。
在android.view.inputmethod.InputMethodManager
类中,存在两个对象分别为mServiceView
和,NextServiceView
,他会持有MainActivity的引用,那么我们可以在MainActivity调用onDestory方法之后,将GC链断掉,这样就不会持有MainActivity的引用,防止内存泄漏。
4、内存抖动
内存抖动指的是内存频繁地分配与回收,分配的速度大于回收的速度,最终导致OOM。
同样是通过Profiler工具,就可以定位到内存抖动的位置,如果出现频繁地垃圾回收,如下图那样,就是出现了内存抖动。
在项目开发过程中,出现内存抖动的场景,就是使用字符串的“+=”,在使用+=的时候,每次调用都会new StringBuilder,开辟一块内存,如果频繁地调用,那么就频繁地创建和销毁对象,最终可能导致内存溢出或者ANR异常。
解决方法:在循环外创建StringBuilder对象,在字符串连接的时候,采用append连接字符串。
另一种场景:在使用WebView的时候,就会出现内存泄漏和内存抖动的情况出现。
如果是按照上面的方式 去定位问题出现的位置,发现找不到,因为在app heap中的代码不是自己写的,都是系统的,那怎么去解决这个问题。
一般在使用WebView的时候,都是开一个进程,不在APP的主进程中做任何操作,这样就能解决内存抖动的问题。
将WebView所在的Activity单独开一个进程。
<activity android:name=".view.activity.ArticleInfoActivity" android:process=":P" />
改进后的内存就变为比较顺滑了。
还有就是算法的问题,如果使用递归的方式,那么是非常消耗CPU的性能的,往往也会带来内存抖动的影响,因此考虑优化算法,减少内存抖动。
5、书写代码时注意内存优化
(1)数据类型的使用
举例int
和 float
@Test
public void text(){
long time = System.currentTimeMillis();
int num = 0;
for (int i = 0; i < 10000; i++) {
num += i;
}
long now = System.currentTimeMillis();
System.out.println("time="+(now - time));
}
输出结果:time=5
@Test
public void textInt(){
long time = System.currentTimeMillis();
float num = 0;
for (float i = 0; i < 1000000; i++) {
num += i;
}
long now = System.currentTimeMillis();
System.out.println("time="+(now - time));
}
输出结果:time=8
(2)选择合适的数据结构和算法。
在使用HashMap时,不管是否存储数据,系统都会给HashMap开辟一块内存,因为默认底层数组容量大小是16,所以在数据量小于1000时,使用SparseArray或者ArrayMap代替HashMap能够减小系统内存开销。
在使用HashMap时,如果输入的key是int,那么会将其自动转换为Integer封装类,这是非常消耗性能的;在SparseArray中,保存key和value的是两个数据,分别为保存key的int数组,和保存value的Object数组,所以int的key不会转换为Integer。
ArrayMap同样也是可以代替HashMap的数据结构,在内部也是存在两个数据,一个int数组,存储item的hashcode,另一个数组Object[ ]数组用来存储key/value键值对,它的默认构造方法,默认是0,也就是不会占据内存空间。
但是SparseArray和ArrayMap在查找索引时都是通过二分查找,当数据量特别大的时候,性能就会减半,所以只有在小数据量的情况下,用来代替HashMap,提高内存效率。
(3)枚举优化
在项目开发过程中,如果存在4个状态需要选择,那么往往会采用枚举的形式。
public enum SHAPE{
OVAL,
RECT,
CIRCLE,
SQUARE
}
但是在使用枚举时,系统会开4个栈,这样是非常消耗系统内存,性能很低,所以通常采用自定义注解 + static final的形式来优化。
public class Shape {
public static final int OVAL = 0;
public static final int RECT = 1;
public static final int CIRCLE = 2;
public static final int SQUARE = 3;
//该注解,意味着使用Model注解的地方只能使用这4个状态值
@IntDef(flag = true,value = {OVAL,RECT,CIRCLE,SQUARE})
@Target(value = {ElementType.FIELD,ElementType.PARAMETER,ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Model{
}
private @Model int value = OVAL;
@Model
public int getShape() {
return value;
}
public void setShape(@Model int value) {
this.value = value;
}
}
在使用的时候,就可以自己设置取值,只能取Shape中的4个值,其他的值就会报错。
Shape shape = new Shape();
shape.setShape(Shape.SQUARE);
System.out.println(shape.getShape());
//可以使用或,这是因为@IntDef注解的flag设置为true
shape.setShape(Shape.SQUARE | Shape.CIRCLE);
(4)static 和 static final
static变量,会在类加载的时候完成初始化,会申请方法区内存;
static final不会在类加载的时候初始化,打包到dex文件中可以直接使用,并不会在类初始化的时候申请内存。
因此基本数据类型可以使用static final来修饰。
(5)避免垃圾回收器回收将来可能会重用的对象
这种情况通常需要设计对象池,使用对象池缓存可重复使用的对象。
对象池的思想:可以设置对象池初始容量,存在两个队列,一个是空闲的Pool,一个是正在使用的对象Pool(LintPool),当外界申请某个对象的时候,如果空闲Pool中有对象可用,那么就拿到对象去LintPool中;如果空闲Pool没有可用对象,且没有达到maxCapacity,那么可以生成一个对象,拿到LintPool;反之,就等待有空闲对象即可。
public abstract class ObjectPool<T> {
//存在两个队列
private SparseArray<T> freePool;
private SparseArray<T> lintPool;
//对象池最大个数
private int maxCapacity;
public ObjectPool(int minCapacity,int maxCapacity){
//首先初始化空闲池中的可用对象
init(minCapacity);
this.maxCapacity = maxCapacity;
}
//构造方法初始化
public ObjectPool(int maxCapacity){
this(maxCapacity/2,maxCapacity);
}
private void init(int minCapacity) {
//实例化队列
freePool = new SparseArray<>();
lintPool = new SparseArray<>();
//初始化可用对象
for (int i = 0; i < minCapacity; i++) {
freePool.put(i,create());
}
}
protected abstract T create();
//从freePool取出数据
public T getObject(){
T t = null;
synchronized (freePool){
int freeSize = freePool.size();
for (int i = 0; i < freeSize; i++) {
//取出可用的对象
int key = freePool.keyAt(i);
t = freePool.get(key);
if(t != null){
//如果当前对象池中对象可用,就放到使用池
this.lintPool.put(key,t);
this.freePool.remove(key);
return t;
}
}
//如果空闲池对象可用,空间大小没有超出maxCapacity
if(t == null && freePool.size() + lintPool.size() < maxCapacity){
t = create();
this.lintPool.put(freePool.size() + lintPool.size(),t);
}
}
return t;
}
//使用完毕从lingPool移除
public void remove(T t){
if(t == null){
return;
}
int key = lintPool.indexOfValue(t);
this.lintPool.remove(key);
this.freePool.put(key,t);
}
}
使用:
public class MyObject extends ObjectPool<Object> {
public MyObject(int minCapacity, int maxCapacity) {
super(minCapacity, maxCapacity);
}
//创建对象
@Override
protected Object create() {
return new Object();
}
}
MyObject object = new MyObject(2,4);
Object object1 = object.getObject();
Object object2 = object.getObject();
Log.e("TAG","o1==="+object1.hashCode());
Log.e("TAG","o2==="+object2.hashCode());
2020-03-26 09:25:33.805 481-481/com.example.myapplication E/TAG: o1===229109964
2020-03-26 09:25:33.805 481-481/com.example.myapplication E/TAG: o2===94887701
在构造方法中初始化两个对象,打印出两个对象的hashcode,如果获取4个对象,那么就会输出最大空间的4个对象。
2020-03-26 09:34:38.057 1568-1568/com.example.myapplication E/TAG: o1===229109964
2020-03-26 09:34:38.057 1568-1568/com.example.myapplication E/TAG: o2===94887701
2020-03-26 09:34:38.057 1568-1568/com.example.myapplication E/TAG: o3===229675562
2020-03-26 09:34:38.057 1568-1568/com.example.myapplication E/TAG: o4===120797211
(6)避免重复申请内存
在循环中创建对象,或者在频繁调用的方法中创建对象,例如onDraw方法。
(7)不要使用非静态内部类和匿名内部类
这种情况会持有Activity的引用,导致内存泄漏,简单说一下匿名内部类。
public class Main2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
//这个地方会标黄,意味着会发生内存泄漏
new AysncTask().execute();
}
private class AysncTask extends AsyncTask{
@Override
protected Object doInBackground(Object[] objects) {
return doSomething();
}
private Object doSomething() {
return new Object();
}
}
}
AysncTask 是一个匿名内部类,这个类必定会持有Main2Activity 的引用,导致内存泄漏,这种情况如何去优化?
new AysncTask(this).execute();
}
private class AysncTask extends AsyncTask{
private WeakReference<Main2Activity> weakReference;
public AysncTask(Main2Activity main2Activity){
weakReference = new WeakReference<>(main2Activity);
}
@Override
protected Object doInBackground(Object[] objects) {
return doSomething();
}
private Object doSomething() {
return new Object();
}
}
采用弱引用的方式去修饰当前Activity。
(8)单例持有MainActivity的引用
public class Singleton {
private static Singleton singleton;
private Callback callback;
public static Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
public Callback getCallback(){
return callback;
}
public void setCallback(Callback callback){
this.callback = callback;
}
public interface Callback{
void callback();
}
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
new AysncTask(this).execute();
Singleton.getInstance().setCallback(new Singleton.Callback() {
@Override
public void callback() {
//此处会持有Activity的引用
}
});
}
在Activity中使用setCallback
的时候,就会持有Callback的引用,而且Callback不会被释放。
所以在使用Callback
的时候,就需要使用WeakReference
public class Singleton {
private static Singleton singleton;
// private Callback callback;
private WeakReference<Callback> callback;
public static Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
public WeakReference<Callback> getCallback(){
return callback;
}
public void setCallback(Callback callback){
this.callback = new WeakReference<>(callback);
}
public interface Callback{
void callback();
}
}