记一次项目内存优化--内存泄漏

news2025/1/20 5:45:49

需求–内存泄漏优化,PSS有所下降, OOM率减少

主要是与某个版本作基准进行对比(一般是最新版本的前一个版本作原数据),优化后,PSS有所下降,线上OOM率减少(Bugly版本对比),泄漏点减少(从捉取一些线上上传回来的内存堆栈信息分析,或本地测试后dump下hprof文件分析)。

内存泄漏优化的思路

  • 了解什么是内存泄漏
  • 了解虚拟机中的对象的创建过程
  • 了解Java内存分配模型
  • 了解垃圾回收分代收集理论
  • 了解java的引用类型
  • GC是如何判断对象存活
  • 有哪些对象可作为GC Roots
  • 了解内存泄漏的工具
  • 总结

什么是内存泄漏

App程序中己动态分配的堆内存,由于某种原因,App程序未释放或无法释放,会造成系统(手机)内存的浪费。长生命周期对象持有短生命周期对象强引用,从而导致短生命周期对象无法被回收。 我们注意这两个关键词堆内存、强引用

虚拟机中的对象的创建过程(类的生命周期)

什么都不用说,先上张自画图。为大家推荐一本书《深入理解JVM》

第一步,当虚拟机遇到一条new指令时,首先检查这条指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

第二步,如果检查通过后,虚拟机将为这个new出的对象进行分配内存。划分内存是通过指针碰撞、空间列表的组合,同时也考虑并发安全问题(CAS(Compare And Swap的缩写–乐观锁)失败重试、本地线程内存缓冲)。这中是进行内存分配哦,这时候还不能确定对象所需要的内存大小。在类加载完成后才确定内存的大小。

第三步,内存分配完成后,虚拟机将分配到的内存空间进行初始化为零值(默认的初始值),但不包括对象头信息,如果使用TLAB(Thread Local Allocation Buffer ,即线程本地分配缓冲区),这过程可以提前至TLAB分配时进行(Eden区划分出一小块区域作为TLAB)。这一步操作是保证了对象的实例成员(字段)在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

第四步,对象进行必要的设置(主要是一些对象头信息的设置),例如这个对象的运行状态、GC分代年龄、锁状态、属于哪个类的实例、哈希码等等信息。

第五步,从前面几个步骤知道,只是设置了对象头信息和所有类成员(字段)赋为默认的初始值,对象并没有执行方法,所以最后是会接着执行方法,这样才算创建出一个真正可用的对象。几乎所有对象是存放在堆区。

Java内存分配模型

使用一张图,快速了解一下内存分配模型。

方法区

编译时就分配好,在程序整个运行期间都存在。它用于存储已经被虚拟机加载的类信息、静态变量、常量等数据。

堆区

几乎所有 new 出来的对象是存放在堆区,由 Java 垃圾回收器回收。堆中的对象是垃圾回收的重点。 堆区的划分新生代、老年代:

新生代

新生代是用来存放新new出来的对象,划分为 Eden区、From Survivor区 、To Survivor区 。几乎所以的new出来的对象都会存放在Eden区 (如果new出来的对象占用的内存非常大,在新生代中存放不下,直接进入老年代存放)。

当Eden区的内存空间不足时,系统会触发Minor GC /Young GC进行回收Eden区的对象(From Survivor区 、To Survivor区不会触发GC),经过GC后,一些对象仍然存活(对象被引用着–通过GC Root可达性来判断),则会被移到To Survivor区存放,当对象在Survivor区熬过一次GC后,此对象的GC年龄就会+1(GC年龄是对象头信息的一个标记参数),会被复制到From Survivor区,当From Survivor区的对象达到一定年龄时(默认年龄是15,但可以通过XX:MaxTenuringThreshold设置),被移到老年代,否则复制到To Survivor区。

老年代

老年代是新生代存放不下的大对象,或对象经过多次Minor GC /Young GC后仍然存活的对象(长期存活的对象)。

当随着Eden区的Minor GC /Young GC持续进行,老年代的对象持续增加,会导致老年代可用的内存空间也会持续减少,最终系统会触发Major GC。

元空间(永久代)

永久代(持久代)是存放包含应用的类/方法信息,以及JRE库的类和方法信息。然而在Java8中,元空间取代了永久代,元空间(Metaspace)被称为“元数据区”。

需要注意的是:元空间并不在虚拟机中哦,而是使用本地内存(以前永久代是在jvm中的)。这样就解决了以前永久代的OOM问题,元数据和class对象存放在永久代中,容易出现性能问题和内存溢出,毕竟是和老年代共享堆空间。

堆内存分配策略

内存分配原则

  • 对象优先在Eden分配----如果说Eden内存空间不足,就会发生Minor GC /Young GC。
  • 大对象直接进入老年代----大对象:需要大量连续内存空间的Java对象,比如很长的字符串和大型数组。会导致新生代内存有空间,还是需要提前进行垃圾回收获取连续空间来放此大对象。Survivor区会进行大量的内存复制,-XX:PretenureSizeThreshold 参数 ,大于这个数量直接在老年代分配,缺省为0 ,表示绝不会直接分配在老年代。当Eden分配和Survivor区都没有足够空间存放此大对象时,则直接分配到老年代。
  • 长期存活的对象将进入老年代----Survivor区的对象达到一定年龄时,直接移到老年代。默认15岁,可以通过XX:MaxTenuringThreshold设置。
  • 动态对象年龄判定----为了能更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor区中相同年龄所有对象大小的总和大于Survivor区的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
  • 空间分配担保:新生代中有大量的对象存活,Survivor区不够,当出现大量对象在Minor GC后仍然存活的情况(最极端的情况就是内存回收后新生代中所有对象都存活),就需要老年代进行分配担保,把Survivor区无法容纳的对象直接进入老年代,只要老年代的连续空间大于新生代对象的总大小或者历次晋升的平均大小,就进行Minor GC,否则Full GC。

栈中分配对象

  • 逃逸分析----如果符合逃逸分析规则,则在栈中分配对象。

堆中的优化技术

  • TLAB ----Thread Local Allocation Buffer ,即线程本地线程分配缓冲。

栈区

当方法执行时,会在栈区内存中创建方法体内部的局部变量,方法结束后自动释放内存。

垃圾回收分代收集理论

我们都知道,在java中不同的对象存在不同的生命周期的,java对象在JVM中也存放在不同的区域,所以对不同生命周期不同的存放区,采取不同的回收策略,以提高效率。

当Eden区的内存空间不足时,系统触发Minor GC/Young GC, 随着GC持续进行,老年代的对象持续增加,导致老年代的内存空间不足,系统触发Major GC。当堆区或方法区内存空间不足时,系统触发Full GC。

Full GC:清理成本高,系统资源消耗高,对系统性能产生影响,很多性能什么都是针对Full GC进行的。 触发Full GC的条件有:

  • 调用System.gc()
  • 方法区空间不足
  • 堆区空间不足

不同阶段GC的特点

  • Minor GC/Young GC – 执行非常频繁,速度特别快。
  • Major GC – 速度上,一般会比Minor GC/Young GC慢十倍以上。
  • Full GC – Minor GC和Major GC都会执行,会发出"Stop the World"事件,会中断程序运行,直到GC完成。所以Full GC时,我们会感知到APP有卡顿之感。

垃圾回收分代收集对应的回收算法

  • 复制算法: 实现简单,运行高效,内存复制,内存利用率只有一半。
  • 标记-清除: 利用率100%,不需要内复制,有内存碎片
  • 标记-整理:利用率100%,没有内存碎片,需要内存复制(整理存活的对象,将其拷贝到一块连续内存中)

GC是如何判断对象存活

  • 可达性分析 (java) 通过一系列称之为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所有的引用链,当一个对象到GC Roots有引用链,则说明这个对象存活着;当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的(所谓的垃圾)。

  • 引用计数算法(JVM早期使用的—已经不使用) A对象引用B 对象(+1),同时C对象引用B对象(1+1=2),计数法就是引用一次累加1次,如果没有引用就累减1次,如果归到0时,说明没有引用。缺点:就是相互引用。如A对象引用B对象,同时B对象引用A对象,很难去判断对象是否应该回收。

在Java, 可作为GC Roots的对象包括:

  • 方法区: 类静态属性的对象;
  • 方法区: 常量的对象;
  • 虚拟机栈(本地变量表)中的对象。
  • 本地方法栈JNI(Native方法)中的对象。

四种引用类型

  1. 强引用(StrongReference):JVM 宁可抛出 OOM ,也不会让 GC 回收具有强引用的对象。
  2. 软引用(SoftReference):只有在内存空间不足时,对象才会被回收。
  3. 弱引用(WeakReference):在 GC 时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,对象都会被回收。
  4. 虚引用(PhantomReference):任何时候都可以被GC回收,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否存在该对象的虚引用,来了解这个对象是否将要被回收。可以用来作为GC回收Object的标志。

我们定义对象,应该考虑使用那种引用,多考虑使用软引用(定义一些还有用但并非必须的对象)或弱引用(定义非必须对象)。

Android Studio的profiler工具

  1. 我们也可以利用Android Studio的profiler工具,方便快速查找、观察,简单分析一些对象生成情况。
  2. 也可以dump下hprof文件,结合MAT深入分析与排查,对象发生是否泄漏。
  3. 注意:MAT打开Android Studio的profiler里dump下hprof文件时,利用AS自带的hprof工具转换一下格式(通过命令hprof-conv -z 原hprof文件 输出hprof文件),不然打开是乱码。

当然检测内存泄漏的工具和方法有很多,就不一一列举了,感兴趣的可以网上查阅一下。

常见的内存问题场景

  • 静态成员/单例
    • 作为GC ROOT,持有短生命周期引用(如Activity)导致其短生命周期对象无法释放。
  • 集合类
    • 当使用集合时,只有添加元素,没有对应的删除元素。
  • 非静态内部类/匿名内部类
    • 如Handler postDelayed一个匿名Runnable,退出Activity时消息没处理完。
  • 上下文 – Context
    • 持有的上下文,需要特别注意。
  • 注册/反注册
    • 如EventBus只有注册没有注销。addXXXListener函数,需要有对应的removeXXXListener等等。
  • 未关闭/释放资源
    • 如FileOutputStream未close。
  • 系统Bug
    • WebView、InputMethodManager等

总结

  • 上面的内存相关知识也是自己学习的一种总结,有错误的可以留言指正。
  • 内存优化,需要对下面的知识有一定的了解。
    • Java内存分配模型
    • Java的四大引用及其使用场景
    • 内存检测工具及常用命令
    • GC Root的定义

为了帮助到大家更好的全面清晰的掌握好性能优化,准备了相关的核心笔记(还该底层逻辑):https://qr18.cn/FVlo89

性能优化核心笔记:https://qr18.cn/FVlo89

启动优化

内存优化

UI优化

网络优化

Bitmap优化与图片压缩优化https://qr18.cn/FVlo89

多线程并发优化与数据传输效率优化

体积包优化

《Android 性能监控框架》:https://qr18.cn/FVlo89

《Android Framework学习手册》:https://qr18.cn/AQpN4J

  1. 开机Init 进程
  2. 开机启动 Zygote 进程
  3. 开机启动 SystemServer 进程
  4. Binder 驱动
  5. AMS 的启动过程
  6. PMS 的启动过程
  7. Launcher 的启动过程
  8. Android 四大组件
  9. Android 系统服务 - Input 事件的分发过程
  10. Android 底层渲染 - 屏幕刷新机制源码分析
  11. Android 源码分析实战

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/895762.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网工内推 | 数通工程师,有运营商工作经验优先

01 国育产教融合教育科技(海南)有限公司 招聘岗位:通信工程师 职责描述: 1、负责锐捷4/5G皮基站产品的工程交付工作,包括现场勘测、硬件督导、开通调测、测试优化等技术交付工作; 2、负责锐捷4/5G皮基站网…

【探索Linux】—— 强大的命令行工具 P.5(yum工具、git 命令行提交代码)

阅读导航 前言一、软件包管理器 yum1.yum的概念yum的基本指令使用例子 二、git 命令行提交代码总结温馨提示 前言 前面我们讲了C语言的基础知识,也了解了一些数据结构,并且讲了有关C的一些知识,也学习了一些Linux的基本操作,也了…

I2S/PCM board-level 约束及同步(latencyskewbitsync)

I2S/PCM是典型的低速串口,在两个方向上分别有两组信号,我们已soc为视角分为soc-adif和外设audio-codec。 那么adif输入: sclk_i, ws_i, sdi 当然并不是三个输入信号同时有效,只有adif RX slave时,三个输入都会有效…

贷款公司精准获客,快速找到目标客户,直击获客高点!

目前,运营商的大数据技术正在逐步发展壮大,并已广泛应用于各个行业。运营商大数据市场有待发展的潜在行业发展趋势难以预料,整体能源规模达数万亿元。运营商大数据与三家网络运营商进行了深度战略合作,利用移动电信运营商的大数据…

使用coloc 进行 QTL 共定位Colocalization

GWAS找到显著信号位点后,需要解释显著信号位点如何影响表型。 常见的一个解释方法是共定位分析。 主流的共定位分析包括: 1)GWAS和eQTL共定位; 2)GWAS和sQTL共定位; 3)GWAS和meQTL共定位&am…

上一个说软件测试简单的,已经被面试官问emo了···

现在已经过了 ”不会但我会学“ 就能感动面试官的时代,随着供需关系的变化,不论是对于面试官还是面试者,面试的成本越来越高。为了筛选到更优秀的程序员,面试官们可谓是绞尽了脑汁,”面试造火箭,工作拧螺丝…

PS丢失d3dcompiler_47.dll文件怎么办(附详细修复方法)

我们在安装PS等软件的时候,有可能安装完之后出现以下问题(特别是win10或者win11系统) 错误: 打开PS的时候出现这个错误:无法启动此程序,因为计算机中丢失D3DCOMPILER_47.dll。尝试重新安装该程序以解决此问…

03-微信小程序常用组件-视图容器组件

微信小程序组件-视图容器 文章目录 视图容器view 视图容器案例代码 swiper 滑块视图容器案例代码indicator-color 微信小程序包含了六大组件: 视图容器、 基础内容、 导航、 表单、 互动和 导航。这些组件可以通过WXML和WXSS进行布局和样式设置,从…

CFD特性FPmarkets澳福认为了解这11种足够了

CFD在交易中很重要,但CFD特性很多投资者不了解,FPmarkets澳福认为了解这11种足够了: 1. 投资者通过标的资产价格价值的变化获利,而不拥有标的资产。 2. 差价合约交易没有固定的到期日。 3. 与期货交易类似,差价合约交易…

海外问卷脚本机器人哪里哪里有?是真的吗?

大家好,我是橙河老师,今天讲一讲海外问卷项目能不能用脚本操作? 最近没怎么写文章,确实比较忙。我本人每天至少要面对5-10个客户咨询项目,每隔一段时间,都会有人问我:操作海外问卷有没有脚本&a…

文字点选验证码识别(上)-YOLO位置识别

声明 本文以教学为基准、本文提供的可操作性不得用于任何商业用途和违法违规场景。 本人对任何原因在使用本人中提供的代码和策略时可能对用户自己或他人造成的任何形式的损失和伤害不承担责任。 如有侵权,请联系我进行删除。 文章中没有代码,只有过程思路,请大家谨慎订阅。…

集简云简化流程模板,轻松实现工作流程自动化

集简云平台内置大量自动化流程模板,用户可以在“模板中心”搜索应用名称,选择适合自己的场景,直接使用。本期分享集简云自动化工作流程。 模板推荐 模板1:小鹅通新增订单后同步到seatable并更新微伴助手用户信息 集成应用&#…

redis 存储结构原理 1

关于 redis 相信大家都不陌生了,之前有从 0 -1 分享过 redis 的基本使用方式,用起来倒是都没有啥问题了,不过还是那句话,会应用之后,我们必须要究其原理,知其然知其所以然 今天我们来分享一下关于 redis 的…

【网络基础实战之路】VLAN技术在两个网段中的实际应用详解

系列文章传送门: 【网络基础实战之路】设计网络划分的实战详解 【网络基础实战之路】一文弄懂TCP的三次握手与四次断开 【网络基础实战之路】基于MGRE多点协议的实战详解 【网络基础实战之路】基于OSPF协议建立两个MGRE网络的实验详解 【网络基础实战之路】基于…

Matplotlib数据可视化(五)

目录 1.绘制折线图 2.绘制散点图 3.绘制直方图 4.绘制饼图 5.绘制箱线图 1.绘制折线图 import matplotlib.pyplot as plt import numpy as np %matplotlib inline x np.arange(9) y np.sin(x) z np.cos(x) # marker数据点样式,linewidth线宽,li…

揭开区块链地址背后的故事,你需要知道的KYA

作者|Jason Jiang 在区块链世界中,除了交易还有另一个基础要素:地址。在欧科云链日前推出的Onchain AML合规技术方案,也有一个与区块链地址密切相关的概念:KYA(Know Your Address,了解你的地址&…

[LitCTF 2023]Ping

因为直接ping会有弹窗。这里在火狐f12,然后f1选禁用javascript,然后ping 然后输入127.0.0.1;cat /flag 得到flag, 查看其他大佬的wp ,这里还可以抓包。但是不知道为什么我这里的burp 用不了

数字化转型能带来哪些价值?_光点科技

随着科技的迅猛发展,数字化转型已成为企业和组织的一项重要战略。它不仅改变了商业模式和运营方式,还为各行各业带来了诸多新的机遇和价值。在这篇文章中,我们将探讨数字化转型所能带来的价值。 数字化转型能够显著提升效率和生产力。通过引入…

【React学习】React组件生命周期

1. 介绍 在 React 中,组件的生命周期是指组件从被创建到被销毁的整个过程。React框架提供了一系列生命周期方法,在不同的生命周期方法中,开发人员可以执行不同的操作,例如初始化状态、数据加载、渲染、更新等。一个组件的生命周期…

TCP中窗口和滑动窗口的含义以及流量控制

一.窗口 在TCP中由于要保证可靠性,所以每发送一条数据后,都需要接收方返回一条应答报文,要是我们每发送一条数据,发送方就等待接收应答报文,收到之后再去发送下一条数据,这样我们就会花费大量的时间在等待应…