文章目录

  • 性能优化:
  • 工具:
    • memory profiler
    • LeakCanary
    • arthook
    • epic 库
  • java内存管理机制
    • java 内存回收机制
    • Android内存管理机制
      • Dalvik与 Art区别
      • Low Memory Killer
  • 内存抖动解决
  • 内存泄漏解决
    • 第一个内存泄漏点
    • 内存很大的bitmap
      • 第一个地方 生成二维码的时候每隔一定时间会动态创建二维码
        • 解决方案:
      • 如何定位问题
      • native 内存一直在增加 分析
      • 发现个问题 android studio 插线后使用profile内存自动升高, 感觉是android studio 的bug
    • Bitmap内存模型
    • bitmap 优化
    • 内存优化细节
    • 优化结果:

性能优化:

工具:

memory profiler

android studio 自带的. 找不到profile 的话 顶部导航栏

  1. 点击下载样式的按钮可以找到当前的内存

这样可以看到当前内存是哪里消耗的最多,还有一些其他的内存信息, 最主要看一个bitmap

LeakCanary

https://square.github.io/leakcanary/getting_started/

大致原理

监听Activity生命周期->onDestroy以后延迟5秒判断Activity有没有被回收->如果没有回收,调用GC,再此判断是否回收,如果还没回收,则内存泄露了,反之,没有泄露。

引入:

dependencies {// debugImplementation because LeakCanary should only run in debug builds.debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6'
}

arthook

epic 库

支持ART上的Java方法HOOK

java内存管理机制

  1. 方法区: 存储 java静态变量 ,常量.这块区域所有线程都共享.和Java堆一样也是各个线程共享的内存区域,他存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。。
  2. 虚拟机栈: Java虚拟机栈 .用于存储局部变量、操作数、操作数栈、动态链接、方法出口等信息. 局部变量表存放了编译期可知的各种基本数据类型、对象引用和returnAddress类型。如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机可以动态扩展,如扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
  3. 本地方法栈: :与虚拟机栈类似,他们之间的区别是虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。
  4. 堆: Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域。在此内存区域中唯一目的就是存放对象实例,几乎所有的对象都在这里分配内存。
  5. 程序计数器:是一块较小内存,可以看作是当前线程所执行的字节码的行号指示器。每条线程都需要有一个独立的程序计数器,各个线程之间计数器互不影响。
    比如当前程序执行到第几行.

java 内存回收机制

(1).标记-清除算法:

最基础的垃圾收集算法,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收掉所有被标记的对象。

标记-清除算法的缺点有两个:首先,效率问题,标记和清除效率都不高。其次,标记清除之后会产生大量的不连续的内存碎片,空间碎片太多会导致当程序需要为较大对象分配内存时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

(2).复制算法:

将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉。这样使得每次都是对其中一块内存进行回收,内存分配时不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。

复制算法的缺点显而易见,可使用的内存降为原来一半。

(3).标记-整理算法:

标记-整理算法在标记-清除算法基础上做了改进,标记阶段是相同的标记出所有需要回收的对象,在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。

标记-整理算法相比标记-清除算法的优点是内存被整理以后不会产生大量不连续内存碎片问题。

复制算法在对象存活率高的情况下就要执行较多的复制操作,效率将会变低,而在对象存活率高的情况下使用标记-整理算法效率会大大提高。

(4).分代收集算法:

根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老代中创建。

  • 结合多种收集算法的优势
  • 新生代对象存活率低,复制算法
  • 老年代对象存活率高,标记-整理算法.

Android内存管理机制

  • 内存弹性分配,分配值与最大值受具体设备影响
  • OOM场景: 内存真正不足. 可用内存不足

Dalvik与 Art区别

Dalvik是Google公司自己设计用于Android平台的虚拟机
ART代表AndroidRuntime 应用安装的时候就预编译字节码到机器语言,这一机制叫Ahead-Of-Time(AOT)预编译。在移除解释代码这一过程后,应用程序执行将更有效率,启动更快

  • Dalvik仅固定一种回收算法,没有改变
  • 5.0以后用art虚拟机.art回收算法可运行期间选择(在不同情况下选择不同垃圾回收机制-) 比如当前应用在前台,对用户来说响应速度最重要.那么就选择最简单的算法: 标记清除算法, 当应用界面在后台.就可以选择使用标记-整理算法补充

Low Memory Killer

  • 进程分类,有优先级.优先回收低优先级的进程
  • 回收收益

内存抖动解决

  • 定义: 内存频繁废品和回收导致内存不稳定

  • 表现: 频繁GC. 内存曲线呈锯齿状

  • 危害: 导致卡顿 , OOM

  • 使用Memor Profiler初步排查

  • 使用Memor Profiler 或cpu Profile结合代码排查

内存泄漏解决

内存抖动的优化

尽量避免在循环体或者频繁调用的函数内创建对象,应该把对象创建移到循环体外。总之就是尽量避免频繁GC。

项目中出现的问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jZdUgVJa-1617777245772)(https://liudao01.github.io/picture/img/686db9de-e141-44d9-ab7f-67de6aa2b134.png)]

第一个内存泄漏点

拉开看

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VfQPra93-1617777245775)(https://github.com/liudao01/picture/img/20210202215043.png)]

接着我们点击Allocations进行对象分配数量排序,之所以点击这个是因为一般在循环,频繁调用的地方可能发生内存抖动

排名第一的是String ,
Cleaner是垃圾回收相关的对象,NativeAllocationRegistry是内存分配相关的额对象,我们查看其调用栈如图:
我一看咋还有百度的. 我进百度工具类N多个循环在打印信息.难怪内存抖动. 如下

 StringBuffer sb = new StringBuffer(256);
//                sb.append("time : ");
//                /**
//                 * 时间也可以使用systemClock.elapsedRealtime()方法 获取的是自从开机以来,每次回调的时间;
//                 * location.getTime() 是指服务端出本次结果的时间,如果位置不发生变化,则时间不变
//                 */
//                sb.append(location.getTime());
//                sb.append("\nlocType : ");// 定位类型
//                sb.append(location.getLocType());
//                sb.append("\nlocType description : ");// *****对应的定位类型说明*****
//                sb.append(location.getLocTypeDescription());
//                sb.append("\nlatitude : ");// 纬度
//                sb.append(location.getLatitude());
//                sb.append("\nlongtitude : ");// 经度
//                sb.append(location.getLongitude());
//                sb.append("\nradius : ");// 半径
//                sb.append(location.getRadius());
//                sb.append("\nCountryCode : ");// 国家码
//                sb.append(location.getCountryCode());
//                sb.append("\nProvince : ");// 获取省份
//                sb.append(location.getProvince());
//                sb.append("\nCountry : ");// 国家名称
//                sb.append(location.getCountry());
//                sb.append("\ncitycode : ");// 城市编码
//                sb.append(location.getCityCode());
//                sb.append("\ncity : ");// 城市
//                sb.append(location.getCity());
//                sb.append("\nDistrict : ");// 区
//                sb.append(location.getDistrict());
//                sb.append("\nTown : ");// 获取镇信息
//                sb.append(location.getTown());
//                sb.append("\nStreet : ");// 街道
//                sb.append(location.getStreet());
//                sb.append("\naddr : ");// 地址信息
//                sb.append(location.getAddrStr());
//                sb.append("\nStreetNumber : ");// 获取街道号码
//                sb.append(location.getStreetNumber());
//                sb.append("\nUserIndoorState: ");// *****返回用户室内外判断结果*****
//                sb.append(location.getUserIndoorState());
//                sb.append("\nDirection(not all devices have value): ");
//                sb.append(location.getDirection());// 方向
//                sb.append("\nlocationdescribe: ");
//                sb.append(location.getLocationDescribe());// 位置语义化信息
//                sb.append("\nPoi: ");// POI信息
//                if (location.getPoiList() != null && !location.getPoiList().isEmpty()) {
//                    for (int i = 0; i < location.getPoiList().size(); i++) {
//                        Poi poi = (Poi) location.getPoiList().get(i);
//                        sb.append("poiName:");
//                        sb.append(poi.getName() + ", ");
//                        sb.append("poiTag:");
//                        sb.append(poi.getTags() + "\n");
//                    }
//                }
//                if (location.getPoiRegion() != null) {
//                    sb.append("PoiRegion: ");// 返回定位位置相对poi的位置关系,仅在开发者设置需要POI信息时才会返回,在网络不通或无法获取时有可能返回null
//                    PoiRegion poiRegion = location.getPoiRegion();
//                    sb.append("DerectionDesc:"); // 获取POIREGION的位置关系,ex:"内"
//                    sb.append(poiRegion.getDerectionDesc() + "; ");
//                    sb.append("Name:"); // 获取POIREGION的名字字符串
//                    sb.append(poiRegion.getName() + "; ");
//                    sb.append("Tags:"); // 获取POIREGION的类型
//                    sb.append(poiRegion.getTags() + "; ");
//                    sb.append("\nSDK版本: ");
//                }
//                sb.append(mClient.getVersion()); // 获取SDK版本

我给注释后就好很多了

内存很大的bitmap

bitmap 反复创建销毁的问题

有两个地方 一个是首页轮播图的图片, 另一个是

闪电付的图片存在重复创建问题. 有两图片一个是二维码一个是条形码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XTb2N5Lx-1617777245777)(https://github.com/liudao01/picture/blob/master/img/20210202-222641.jpg)]

第一个地方 生成二维码的时候每隔一定时间会动态创建二维码

     /*** 生成二维码** @param content*/private void createQRCode(String content) {//生成二维码相关放在子线程里面ThreadManager.getDownloadPool().execute(() -> {qrCodeBitmap = CodeUtils.createQRCode(content, 540, null);if (activity != null) {activity.runOnUiThread(() -> {//显示二维码ivQrCode
//                    final int byteCount = qrCodeBitmap.getByteCount();
//                    LogUtil.d("二维码占得内存字节 = "+byteCount);ivQrCode.setImageBitmap(qrCodeBitmap);});}});

优化前内存消耗

  • 优化方案1

生成图片前先把图片清空,再用新的bitmap给他赋值

代码如下:

优化后内存消耗:

结果: 降低了将近50M 的内存 恐怖啊

  • 优化方案2

降低图片的质量:

先打印下生成的二维码所占的内存字节数

获取字节数方法:

bitmap.getByteCount
 二维码占得内存字节 = 1166400条形码占得内存字节 = 1166400

看一下内存消耗:

两张图所占内存 大约有2M 每次返回都要重新创建.这里可以优化一下.
这里我用的是库 默认创建的bitmap是ARGB_8888 那么我可以给优化下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-251XsXrP-1617777245780)(https://liudao01.github.io/picture/img/企业微信截图_99fd3f8d-fdac-4a96-bf3c-68bad123e323.png)]

解决方案:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3XRkMStC-1617777245781)(https://liudao01.github.io/picture/img/b5fa9ab8-4af3-4b99-84bc-d1021f1ddd2e.png)]

http://xingyun.xiaojukeji.com/docs/dokit/#/androidGuide

如何定位问题

开始的内存情况

运行一段时间后的内存情况

可以明显看到 graphics 内存增加 所以明显是图片的问题.

可以分析1 从网络 2 从本地. 网络的我先把加载网络大图的地方全部注释,依旧内存不断增大.

结合我的代码可以知道 我的页面存在循环创建对象的情况.

  for (int i = 0; i < size; i++) {if (i == 0) {rl_ecard_root.setBackgroundResource(R.mipmap.me_e_card1);} else if (i == 1) {rl_ecard_root.setBackgroundResource(R.mipmap.me_e_card2);} else if (i % 3 == 2) {rl_ecard_root.setBackgroundResource(R.mipmap.me_e_card3);}}

有这样的代码 这样每次进入返回页面都会重复创建

优化后:

native 内存一直在增加 分析

  1. dump java heap
  2. 保存memory-20210204T161535.hprof 文件
  3. mat 下载 https://www.eclipse.org/mat/downloads.php
  4. 创建java虚拟机失败了 https://www.jianshu.com/p/15597858caa0 (我通过vim改才成功)

发现个问题 android studio 插线后使用profile内存自动升高, 感觉是android studio 的bug

Bitmap内存模型

获取bitmap内存占用

  • getBytecount
  • 宽 * 高 * 一个像素所占用的内存
  • 资源目录需要再乘以一个压缩比例

常规优化方案:

  • 背景: 图片对内存优化至关重要,图片宽高大于控件宽高

bitmap 优化

Bitmap的优化策略
经过上面的分析,我们可以得出Bitmap优化的思路:

  1. BitmapConfig的配置
  2. 使用decodeFile、decodeResource、decodeStream进行解析Bitmap时,配置inDensity和inTargetDensity,两者应该相等,值可以等于屏幕像素密度*0.75f
  3. 使用inJustDecodeBounds预判断Bitmap的大小及使用inSampleSize进行压缩
  4. 对Density>240的设备进行Bitmap的适配(缩放Density)
  5. 2.3版本inNativeAlloc的使用
  6. 4.4以下版本inPurgeable、inInputShareable的使用
  7. Bitmap的回收

所以我们根据以上的思路,我们将Bitmap优化的策略总结为以下3种:

  1. 对图片质量进行压缩
  2. 对图片尺寸进行压缩
  3. 使用libjpeg.so库进行压缩

https://blog.csdn.net/u012124438/article/details/66087785

内存优化细节

  • LargeHeap 属性 开启
  • onTrimMemory 当系统内存不足会回调这个. 我们可以在这里回收一些内存供应用使用
  • 使用优化过的集合: SparseArray
  • 谨慎使用SharedPreference
  • 谨慎使用外部库

优化结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nV1XaOAE-1617777245784)(https://liudao01.github.io/picture/img/企业微信截图_96d7a599-03fa-4ca9-ac26-fd6d40916525.png)]

android—性能优化2—内存优化相关推荐

  1. Android 系统性能优化(55)---Android 性能优化之内存优化

    Android 性能优化之内存优化 前言 Android App优化这个问题,我相信是Android开发者一个永恒的话题.本篇文章也不例外,也是来讲解一下Android内存优化.那么本篇文章有什么不同 ...

  2. App性能优化(布局优化,线程优化,app瘦身优化,页面切换优化,App启动优化,内存优化)

    Android APP性能优化(最新总结) 在目前Android开发中,UI布局可以说是每个App使用频率很高的,随着UI越来越多,布局的重复性.复杂度也随之增长,这样使得UI布局的优化,显得至关重要 ...

  3. Lua性能优化—Lua内存优化

    原文链接https://blog.uwa4d.com/archives/usparkle_luaperformance.html 这是侑虎科技第236篇原创文章,感谢作者舒航供稿,欢迎转发分享,未经作 ...

  4. Android App性能优化之内存优化

    为什么要进行内存优化? 1.App运行内存限制,OOM导致App崩溃 2.App性能:流畅性.响应速度和用户体验 Android的内存管理方式 Android系统内存分配与回收方式 ●   一个App ...

  5. Android性能优化之内存优化 1

    导语 智能手机发展到今天已经有十几个年头,手机的软硬件都已经发生了翻天覆地的变化,特别是Android阵营,从一开始的一两百M到今天动辄4G,6G内存.然而大部分的开发者观看下自己的异常上报系统,还是 ...

  6. Android 性能监测工具,优化内存、卡顿、耗电、APK的方法

    导语     安卓大军浩浩荡荡,发展已近十个年头,技术优化月新日异,如今 Android 9.0 代号P  都发布了,Android系统性能已经非常流畅了.但是,到了各大厂商手里,改源码自定系统,使得 ...

  7. Android面试-Android性能优化和内存优化、APP启动速度一线大厂的实战案例解析

    一.Android 内存管理机制 二.优化内存的意义 三.避免内存泄漏 四.优化内存空间 五.图片管理模块的设计与实现 六.总结 深入探索Android内存优化 第一章.重识内存优化 第二章.常见工具 ...

  8. Android性能优化之内存优化浅析

    一.背景 Android由于是以Java语言为主要开发语言,所以它的内存管理并不像C语言那样由开发者去管理内存的分配以及回收等,而是交由JVM虚拟机的内存回收机制去处理.这就导致我们在开发过程中难免会 ...

  9. Android 系统性能优化(30)---Android性能全面分析与优化方案研究

    Android 性能优化 1.结合以下四个部分讲解: 性能问题分类 性能优化原则和方法 借助性能优化工具分析解决问题 性能优化指标 2性能问题分类 1.渲染问题:过度绘制.布局冗杂 2.内存问题:内存 ...

最新文章

  1. 【拓扑排序】【堆】CH Round #57 - Story of the OI Class 查错
  2. 采用web技术开发PC应用
  3. MariaDB 加密特性及使用方法
  4. spark学习-75-源代码:Endpoint模型介绍(6)-Endpoint的消息的接收(2)
  5. 新鲜出炉的百度js面试题
  6. The Nighth Assignment
  7. Python Imaging Library: ImageChops Module(图像通道操作模块)
  8. POJ NOI MATH-7649 我家的门牌号
  9. python中类方法、类实例方法、静态方法的使用与区别
  10. zabbix企业应用之low level discovery监控memcache
  11. 使用Nexus在Windows中搭建Maven私服
  12. mouse without borders其他选择中英对照说明
  13. web工程引用其他java工程_并读取spring配置文件_SpringBoot项目实战(8):四种读取properties文件的方式...
  14. 关注这场直播,了解能源行业双碳目标实现路径
  15. ArcGIS 线简化算法的使用及两种方法的比较
  16. 一个运行成功的hibernate例子(解决一直报hibernate mapping exception的错误)
  17. 如何掌握电烙铁焊接技术
  18. 半导体产业的根基:晶圆是什么
  19. 最近的错误整理(LMY)
  20. 期刊投稿状态_期刊投稿后的7种状态,如何应对

热门文章

  1. yolov7基于python 的onnx推理
  2. LeetCode热题100中使用辅助栈方法的题目的整理(待更)
  3. 随便看看,也是好久没更新了.....
  4. 绿地再次牵手国际会展业巨头,加速打造会展板块
  5. HOJ 2786 Convert Kilometers to Miles
  6. Java基于Redis实现附近的人(内附源码)
  7. 自考计算机毕业论文答辩视频,自考毕业论文答辩的全过程
  8. CC2640R2F BLE5.0 蓝牙协议栈GAP Bond管理和LE安全连接
  9. 博雅互动(静态网页)分享
  10. 全球各大运营商代码。方便国外卡的朋友修改运营商显示