首页 > 系统相关 >Android内存优化内存抖动的概念和危害

Android内存优化内存抖动的概念和危害

时间:2023-09-01 16:01:28浏览次数:48  
标签:抖动 创建 对象 GC 内存 字符串 Android array

内存抖动是一种内存管理的不良现象,它会影响应用的性能和稳定性。本文将从以下几个方面介绍内存抖动的定义、原因、后果和检测方法。

一、内存抖动的定义

Android内存优化内存抖动的概念和危害_Android

内存抖动示例图

内存抖动是指内存频繁分配和回收导致的不稳定现象。在Java中,内存分配和回收是由垃圾回收器(GC)来管理的。GC会定期扫描内存中的对象,判断哪些对象是无用的,然后释放它们占用的空间。这个过程称为垃圾回收(GC)。

GC是一种有益的机制,它可以避免内存泄漏,提高内存利用率。但是,如果GC过于频繁或者耗时过长,就会影响应用的运行效率。当GC发生时,应用的线程会被暂停,等待GC完成后才能继续执行。这个过程称为GC停顿(GC Pause)。

如果应用中存在大量短期存在的对象,或者对象的生命周期不一致,就会导致内存分配和回收的次数增加,从而增加GC的频率和时间。这就是内存抖动的本质。

二、内存抖动的原因

导致内存抖动的原因有很多,这里列举一些常见的场景:

  • 字符串拼接:字符串是不可变的对象,每次拼接字符串都会创建一个新的字符串对象,并且丢弃旧的字符串对象。这样就会产生大量短期存在的字符串对象,增加GC的压力。例如:
// 以下代码会创建5个字符串对象:"Hello"、"World"、"Hello World"、"!"、"Hello World!"
String s = "Hello" + "World" + "!";
  • 资源复用:如果没有正确地复用资源,比如Bitmap、Drawable、File等,就会导致资源被重复创建和销毁,占用更多的内存空间,并且触发更多的GC。例如:
// 以下代码每次都会创建一个新的Bitmap对象,并且在使用完毕后立即回收
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
imageView.setImageBitmap(bitmap);
bitmap.recycle();
  • 不合理的对象创建:如果在循环或者频繁调用的方法中创建了不必要的对象,或者使用了不合适的数据结构,就会导致内存分配和回收的次数增加,造成内存抖动。例如:
// 以下代码每次都会创建一个新的ArrayList对象,并且在方法返回后立即被回收
public List<String> getNames() {
    List<String> names = new ArrayList<>();
    names.add("Alice");
    names.add("Bob");
    names.add("Charlie");
    return names;
}

四、内存抖动的后果

内存抖动会给应用带来以下几种负面影响:

  • 频繁GC:当内存分配和回收过于频繁时,GC就会更加频繁地执行,消耗更多的CPU资源,并且影响应用线程的执行。
  • 内存曲线呈锯齿状:当内存分配和回收不平衡时,内存使用量就会呈现出波动性,上下起伏,形成锯齿状。这样就会增加OOM(Out Of Memory)错误发生的风险。
  • 页面卡顿:当GC停顿时间过长时,应用线程就会被暂停,导致页面的渲染和交互出现延迟,用户感受到卡顿现象。

五、内存抖动的检测

要检测应用是否存在内存抖动,可以使用一些工具来监控和识别内存抖动。例如:

  • Memory Profiler:Memory Profiler是Android Studio中的一个工具,它可以实时显示应用的内存使用情况,包括内存分配、回收、泄漏等。通过Memory Profiler,可以观察到内存抖动的现象,比如内存曲线的锯齿状,以及GC的频率和时间。
  • Allocation Tracker:Allocation Tracker是Memory Profiler中的一个功能,它可以记录应用在一段时间内创建的所有对象,以及它们的类型、大小、数量等。通过Allocation Tracker,可以找出应用中产生内存抖动的代码,比如字符串拼接、资源复用、不合理的对象创建等。

以下是一个使用Memory Profiler和Allocation Tracker检测内存抖动的示例图:

Android内存优化内存抖动的概念和危害_内存分配_02

检测路径

六、常见的内存优化方式

①、避免字符串拼接:

字符串拼接是一种非常低效的操作,它会产生大量无用的字符串对象,增加GC的压力。为了避免字符串拼接,可以使用以下几种方法:

1.StringBuilder:

StringBuilder是一个可变的字符串类,它可以在不创建新对象的情况下,对字符串进行修改和拼接。使用StringBuilder可以大大减少字符串对象的创建和回收。例如:

// 以下代码只会创建一个StringBuilder对象和一个字符串对象:"Hello World!"
    StringBuilder sb = new StringBuilder();
    sb.append("Hello");
    sb.append("World");
    sb.append("!");
    String s = sb.toString();
2.String.format:

String.format是一个静态方法,它可以根据指定的格式化字符串和参数,生成一个新的字符串对象。使用String.format可以避免在循环中拼接字符串,提高代码的可读性和性能。例如:

// 以下代码只会创建一个字符串对象:"Hello World!"
String s = String.format("%s %s!", "Hello", "World");
3.资源文件:

资源文件是一种存储在应用中的文本文件,它可以用来保存一些常量或者多语言的字符串。使用资源文件可以避免在代码中硬编码字符串,减少字符串对象的创建和回收。例如:

<!-- 以下代码是一个资源文件(res/values/strings.xml)中的一段内容 -->
    <resources>
        <string name="hello_world">Hello World!</string>
    </resources>
// 以下代码只会创建一个字符串对象:"Hello World!"
    String s = getResources().getString(R.string.hello_world);

②、资源复用: 资源复用是一种有效的优化内存抖动的方法,它可以减少资源的创建和销毁,提高内存利用率。为了实现资源复用,可以使用以下几种方法: 1.对象池: 对象池是一种设计模式,它可以用来管理一组可重用的对象,而不是每次都创建和销毁对象。当需要一个对象时,可以从对象池中获取一个空闲的对象,使用完毕后,可以将对象归还到对象池中,等待下次使用。这样就可以避免频繁的内存分配和回收,减少GC的压力。例如:

// 以下代码是一个简单的Bitmap对象池的实现
    public class BitmapPool {
        // 一个存储Bitmap对象的队列
        private Queue<Bitmap> queue;
        // 对象池的最大容量
        private int capacity;

        // 构造方法,初始化队列和容量
        public BitmapPool(int capacity) {
            this.queue = new LinkedList<>();
            this.capacity = capacity;
        }

        // 从对象池中获取一个Bitmap对象,如果没有空闲的对象,就返回null
        public Bitmap getBitmap() {
            return queue.poll();
        }

        // 将一个Bitmap对象归还到对象池中,如果对象池已满,就回收该对象
        public void returnBitmap(Bitmap bitmap) {
            if (queue.size() < capacity) {
                queue.offer(bitmap);
            } else {
                bitmap.recycle();
            }
        }
    }

2.复用参数: 复用参数是一种避免在方法中创建不必要的对象的方法,它可以将一些可变的参数作为方法的输入和输出,而不是在方法内部创建新的对象。这样就可以减少对象的创建和回收,提高代码的效率。例如:

// 以下代码是一个计算两个向量之间夹角的方法,它使用了一个复用参数result来存储计算结果,而不是在方法内部创建一个新的float数组
    public void calculateAngle(float[] vector1, float[] vector2, float[] result) {
        // 计算两个向量的点积
        float dotProduct = vector1[0] * vector2[0] + vector1[1] * vector2[1];
        // 计算两个向量的模长
        float length1 = (float) Math.sqrt(vector1[0] * vector1[0] + vector1[1] * vector1[1]);
        float length2 = (float) Math.sqrt(vector2[0] * vector2[0] + vector2[1] * vector2[1]);
        // 计算两个向量之间的夹角(弧度)
        float angle = (float) Math.acos(dotProduct / (length1 * length2));
        // 将计算结果存储在复用参数result中
        result[0] = angle;
    }

③、合理的对象创建: 合理的对象创建是一种避免内存抖动的基本原则,它要求我们在编写代码时,尽量减少不必要的对象创建,或者使用更合适的数据结构。为了实现合理的对象创建,可以遵循以下几个建议: 1.避免在循环或者频繁调用的方法中创建对象: 如果在循环或者频繁调用的方法中创建了不必要的对象,就会导致内存分配和回收过于频繁,造成内存抖动。因此,在编写代码时,应该尽量将对象的创建放在循环或者方法之外,或者使用静态变量或者成员变量来保存对象。例如:

// 以下代码是一个计算斐波那契数列第n项的方法,它使用了一个BigInteger数组来存储中间结果,但是每次调用该方法都会创建一个新的数组对象
    public BigInteger fibonacci(int n) {
        // 创建一个BigInteger数组,用来存储中间结果
        BigInteger[] array = new BigInteger[n + 1];
        // 初始化数组的第0项和第1项
        array[0] = BigInteger.ZERO;
        array[1] = BigInteger.ONE;
        // 从第2项开始,计算斐波那契数列
        for (int i = 2; i <= n; i++) {
            // 使用数组的前两项相加,得到当前项
            array[i] = array[i - 1].add(array[i - 2]);
        }
        // 返回数组的最后一项,即斐波那契数列的第n项
        return array[n];
    }
// 以下代码是一个优化后的计算斐波那契数列第n项的方法,它使用了一个静态变量来保存BigInteger数组,避免了每次调用该方法都创建一个新的数组对象
    public class FibonacciCalculator {
        // 创建一个静态变量,用来存储BigInteger数组
        private static BigInteger[] array;

        // 计算斐波那契数列第n项的方法
        public static BigInteger fibonacci(int n) {
            // 如果静态变量为空,或者长度不足,就重新创建一个新的数组对象,并初始化第0项和第1项
            if (array == null || array.length < n + 1) {
                array = new BigInteger[n + 1];
                array[0] = BigInteger.ZERO;
                array[1] = BigInteger.ONE;
            }
            // 从第2项开始,计算斐波那契数列
            for (int i = 2; i <= n; i++) {
                // 如果当前项为null,就使用数组的前两项相加,得到当前项
                if (array[i] == null) {
                    array[i] = array[i - 1].add(array[i - 2]);
                }
            }
            // 返回数组的最后一项,即斐波那契数列的第n项
            return array[n];
        }
    }

④、使用合适的数据结构: 如果使用了不合适的数据结构,就会导致内存分配和回收不平衡,或者浪费内存空间,造成内存抖动。因此,在编写代码时,应该根据实际需求,选择合适的数据结构。例如: 1.使用基本类型而不是包装类型: 基本类型(如int、float、boolean等)是直接存储在栈上的,它们不需要创建对象,也不会触发GC。而包装类型(如Integer、Float、Boolean等)是存储在堆上的对象,它们需要创建对象,并且会触发GC。因此,在可能的情况下,应该优先使用基本类型而不是包装类型。例如:

// 以下代码使用了包装类型Integer来存储一个整数值,这会导致内存分配和回收
        Integer value = new Integer(100);
// 以下代码使用了基本类型int来存储一个整数值,这会避免内存分配和回收
        int value = 100;

2.使用SparseArray而不是HashMap: SparseArray是Android中提供的一种数据结构,它可以用来存储键值对,其中键是int类型,值是任意类型。SparseArray比HashMap更节省内存空间,因为它不需要创建额外的对象来保存键值对。因此,在可能的情况下,应该优先使用SparseArray而不是HashMap。例如:

// 以下代码使用了HashMap来存储一些键值对,其中键是int类型,值是String类型,这会导致内存分配和回收
        HashMap<Integer, String> map = new HashMap<>();
        map.put(1, "Alice");
        map.put(2, "Bob");
        map.put(3, "Charlie");
// 以下代码使用了SparseArray来存储一些键值对,其中键是int类型,值是String类型,这会避免内存分配和回收
        SparseArray<String> array = new SparseArray<>();
        array.put(1, "Alice");
        array.put(2, "Bob");
        array.put(3, "Charlie");

⑤、使用数组而不是集合: 数组是一种固定长度的数据结构,它可以用来存储一组相同类型的元素。数组比集合(如ArrayList、LinkedList等)更节省内存空间,因为它不需要创建额外的对象来保存元素。因此,在可能的情况下,应该优先使用数组而不是集合。例如:

// 以下代码使用了ArrayList来存储一组整数值,这会导致内存分配和回收
    ArrayList<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
// 以下代码使用了数组来存储一组整数值,这会避免内存分配和回收
    int[] array = new int[3];
    array[0] = 1;
    array[1] = 2;
    array[2] = 3;

标签:抖动,创建,对象,GC,内存,字符串,Android,array
From: https://blog.51cto.com/u_16175630/7323977

相关文章

  • return new 内存泄漏
    样例如下:#include<iostream>classB{public:intFun(){return100;}}classA{public:B*CreateB()//new了个B对象{returnnewB();}}intmain(){A*aa=newA();std::cout<&......
  • 243.mysql 内存统计相关
    1.查看mysql是否打开了关于innodb内存相关的监控SELECT*FROMperformance_schema.setup_instrumentsWHERENAMELIKE'%memory/innodb%';2.没有打开的话开一下(自己判断:重启数据库会恢复原有设置)#updateperformance_schema.setup_instrumentssetenabled='yes'wher......
  • 无涯教程-Android Online Test函数
    Android在线测试模拟了真正的在线认证考试。您将看到基于Android概念的多项选择题(MCQ),将为您提供四个options。您将为该问题选择最合适的答案,然后继续进行下一个问题,而不会浪费时间。完成完整的考试后,您将获得在线考试分数。总问题数-20最长时间-20分钟StartTest......
  • 无涯教程-Android Online Quiz函数
    以下测验提供与Android相关的多项选择题(MCQ)。您将必须阅读所有给定的答案,然后单击正确的答案。如果您不确定答案,则可以使用显示答案按钮检查答案。您可以使用下一个测验按钮检查测验中的新问题集。Q1-android中的PendingIntent是什么?A-这是一种意图B-用于在活动......
  • 解决Activity启动黑屏及设置android:windowIsTranslucent不兼容activity切换动画问题
    注:如果设置和取消会造成不同bug,冲突的解决设置不同的style,具体另行百度。。。。。。。之前在做APP的时候不太关注这个问题,因为自己在使用其他APP的时候也会在应用启动的初始有一个黑屏闪过后才会出现应用的欢迎页。直到最近开发过程中发现自己在欢迎页启动的线程由于请求和......
  • Android内存优化的背景
    一、Android内存管理机制Android系统在运行时使用内存管理机制来分配和回收内存,以确保应用程序在合适的内存限制下运行,并优化系统资源的使用效率。Android系统中有不同类型的内存区域,每个区域都有不同的作用。Java堆(JavaHeap):Java堆是用于分配Java对象的主要内存区域。Android应用......
  • 无涯教程-Android - EditText函数
    EditText是TextView的覆盖层,该覆盖层将自身配置为可编辑的。它是TextView的预定义子类,其中包含丰富的编辑功能。EditText-属性以下是与EditText控件相关的重要属性。您可以查看Android官方文档以获取属性的完整列表以及可以在运行时更改这些属性的相关方法。继承自android.......
  • svchost.exe占用内存过高--(windows update检查更新时一直处于正在检查)
    1.打开任务管理器-进程,找到svchost对应占用内存过高对应PID值例如:60982.打开任务管理器-服务,查找PID为6098的服务,如果是windowsupdate引起的则操作以下步骤 1)右击windowsupdate-属性设为停止 2)C:\windows\softwaredistribution删除里面......
  • Unity Android Studio 混合开发实践(Unity工程导入Android项目进行开发)
    前言最近接到一个任务是将一个unity开发的游戏接入到现有的Android项目里,然后在现有的App实现点击一个按钮打开游戏,并且在游戏内提供一个可以退出到App的按钮。整体需求是很明确的,难点主要有两个:我们公司是做应用开发的,没有任何游戏开发的技能储备。在游戏中需要和Native进行......
  • android短视频开发,js如何设置canvas绕图形中心旋转
    android短视频开发,js如何设置canvas绕图形中心旋转1.准备一个页面拟写一个页面,用于实验,代码如下 <!DOCTYPEhtml><html><head><metacharset="utf-8"><metaname="viewport"content="width=device-width,initcal-scale=1.0"/><title>......