范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

内存优化基础论初识Android内存优化

  【小木箱成长营】内存优化系列文章:
  内存优化 · 工具论 · 常见的 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 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 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 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 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 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 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 data = new ArrayList<>();         data.add("Hello");         data.add("World"); // 创建强引用         ArrayList 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 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 softReference = new SoftReference<>(referent);         referent = null;         System.gc(); // 软引用可以在内存不足时被回收         System.out.println(softReference.get());     } } 输出结果:
  java.lang.Object@2f92e0f4
  这段代码创建了一个  Object    的实例,并使用它作为 SoftReference    的引用对象。
  然后,它将该实例设置为  null   ,并试图强制进行垃圾回收。如果内存不足,软引用会被回收,并且可以从 softReference    获取的对象将为 null   。虚引用虚引用概念
  虚引用是 Java 中最弱的引用类型,对于虚引用,对象只存在于垃圾回收的最后阶段,在这个阶段,对象将被回收,而无论内存是否充足。虚引用主要用于监测对象被回收的状态,而不是用于缓存对象。 虚引用案例public class PhantomReferenceExample {     public static void main(String[] args) {         Object referent = new Object();         ReferenceQueue referenceQueue = new ReferenceQueue<>();         PhantomReference 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 内存时代,再谈内存优化
短发男士不怕切口长做全脸拉皮,无p图,10天像变了一个人以下是求美者的自述男士做拉皮这件事,肯定很多人不理解,觉得没必要,可是男生就没有追求年轻和美的权利了吗?我想答案肯定是否定的。之前埋线光电项目都做过,但脸的松垮感还是很明显,尤其鼻2023年退休,独生子女父母能增发多少养老金?六个省份的退休比较持续30多年的独生子女时代,造就了我国1。76亿独生子女家庭。人口与计划生育法规定,领取了独生子女光荣证的父母,可以按照各省确定的标准享受相应的奖励待遇。有关独生子女家庭的奖励主要得物回应某手机提示删照无权限和能力删除相册图片近日,有网友发文称,他在得物App上购物后发现问题,拍下视频向得物反映,没想到他发现得物涉嫌通过调用他的手机权限,删除了他手机里的相关视频证据。11月13日,得物官方回应称,已第一场景再现心上插了一把刀,即使流血也要忍对不起,我没忍住!近日,鲁医生上门掌掴幼儿事件已经闹得人尽皆知了。鲁医生错了吗?错了!自己的孩子被人家的孩子不止一次地打出血,上门讨要说法还错了?错在哪?鲁医生自己说了,他就错在没像月亮一样宇航员涌向西班牙岛屿训练宇航员亚历山大格斯特(AlexanderGerst)跪在一个深陨石坑的边缘,用凿子收集火山岩样本,小心翼翼地将其放入白色塑料袋中。格斯特不在月球上,即使它看起来像。他位于西班牙加那场面壮观!数百只黑颈鹤迁徙至四川红原月亮湾集结休整黑颈鹤在红原月亮湾嬉戏初冬,在四川阿坝州红原县的月亮湾湿地,大批赤麻鸭在空中飞舞,还有大量国家一级保护动物黑颈鹤迁徙到此。近年来,红原县注重生态环境保护,提升固土保水涵养水源的生态170多万保时捷3个月亮故障灯王女士新买了一辆保时捷帕拉梅拉轿跑,上个月亮起了故障灯。嘉兴保时捷中心告诉她,这个问题暂时无法解决。视频花170万买的保时捷帕拉梅拉,故障灯何时能处理?王女士这辆保时捷帕拉梅拉四座忽有故人心上过,回首山河已是秋头条创作挑战赛这是一个平凡的秋天的午后,阳光正好,微风不燥。我听着外面孩子们的嬉闹声,望着那站成了窗外风景的树木有些发呆。孩子们这是上完了网课在玩闹吗?为什么还不能恢复正常的工作学人,名字,寻找情绪,情感,成长人生,太阳,月亮,光与记忆引言你们叫什么名字,我们在人海中相遇,我曾经呼喊你们的名字,最终在人海中失去你们的名字。以你们的名字,穿过岁月的走廊,再看一眼曾经的你们。或许你们在这个世界上的某个地方,或许以不同鲁医生的这一巴掌,打醒了社会上的不少人头条创作挑战赛鲁医生上门打孩子和老人事件,这两天开始有了反转,人们从开始一股脑地对鲁医生愤怒谴责,开始为他的行为进行辩解原谅。在看了幼儿园里的监控后,人们不再普遍去同情被打的孩子。蒙草生态荣获2022年中国企业社会责任碳中和年度先锋奖中证网讯(王珞)近日,在2022年中国进口博览会中国国际经济管理技术高峰论坛上,蒙草生态荣获2022中国企业社会责任碳中和年度先锋奖项。进博会期间,蒙草生态受邀参加以拥抱数字时代共
小鹏汽车大变天!何小鹏和王凤英要带领公司再创业?小鹏汽车换血多位核心高管最近有个消息让我很震惊小鹏汽车多名核心高管处于已离职或者将离职状态。这是怎么回事?难道小鹏汽车要垮了吗?其实不然。从我了解到的情况来看,这是小鹏汽车在进行一夏天特有的黄皮果,你知道它该怎么吃吗?哪些人不适合吃?大家好,我是博杰医生!黄皮果是南方朋友的常见水果,黄皮果是一种色香味俱佳的水果,人们常说,饥荔枝,饱黄皮,由此可见,黄皮具有一定的养生功效,可以说黄皮浑身都是宝。你知道夏天吃黄皮有谁说我们失望了?农民养老金增至月800元钱不是梦首先感谢代表们能够把农民养老金增长到每月800元钱这条建议带到了党中央,因为这件事说明了农民的养老问题,已经得到了很多人的关注,我觉得这就是一种进步,尽管这次会议没有通过。说没有失走进内蒙古博物院(馆藏图集)多年以后再次走进呼和浩特市内蒙古博物院,最大的收获是见到五管瓶和长乐未央砖的实物。内蒙古博物院位于内蒙古呼和浩特市新城区新华东街,原内蒙古博物馆作为1957年自治区成立10周年大庆腾格里沙漠,内蒙古阿拉善左旗,徒步盛宴,重走丝绸之路如果你希望孩子可以在旅程中得到坚毅能力锻炼野外生存训练敬畏尊重自然保护自然环境团队沟通协调动手创造能力如果你希望给孩子前所未有的陪伴之旅让亲子关系更进一步那就和孩子一起勇闯一次沙漠韩旭20分13板,李梦24分8助!四川8382内蒙古,夺得WCBA总冠军3月12日晚间,本赛季WCBA总决赛第三场的比赛正式打响,四川女篮主场迎战内蒙古女篮,由于此前两队战成11平,本场比赛的胜利者将最终赢得WCBA总冠军。第一节比赛,两队进入比赛状态2022年27省财政自给率下滑,今年情况会怎么样?记者樊旭2022年,在经济下行压力加大大规模减税退税缓税以及支出增加等因素影响下,全国31省(自治区直辖市)中,27个省份财政自给率出现滑坡,只有山西陕西内蒙古和新疆4个资源大省的香港2月新房成交大涨近7成,香港财政司司长楼价仍居高,不适合撤销辣招华夏时报(www。chinatimes。net。cn)记者梁宝欣李贝贝深圳报道2月27日,香港特区政府财政司司长陈茂波表示,去年香港楼价累计跌逾15,但目前楼价水平仍然高企。加上政40周岁存款300万元,可以提前退休吗?今年40周岁,存款拥有300万元,可不可以提前退休呢?今年40周岁的年龄,对于绝大部分女性还有15年的时间办理退休,少部分女性,大概还有10年的时间可以办理退休。而对于几乎所有的男2023最新百强县排行榜截至3月2日,全国有1279个县级地区(不含区,下同)披露2022年度GDP和一般公共预算收入数据,同比上一年度各地区经济多数有所增长,国内百强县门槛提高。GDP突破5000亿元。全国政协委员中国能源建设集团董事长宋海良建议加大力度支持新型储能发展中证网讯(记者刘丽靓)2023年全国两会开幕在即,全国政协委员,中国能源建设集团党委书记董事长宋海良建议,加大力度支持新型储能发展。探索建立新型储能容量补偿机制和容量市场,鼓励各地