目录
一. 项目背景
在我司开发的某能源APP中,服务于多家用能企业员工。在这几年的开发中,也着实遇到过很多用户上报的反馈意见,但是有些问题的根源却不得而知,有时候甚至需要对一个模块整体重构才解决问题,经过了这几年迭代,将线上应用的一些实战经验分享于此。
在开发App过程中,经常会遇到OOM(out of memory)内存溢出的情况,本文将结合自己多年的开发经验,探讨下遇到性能问题的解决方案。
二.所遇到的挑战
在我司上线的App和小程序中,会遇到很多并发的操作,尤其在App端更为严重,在有些控制操作时,往往受到网络波动、响应速度的影响,其操作经常会失败、异常退出、甚至闪退等问题,遇到此类问题,往往是程序员头疼的事,因为问题不好定位,不容易发现问题出现的位置。也给线上用户带来了不好的体验。
虽然是2B的业务,短期虽不会对用户量造成损失,但是如果长期解决不了此类问题,不少用户的使用频率逐渐下降。还有些类似手机端远程智能控制机器的场景,如果发生错误,可能呢还会造成不必要的经济损失。
三.以OOM为例,讲解下此类问题的解决步骤
1.什么是OOM
09-08 21:15:38.771: E/dalvikvm-heap(123452): Out of memory on a 20455736-byte allocation. 09-08 21:15:38.779: E/AndroidRuntime(23231): java.lang.OutOfMemoryError
这就是因为程序申请使用“20455736”byte 内存,但Java虚拟机无法提供这么大的内存,所以程序就被迫死掉了。
2.如何查看JVM虚拟机的可用内存
我们系统的内存不会全部给JVM虚拟机来使用的,所以,我们需要得知真正的可用内存是多少,才能知道我们应该优化的方向。
在开发中,可以通过java 一下命令,来测试出,当前系统的可用内存
如: 执行命令java -Xmx2000M -version,如果显示出java的版本信息,则说明2000M内存是可用的。
如此这般,可以一直修改内存大小来测试,直到显示出:
出现上图内容,说明内存已经超出了JVM可用的内存了,那这个内存就是当前JVM的最大内存。
我们可以根据模拟器设置的内存大小,计算出能供给应用使用的内存大小了。不同的机型,内存也不一样大,要确保主流机型偏下的配置,能够正常流畅的运行程序。
3.如何监控和优化应用的内存情况
不管是Android APP还是Java程序我们都可以通过监控该APP在JVM中的运行情况,来判断是否出现异常。
我们可以通过JDK监视和管理控制台(jconsole.exe),在安装目录:jdk/bin。
通过此平台,可以监控应用的运行情况,占用内存情况,已经是否出现死锁的线程,如下图:
4.内存正常与异常情况对比
有内存问题的应用,其内存曲线是这样的(持续递增,不会下降):
上图,表示应用内存从启动后,一直递增,垃圾回收后,内存依然供不应求。那么曲线的终点就是应用发生OOM的极限附近值了。
5.应用的内存优化
内存优化的目的,就是通过我们对JVM参数的调整,让应用所使用的内存,能够很好的回收,长期运行也不会出现内存只增不减的现象。
①.优化老生代内存占比
如果Old GC占用的内存一直递增,且占比较大,我们可以通过优化JVM参数,将-XX:NewRatio值设置为4,意味着:新生代占1,老年代占4,年轻代占整个堆的1/5。这样优化,多分配给GC 老年代的一些内存。
SurviorRatio也设置为4,即设置Eden区的比例占多少,S0/S1相同。
②.提高survivor区的目标使用率
设置survivor区的目标使用率,当使用率达到时重新调整TenuringThreshold值,让对象尽早的去old区。
-XX:TargetSurvivorRatio:一个计算期望Survivor区存活大小(Desired survivor size)的参数。默认值为50,即50%
-XX:TargetSurvivorRatio=50 (默认为50,即达到)
对象进入Survivor区之后,由于使用的是默认的自适应策略,导致当年龄为1的时候,Minor GC1次,就被移动到老年代了。导致老年代内存消耗过快,在并发场景下,一会就发生一次FGC。
③.调高程序运行期间的最大内存
如果通过优化后,程序在某些耗费资源的时间点,依然需要很大的内存,那么我们只能调高JVM中应用运行的最大内存。
④.选择最佳的垃圾回收算法
不同垃圾回收方法测试数据,在第①步和第②步中,我们实际是一点点的测试,最后确定使用哪种方案的,多次试验记录:
Id |
NewRatio |
|
SurviorRatio |
TransResponse Time |
Throughput |
Passed Transactions |
|
1 |
2 |
|
25 |
3.139s |
3016230.514 |
7528 |
|
2 |
1 |
|
25 |
3.161s |
2975581.301 |
7452 |
|
3 |
3 |
|
25 |
2.814s |
3334717.818 |
8383 |
|
4 |
4 |
|
25 |
2.659s |
3505592.450 |
8846 |
|
5 |
5 |
|
25 |
2.860s |
3270596.069 |
8232 |
|
6 |
4 |
|
15 |
2.499s |
3765121.986 |
9426 |
|
7 |
4 |
|
5 |
1.986s |
4750776.581 |
11843 |
|
8 |
4 |
|
4 |
1.968s |
4825608.161 |
11947 |
|
9 |
4 |
|
3 |
2.507s |
3770420.243 |
9388 |
|
10 |
|
-XX:TargetSurvivorRatio=90 |
1.924 |
4945053.874 |
12216 |
||
11 |
|
-Xmx1024M |
1.903 |
4974137.908 |
12360 |
四.对IOS及React Native应用的监控
上述提到动监控工具以及优化策略都是针对基于Java平台的,那么像IOS和React Native开发的应用该怎样监控呢?如何了解这些应用运行时,发生了哪些错误和异常呢?如何将这些错误收集,转入到开发人员的Bug修复计划中呢?
在此平台上,我们可以实时监控当天应用出现的异常,并且有完整的统计发成的异常以及异常数、机型、版本号、应用下载来源市场、操作系统、用户地域。也有关于应用对各个机型的内存的占用情况,我们用再多的模拟器和真机测试,也抵不上真实的用户的大数据。
友盟U-APM移动应用性能监控平台提供的崩溃、卡顿、启动、内存、网络等分析数据,可以全方位覆盖开发人员还原Bug的各项指标,帮助开发人员快速定位和修复bug。
五.总结
1、性能调优要做到有的放矢,需要根据实际业务系统的特点,以一定时间的JVM日志记录为依据,进行有针对性的调整、比较和观察。
2、性能调优是个及其耗时且无止境的过程,要综合权衡调优成本(包含人力成本)和更换硬件成本的大小,来换取用户体验。
3、性能调优不仅仅包括JVM的调优,还有服务器硬件配置、操作系统参数、中间件线程池、数据库连接池、数据库本身参数以及具体的数据库表、索引、分区等的调整和优化。
4、通过特定工具(应用性能监控平台U-APM)检查代码中存在的性能问题并加以修正是一种比较经济快捷的调优方法,上工治未病,不能等到用户反馈,开发人员才迟迟发现问题。所以,监控平台一定要上,将平台捕捉到的异常,修复和优化在用户感知之前
友盟U-APM移动应用性能监控平台提供的崩溃、卡顿、启动、内存、网络等分析数据,可以全方位覆盖开发人员还原Bug的各项指标,帮助开发人员快速定位和修复bug。
附录:《个人简介》
本人目前就职于某上市能源企业,从事互联网开发行业7年,在移动开发方向,参与过两款Android APP和RN小程序,在其中担任APP架构设计和主要开发人员。目前负责能源互联网数字化转型相关工作,对物联网数据平台、监控平台、能源管理平台等应用有多年经验,对数据治理、数据管理、应用开发、平台运维都深有涉及。
业余时间,喜阅读,学习不限于计算机领域的知识。常撰写博客,分享技术和从业知识,与同行探讨技术细节。