内存优化_内存表优化 - CSDN
内存优化 订阅
程序在运行时,Windows会将其直接调入到物理内存中,但物理内存毕竟有限,因此,微软又设计了虚拟内存,它其实就是硬盘中的一块空间,Windows会将一些暂时不用,但可能以后会用到的数据从物理内存移动到虚拟内存中,从而保证有足够的物理内存给当前运行的程序使用。所以,电脑的内存=实际物理内存容量+“分页文件”(就是交换文件)。如果需要,“分页文件”会动用硬盘上所有可用空间。内存优化的好处是:在将占用物理内存的程序移动到虚拟内存后,再启动新程序,程序、系统运行的速度会变得更快,提升系统工作效率。 展开全文
程序在运行时,Windows会将其直接调入到物理内存中,但物理内存毕竟有限,因此,微软又设计了虚拟内存,它其实就是硬盘中的一块空间,Windows会将一些暂时不用,但可能以后会用到的数据从物理内存移动到虚拟内存中,从而保证有足够的物理内存给当前运行的程序使用。所以,电脑的内存=实际物理内存容量+“分页文件”(就是交换文件)。如果需要,“分页文件”会动用硬盘上所有可用空间。内存优化的好处是:在将占用物理内存的程序移动到虚拟内存后,再启动新程序,程序、系统运行的速度会变得更快,提升系统工作效率。
信息
中文名
内存优化
目    的
主要是为了保持虚拟内存的连续性
提    高
内存的使用效率
优    化
内存的管理
内存优化技巧方法
如何优化内存的管理,提高内存的使用效率,尽可能地提高运行速度,是我们所关心的问题。下面介绍在Windows操作系统中,提高内存的使用效率和优化内存管理的几种方法。改变页面文件的位置其目的主要是为了保持虚拟内存的连续性。因为硬盘读取数据是靠磁头在磁性物质上读取,页面文件放在磁盘上的不同区域,磁头就要跳来跳去,自然不利于提高效率。而且系统盘文件众多,虚拟内存肯定不连续,因此要将其放到其他盘上。改变(调整大小)页面文件位置的方法是:用鼠标右键点击“我的电脑”,选择“属性→ 高级→性能设置→高级→更改虚拟内存”,在驱动器栏里选择想要改变到的位置即可。值得注意的是,当移动好页面文件后,要将原来的文件删除(系统不会自动删除)。因为C盘扇区最近,所以尽量把虚拟内存设置在C盘,且不要设置其他盘的内存,使其具有连续性。改变页面文件的大小改变了页面文件的位置后,我们还可以对它的大小进行一些调整。调整时我们需要注意,不要将最大、最小页面文件设为等值。因为通常内存不会真正“塞满”,它会在内存储量达到一定程度时,自动将一部分暂时不用的数据放到硬盘中。最小页面文件越大,所占比例就低,执行的速度也就越慢。最大页面文件是极限值,有时打开很多程序,内存和最小页面文件都已“塞满”,就会自动溢出到最大页面文件。所以将两者设为等值是不合理的。一般情况下,最小页面文件设得小些,这样能在内存中尽可能存储更多数据,效率就越高。最大页面文件设得大些,以免出现“满员”的情况。禁用页面文件当拥有了8GB以上的内存时,页面文件的作用将不再明显,因此我们可以将其禁用。方法是:依次进入注册表编辑器“HKEY_LOCAL_MACHINESystemCurrentControlSetControlSession Ma-nagerMemoryManagement”下,在“DisablePa-ging Executive”(禁用页面文件)选项中将其值设为“1”即可。清空页面文件在同一位置上有一个“ClearPageFileAtShutdown(关机时清除页面文件)”,将该值设为“1”。这里所说的“清除”页面文件并非是指从硬盘上完全删除pagefile.sys文件,而是对其进行“清洗”和整理,从而为下次启动Windows XP时更好地利用虚拟内存做好准备。调整高速缓存区域的大小可以在“计算机的主要用途”选项卡中设置系统利用高速缓存的比例(针对Windows 98)。如果系统的内存较多,可选择“网络服务器”,这样系统将用较多的内存作为高速缓存。在CD-ROM标签中,可以直接调节系统用多少内存作为CD-ROM光盘读写的高速缓存。监视内存系统的内存不管有多大,总是会用完的。虽然有虚拟内存,但由于硬盘的读写速度无法与内存的速度相比,所以在使用内存时,就要时刻监视内存的使用情况。Windows操作系统中提供了一个系统监视器,可以监视内存的使用情况。一般情况下如果只有60%的内存资源可用,这时你就要注意调整内存了,不然就会严重影响电脑的运行速度和系统性能,否则会很卡的。及时释放内存空间如果你发现系统的内存不多了,就要注意释放内存。所谓释放内存,就是将驻留在内存中的数据从内存中释放出来。释放内存最简单有效的方法,就是重新启动计算机。另外,就是关闭暂时不用的程序。还有要注意剪贴板中如果存储了图像资料,是要占用大量内存空间的。这时只要剪贴几个字,就可以把内存中剪贴 板上原有的图片冲掉,从而将它所占用的大量的内存释放出来。优化内存中的数据在Windows中,驻留内存中的数据越多,就越要占用内存资源。所以,桌面上和任务栏中的快捷图标不要设置得太多。如果内存资源较为紧张,可以考虑尽量少用各种后台驻留的程序。平时在操作电脑时,不要打开太多的文件或窗口。长时间地使用计算机后,如果没有重新启动计算机,内存中的数据排列就有可能因为比较混乱,从而导致系统性能的下降。这时你就要考虑重新启动计算机。提高系统其他部件的性能计算机其他部件的性能对内存的使用也有较大的影响,如总线类型、CPU、硬盘和显存等。如果显存太小,而显示的数据量很大,再多的内存也是不可能提高其运行速度和系统效率的。如果硬盘的速度太慢,则会严重影响整个系统的工作。提高计算机运算速度优质的内存能提高计算机的内存读取力度,加强计算机各部件功能的协调性,使计算机的运行速度更流畅。
收起全文
  • 定好优化目标: 比如针对 512MB 的设备...面向东南亚、非洲用户,那对内存优化的标准就要变得更苛刻一些。 内存优化 3 方面 设备分级、Bitmap 优化和内存泄漏这三个方面入手。 1、设备分级 类似 device-year-class ...

    参考文章:https://time.geekbang.org/column/article/71610

    定好优化目标:
    比如针对 512MB 的设备和针对 2GB 以上的设备,完全是两种不同的优化思路。
    面向东南亚、非洲用户,那对内存优化的标准就要变得更苛刻一些。

    内存优化 3 方面

    设备分级、Bitmap 优化和内存泄漏这三个方面入手。

    1、设备分级

    • 类似 device-year-class ,低端机关闭复杂动画,或者某些功能,使用 565 格式图片,更小缓存等。
    if (year >= 2013) {
        // Do advanced animation
    } else if (year >= 2010) {
        // Do simple animation
    } else {
        // Phone too slow, don't do any animations
    }
    
    
    • 缓存管理
      统一缓存。当 系统内存不足 时,及时释放内存。
    • 进程模型
      一个 空进程也会占用 10MB 内存。有节操的增加进程。
    • 安装包大小
      代码、资源、图片以及 so 库的体积,跟它们占用的内存有很大的关系。

    2、Bitmap 优化

    Android bitmap 演进分析:

    • Android 2.x系统,当dalvik allocated + external allocated + 新分配的大小 >= dalvik heap 最大值时候就会发生OOM。其中bitmap是放于external中 。
      Bitmap 对象放在 Java 堆,而像素数据是放在 Native 内存中。如果不手动调用 recycle,Bitmap Native 内存的回收完全依赖 finalize 函数回调,熟悉 Java 的同学应该知道,这个时机不太可控。
      Android 2.x 系统 BitmapFactory.Options 里面隐藏的的inNativeAlloc反射打开后,申请的bitmap就不会算在external中。

    • Android 3.0~Android 7.0 将 Bitmap 对象和像素数据统一放到 Java 堆中。
      这样就算我们不调用 recycle,Bitmap 内存也会随着对象一起被回收。
      Java 堆限制也才到 512MB,可能我的物理内存还有 5GB,但是应用还是会因为 Java 堆内存不足导致 OOM.
      Android 4.x系统,废除了external的计数器,类似bitmap的分配改到dalvik的java heap中申请,只要allocated + 新分配的内存 >= dalvik heap 最大值的时候就会发生OOM(art运行环境的统计规则还是和dalvik保持一致)
      可采用facebook的fresco库,即可把图片资源放于native中。

    • android 8.x 从Java heap 移到了native heap
      有没有一种实现,可以将 Bitmap 内存放到 Native中,也可以做到和对象一起快速释放,同时 GC 的时候也能考虑这些内存防止被滥用?NativeAllocationRegistry 可以一次满足你这三个要求,Android 8.0 正是使用这个辅助回 Native 内存的机制.
      Android 8.0 还新增了硬件位图 Hardware Bitmap,它可以减少图片内存并提升绘制效率。

    具体方法:

    1. 统一图片库
      eg:低端机使用 565 格式。可以使用 Glide、Fresco 或者采取自研都可以。而且需要进一步将所 Bitmap.createBitmap、BitmapFactory 相关的接口也一并收拢。

    2. 统一监控
      在统一图片库后就非常容易监控 Bitmap 的使用情况了,这里主要有三点需要注意。

    • 大图片监控。 防止超宽。像素浪费
      即图片的大小不应该超过view的大小。对此,我们可以重载drawable与ImageView.
      图片存在像素浪费,合理利用.9图
    • 重复图片监控。
      作者回复:这个重复bitmap分析是在服务器后台做的,目前是对所有 bitmap 数组直接计算hash的方法匹对。
    • 图片总内存。
      在 OOM 崩溃的时候,也可以把图片占用的总内存、Top N 图片的内存都写到崩溃日志中,帮助我们排查问题。

    一个好的imageLoader,可以将2.X、4.X或5.X对图片加载的处理对使用者隐藏,同时也可以将自适应大小、质量等放于框架中。

    3、内存泄漏

    内存泄漏简单来说就是没有回收不再使用的内存。

    内存泄漏主要分两种:
    同一个对象泄漏。
    每次都会泄漏新的对象,可能会出现几百上千个无用的对象。

    优秀的框架设计可以减少甚至避免程序员犯错。很多内存泄漏都是框架设计不合理所导致,各种各样的单例满天飞,MVC 中 Controller 的生命周期远远大于 View。

    • Java 内存泄漏
      建立类似 LeakCanary 自动化监测方案。
      在开发过程,我们希望出现泄漏时可以弹出对话框,让开发者更加容易去发现和解决问题。
    • OOM 监控
      美团有一个 Android 内存泄露自动化链路分析组件 Probe ,它在发生 OOM 的时候生成 Hprof 内存快照,然后通过单独进程对这个文件做进一步的分析。不过在线上使用这个文件做进一步的分析。不过在线上使用这个工具风险还是比较大,在崩溃的时候生成内存快照有可能会导致二次崩溃,而且部分手机生成 Hprof 快照可能会耗时几分钟,这对用户造成的体验影响比较大。
    • Native 内存泄漏监控
      上一期我讲到 Malloc 调试(Malloc Debug)和 Malloc 钩子(Malloc Hook)似乎还不是那么稳定。在 WeMobileDev 最近的一篇文章《微信 Android 终端内存优化实践》中,微信也做了一些其他方案上面的尝试。https://mp.weixin.qq.com/s/KtGfi5th-4YHOZsEmTOsjg?
      目前还不那么完善。

    开发过程中内存泄漏排查可以使用 Androd Profiler 和 MAT 工具配合使用,而日常监控关键是成体系化,做到及时发现问题。

    内存监控

    内存泄漏的监控存在一些性能的问题,一般只会对内部人员和极少部分用户开启。
    线上:需要通过其他更有效的方式去监控内存相关的问题。

    1. 采集方式
      要按照用户抽样,而不是按次抽样。持续采集命中用户。
      用户在前台的时候,可以每 5 分钟采集一次 PSS、Java 堆、图片总内存。

    2. 计算指标

      内存 UV 异常率 = PSS 超过 400MB 的 UV / 采集 UV
      其中 PSS 的值可以通过 Debug.MemoryInfo 拿到 其中 PSS 的值可以通过 Debug.MemoryInfo 拿到。
      触顶率:可以反映 Java 内存的使用情况,如果超过 85% 最大堆限制,GC 会变得更加频繁,容易造成 OOM 和卡顿。

    内存 UV 触顶率 = Java 堆占用超过最大堆限制的 85% 的 UV / 采集 UV
    
    long javaMax = runtime.maxMemory();
    long javaTotal = runtime.totalMemory();
    long javaUsed = javaTotal - runtime.freeMemory();
    // Java 内存使用超过最大限制的 85%
    float proportion = (float) javaUsed / javaMax;
    
    

    一般客户端只上报数据,所有计算都在后台处理,这样可以做到灵活多变。

    1. GC监控

    在实验室或者内部试用环境,我们也可以通过 Debug.startAllocCounting 来监控 Java 内存分配和 GC 的情况,需要注意的是这个选项对性能有一定的影响,虽然目前还可以使用,但已经被 Android 标记为 deprecated。通过监控,我们可以拿到内存分配的次数和大小,以及 GC 发起次数等信息。

    long allocCount = Debug.getGlobalAllocCount();
    long allocSize = Debug.getGlobalAllocSize();
    long gcCount = Debug.getGlobalGcInvocationCount();
    

    在 Android 6.0 之后系统可以拿到更加精准的 GC信息。

    // 运行的 GC 次数
    Debug.getRuntimeStat("art.gc.gc-count");
    // GC 使用的总耗时,单位是毫秒
    Debug.getRuntimeStat("art.gc.gc-time");
    // 阻塞式 GC 的次数
    Debug.getRuntimeStat("art.gc.blocking-gc-count");
    // 阻塞式 GC 的总耗时
    Debug.getRuntimeStat("art.gc.blocking-gc-time");
    

    需要特别注意阻塞式 GC 的次数和耗时,因为它会暂停应用线程,可能导致应用发生卡顿。

    展开全文
  • 说起内存我们要先了解JAVA虚拟机 JAVA虚拟机 java虚拟机在运行程序时会在内存空间分配一块区域用于程序的运行,虚拟机又会把这一块区域划分为若干个不同的数据块 线程私有:程序计数器、虚拟机栈、本地方法栈; ...

    说起内存我们要先了解JAVA虚拟机

    JAVA虚拟机

    java虚拟机在运行程序时会在内存空间分配一块区域用于程序的运行,虚拟机又会把这一块区域划分为若干个不同的数据块

    线程私有:程序计数器、虚拟机栈、本地方法栈;

    共享数据区:方法区、java堆。

     

     

    程序计数器相当于一个执行代码的指示器,用来确认下一行执行代码的地址,每个线程都有一个,没有OOM的区。

    虚拟机栈:我们平常所说的栈就是指这一块区域,虚拟机规范中定义了OOM和stackoverflow异常。

    本地方法栈:java虚拟机规范中定义了OOM和stackoverflow异常。

    在hotspotVM(Sun JDK虚拟机)把虚拟机栈和本地方法栈合为一个栈区。

    方法区:分配给ClassLoader加载类信息,常量,静态变量,编译后的代码。

    堆区:分配给对象实例和数组,是虚拟机管理的最大的一块内存,是GC的主战场。

    GC垃圾回收器

    GC如何确定内存回收:

    1、引用计数算法

    就是对象在引用一次计数+1,置空时-1,只有计数为0时进行回收,其缺点也很明显,就是互相引用时计数永不为0。

    2、可达性分析算法

    当一个对象到GC Root没有任何引用链相连时(即从GC Roots节点到该节点不可达),则证明该对象是不可用的。

    3、引用类型

    强引用:不会主动被回收。

    软引用(SoftReference):内存不足时回收,存放一些重要性不是很强又不能随便让清除的对象,比如图片切换到后台不需要马上显示了。

    弱引用(WeakReference):第一次扫到了,就标记下来,第二次扫到直接回收。

    虚引用(PhantomReference):幽灵幻影引用   不对生存造成任何影响,用于跟踪GC的回收通知

    内存泄漏

    产生原因:一个长生命周期的对象持有一个短生命周期对象的引用,通俗讲就是该回收的对象,因为引用问题没有被回收,最终会产生OOM。

     

     

    展开全文
  • iOS性能优化-内存优化

    2018-09-12 16:22:25
    一、为什么需要内存优化 二、内存管理 三、常见问题 四、内存占用 五、检测工具 摘要 一、为什么需要内存优化 The easy answer is users have a better experience. Not only will your app launch faster....

    一、为什么需要内存优化

    The easy answer is users have a better experience.
    Not only will your app launch faster.
    The system will perform better.
    Your app will stay in memory longer.
    Other apps will stay in memory longer.
    Pretty much everything’s better.


    二、内存管理

    Objective-C语言本身是支持垃圾回收机制的,但有平台局限性,仅限于Mac桌面系统开发中,而在iPhone和iPad等苹果移动终端设备中是不支持垃圾回收机制的。在移动设备开发中的内存管理是采用MRC(Manual Reference Counting)以及iOS5以后的ARC(Automatic Reference Counting),本质都是RC引用计数,通过引用计数的方式来管理内存的分配与释放,从而防止内存泄漏。

    内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。


    三、常见问题

    内存问题主要包括两个部分,一个是iOS中常见循环引用导致的内存泄露 ,另外就是大量数据加载及使用导致的内存警告。

    1、mmap
    虽然苹果并没有明确每个App在运行期间可以使用的内存最大值,但是有开发者进行了实验和统计,一般在占用系统内存超过20%的时候会有内存警告,而超过50%的时候,就很容易Crash了,所以内存使用率还是尽量要少,对于数据量比较大的应用,可以采用分步加载数据的方式,或者采用mmap方式。mmap 是使用逻辑内存对磁盘文件进行映射,中间只是进行映射没有任何拷贝操作,避免了写文件的数据拷贝。 操作内存就相当于在操作文件,避免了内核空间和用户空间的频繁切换。之前在开发输入法的时候 ,词库的加载也是使用mmap方式,可以有效降低App的内存占用率。

    1>Cell的重用机制,包括UITableView、UICollectionView。

    2、循环引用
    循环引用是iOS开发中经常遇到的问题,尤其对于新手来说是个头疼的问题。循环引用对App有潜在的危害,会使内存消耗过高,性能变差和Crash等,iOS常见的内存主要以下三种情况:

    1>Delegate
    代理声明为weak是一个即好又安全的做法
    @property (nonatomic, weak) id <MyCustomDelegate> delegate;

    2>NSTimer
    使用类方法
    使用weakProxy
    使用GCD timer

    3>Block

    3、其他
    1>NSNotification addObserver之后,在dealloc里面添加remove。
    2>动画的repeat count无限大,而且也不主动停止动画,基本就等于无限循环。
    3>forwardingTargetForSelector不能返回self。
    4>UIGraphicsBeginImageContext之后调用UIGraphicsEndImageContext。
    5>C语法,malloc之后调用free。
    6>CoreFoundation
    7>明确标注需要release的函数。
    SecPKCS12Import、protocol_copyMethodDescriptionList等


    四、内存占用


    五、检测工具

    1、Xcode Memory Debugger
    2、Instruments
    3、FBRetainCycleDetector
    FBAlloca1onTracker
    FBMemoryProfiler
    4、MLeaksFinder


    摘要

    https://developer.apple.com/videos/play/wwdc2018/416/
    https://juejin.im/post/5b23dafee51d4558e03cbf4f
    https://blog.csdn.net/cordova/article/details/60958978
    https://juejin.im/post/58ca0832a22b9d006418fe43

    展开全文
  • 内存优化

    2019-01-08 14:59:18
    内存泄漏 内存管理 内存模型  Android原生开发以java为主。 在java中,Java内存模型,往往是指Java程序在运行时内存的模型,而Java代码是运行在Java虚拟机之上的,所以Java内存模型,也就是指Java虚拟机的运行...

    内存泄漏

    内存管理

    内存模型

        Android原生开以java主。

    在java中,Java内存模型,往往是指Java程序在运行时内存的模型,而Java代码是运行在Java虚拟机之上的,所以Java内存模型,也就是指Java虚拟机的运行时内存模型。

     

             java中内存全权交给虚拟机去管理,那虚拟机的运行时内存是如何构成的?

            

    很多时候,我们提到内存,会说到堆和栈,这是对内存粗略的一种划分,这种划分的对应内存模型的Java堆,是指虚拟机栈,但是实际上Java内存模型比这复杂多了。

            

             在曾经的日公司(sun 已被甲骨文2009收购) 制定的java虚拟机规范中,运行时内存模型,分为线程私有和共享数据区两大类,其中线程私有的数据区包含程序计数器、虚拟机栈、本地方法区,所有线程共享的数据区包含Java堆、方法区,在方法区内有一个常量池。

     

     

    2.1 程序数器PC

    程序计数器PC是一块较小的内存空间,可以看作所执行字节码的行号指示器。字节码解释器就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,比如循环、跳转、异常处理等等这些基础功能都需要依赖这个计数器来完成。

    在开发多线程应用时,由于Java中的多线程是抢占式的调用,也就是任何一个确定的时刻,cpu都只会执行一条线程,执行哪条线程也是不确定的。所以为了线程切换后能够回到正确的执行位置,每个线程都需要一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,所以这块区域是线程私有的内存。

    当线程正在执行一个Java方法时,PC计数器记录的是正在执行的虚拟机字节码的地址;当线程正在执行的一个Native方法时,PC计数器则为空Undefined)。这一块的内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError的区域

    2.2

    和程序计数器一样,虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是java方法执行的内存模型。

    每个方法(不包含native方法)执行的同时都会创建一个栈帧 用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

             我们平时所说的栈内存就是指的这一块区域。

    Java虚拟机规范规定该区域有两种异常:

    • StackOverFlowError:当线程请求栈深度超出虚拟机栈所允许的深度时抛出 (递归函数)
    • OutOfMemoryError:当Java虚拟机动态扩展到无法申请足够内存时抛出 (OOM)

    2.3 本地方法

    本地方法栈和虚拟机栈差不多,前者是为虚拟机使用到的Native方法提供内存空间。有些虚拟机的实现直接把本地方法栈和虚拟机栈合二为一,比如主流的HotSpot虚拟机。

    异常(Exception)Java虚拟机规范规定该区域可抛出StackOverFlowErrorOutOfMemoryError

    2.4 Java

    Java堆,是Java虚拟机管理的最大的一块内存,也是GC的主战场,所以可以叫它gc(垃圾堆),里面存放的是几乎所有的对象实例和数组数据。

    异常(Exception)Java虚拟机规范规定该区域可抛出OutOfMemoryError

    2.5 方法区

    方法区主要存放的是已被虚拟机加载的类信息、常量、静态变量、编译器编译后的代码等数据。Java虚拟机规范对这一块区域的限制非常宽松,不同的虚拟机实现也不同,相对而言垃圾回收在这个区域比较少的出现。根据java虚拟机规范,当方法区无法满足内存分配需求时,会抛出oom异常。

    2.6 运行常量池

    运行时常量池是方法区的一部分,用于存放编译器生成的各种字面量和符号引用。运行时常量池除了编译期产生的Class文件的常量池,还可以在运行期间,将新的常量加入常量池,比较String类的intern()方法。

    • 字面量:与Java语言层面的常量概念相近,包含文本字符串、声明为final的常量值等。
    • 符号引用:编译语言层面的概念,包括以下3类:
      • 类和接口的全限定名
      • 字段的名称和描述符
      • 方法的名称和描述符

    属于方法区一部分,所以和方法区一样,会oom

     

    局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中。

    ——因为它们属于方法中的变量,生命周期随方法而结束。

    成员变量全部存储与堆中(包括基本数据类型,引用和引用的对象实体)

    ——因为它们属于类,类对象终究是要被new出来使用的。

     

    们说的内存泄露,是针对,也只针对堆内存,他存放的就是引用指向的体。

     

    内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage CollectionGC)完成的,java程序员不需要通过调用函数来释放内存,但gc只能回收无用并且不再被其它对象引用的那些对象所占用的空间。

     

    堆中几乎存放着Java世界中所有的例,垃圾收集器在堆回收之前,第一件事情就是要确定象哪些存活着,哪些象已死去(即不可能再被任何途径使用的)

     

    确定象是否活着的方法有:

    1、引用数算法

           1.1算法分析 

    引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数。当一个对象被创建时,且将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1a = b,b引用的对象实例的计数器+1),当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1

    1.2优缺点

    优点:

      引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。

    缺点:

      无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.

    1引用计数算法无法解决循环引用问题,例如:

    public class Main {
        public static void main(String[] args) {
            MyObject object1 = new MyObject();
            MyObject object2 = new MyObject();
            
            object1.object = object2;
            object2.object = object1;
            
            object1 = null;
            object2 = null;
        }
    }

    最后面两句将object1object2赋值为null,也就是说object1object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数器都不为0,那么垃圾收集器就永远不会回收它们。

     

     

    2、可达性分析算法(主流方法)

    https://img-blog.csdn.net/20160728194719701

      可达性分析算法中,通过一系列的gc root为起始点,从一个GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。

    java中可作为GC Root的对象有

      1.虚拟机栈(本地变量表)中正在运行使用的引用

      2.方法区中静态属性引用的对象

      3. 方法区中常量引用的对象

    4.本地方法栈JNI中引用的对象(Native对象)

    上图中objDobjEGC ROOT不可达,所以可以被回收。而其他的对gc root可达。

    在代码看来,类似于:(GC/Main.java

     

    但是即使在可达性分析算法中不可达的对象,也并非一定要死。当gc第一次扫过这些对象的时候,他们处于“死缓”的阶段。要真正执行死刑,至少需要经过两次标记过程。如果对象经过可达性分析之后发现没有与GC Roots相关联的引用链,那他会被第一次标记,并经历一次筛选。这个对象的finalize方法会被执行。如果对象没有覆盖finalize或者已经被执行过了。虚拟机也不会去执行finalize方法。Finalize是对象逃狱的最后一次机会。

    Reference项目的FinalizeEscapeGC

    输出结果:

    在例子中,对象第一次被执行了finalize方法,但是把自己上交给国家逃了一死,但是在给国家执行任务的时候,不幸牺牲了。所以没办法再自救了。

    这个对象的finalize方法执行了一次(自救而不是被救,this赋值,所以给的还是自己)

     

     

    在说到内存的问题,我们都会提到一个关键词:引用。

        通俗的,通A用并访问到B,那就明A持有B的引用,或A就是B的引用。

        比如 Person p1 = new Person();P1能操作Person对象,因此P1Person的引用;p1是O中的一个成员变量,因此我可以使用o.p1的方式来访问Person类对象的成员,因此o持有一个Person对象的引用

       

        GC过程与对象的引用类型是密切相关的,

    Java对引用的分类Strong reference(强), SoftReference(软), WeakReference(), PhatomReference()

    强引用就是在程序代码中普遍存在的,比如”Object obj = new Object()”这种引用,只要强引用还在,垃圾收集器就不会回收被引用的对象。

    软引用用来定义一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要内存溢出之前,会将这些对象列入回收范围进行第二次回收,如果回收后还是内存不足,才会抛出内存溢出。

    弱引用也是用来描述非必须对象。但他的强度比软引用更弱一些。被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器回收时,无论内存是否足够,都会回收掉被弱引用关联的对象。

    虚引用也称为幽灵引用或者幻影引用,是最弱的引用关系。一个对象的虚引用根本不影响其生存时间,也不能通过虚引用获得一个对象实例。虚引用的唯一作用就是这个对象被GC时可以收到一条系统通知。

     

    Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且生命周期较长候,可以尽量引用和弱引用技

     

    对于软引用和弱引用的选择,

    如果只是想避免OutOfMemory异常的生,可以使用引用。如果用的性能更在意,想尽快回收一些占用内存比大的象,可以使用弱引用。另外可以根据象是否常使用来判断选择软引用是弱引用。如果该对象可能会常使用的,就尽量用引用。如果该对象不被使用的可能性更大些,就可以用弱引用。

     

    内存泄漏就是

    堆内存中的生命周期的象持有短生命周期象的引用,尽管短生命周期象已不再需要,但是因为长生命周期象持有它的引用而致不能被回收,就是Java中内存泄露的根本原因。

     总结一句就是不需要了该回收因引用问题导致不能回收。

     

    内存泄漏会致可用内存慢慢少,程序慢慢卡。最终还会导致臭名昭著的oom 内存溢出。

     

     

    我们的android程序应该怎么排查内存泄漏问题呢?

    在android中我们执行一段代,比如入了一个新的面(Activity),这时候我的内存使用肯定比在前一个面大,而在界面finish返回后,如果内存没有回落,那么很有可能就是出了内存泄漏。

     

    从内存监控工具中观察内存曲线,是否存在不断上升的趋势且不会在程序返回时明显回落。这种方式可以发现最基本,也是最明显的内存泄露问题,对用户价值最大,操作难度小,性价比极高。

    因为他能发现很明显很严重的内存泄漏问题

     

    可以通AS的Memory Profile或者DDMS中的heap察内存使用情况。

       

     

     

    Android Profile

       https://developer.android.com/studio/preview/features/android-profiler.html

     

         The new Android Profiler window in Android Studio 3.0 replaces the Android Monitor tools.

     

        在Android Studio我们可以 运行app。

    然后我们能看到

     

    我们点击memory后

    • ① 强制执行垃圾收集事件的按钮。
    • ② 捕获堆转储的按钮。
    • ③ 记录内存分配的按钮。
    • ④ 放大时间线的按钮。
    • ⑤ 跳转到实时内存数据的按钮。
    • ⑥ 事件时间线显示活动状态、用户输入事件和屏幕旋转事件。
    • ⑦ 内存使用时间表,其中包括以下内容: 
      • 每个内存类别使用多少内存的堆栈图,如左边的y轴和顶部的颜色键所示。
      • 虚线表示已分配对象的数量,如右侧y轴所示。
      • 每个垃圾收集事件的图标。

     

    与之前的Android监控工具相比,新的内存分析器记录了更多内存使用情况,所以看起来你的内存使用量会更高。内存分析器监视一些额外的类别,这些类别增加了总数。

     

     

    在比较茫然的情况下,不知道哪儿出现了内存泄漏,我们可以进行一刀切,来个大致的排查。

     

    我们进入了一大堆页面并最终返回到主页。

     

     

    然后gc ,再dump下内存查看。AS可以点击

    然后等待一段时间会出现:

     

    但是说实话这个页面想要分析出什么很难。一般不会使用这个页面来进行分析,最多打开看一眼刚刚我们进入的Activity是否因为我们退出而回收。

     

    先按照包名来分组,

    Alloc Cout : 对象数

    Shallow Size : 对象占用内存大小

    Retained Set : 对象引用组占用内存大小(包含了这个对象引用的其他对象)

     

    当然一次dump可能并不能发现内存泄漏,可能每次我们dump的结果都不同,那么就需要多试几次,然后结合代码来排查。

     

     

    这里还不能确定发生了内存泄漏。

    我们这时候可以借助一些更专业的工具来进行内存的分析。

    我们先把 这个内存快照保存为hprof文件。

     

    AS自动分析

    其实现在的AS,可以说是非常强大了。我们把刚刚保存的hprof文件拖入到AS中。

     

    这个自动分析任务包含了两个内容,一个是检测Activity的泄漏,一个是检测重复字符串。

    点击运行分析:

    这里出现了MainActivity的泄漏。并且观察到这个MainActivity可能不止一个对象存在,可能是我们上次退出程序的时候发生了泄漏,导致它不能回收。而在此打开app,系统会创建新的MainActivity。

    但是在AS上发现为何没被回收需要运气,更方便的工具是Mat。

     

     

    Memory Analyzer Tool基于eclipse

    可以直接下载:

    http://www.eclipse.org/mat/downloads.php

    也可以在eclispe上安装mat插件:

    点击eclipse marketplace...搜索memory。

     

     

    在使用mat之前我们需要把快照文件转换一下,

    转换工具在sdk/platform-tools/hprof-conv

    -z:排除不是app的内存,比如Zygote

    hprof-conv -z src dst

     

     

    然后在Mat中打开它:

    打开我们的快照文件,

     

    之后我们能看到

     

    我们点击

    以直方图的方式来显示当前内存使用情况可能更加适合较为复杂的内存泄漏分析,它默认直接显示当前内存中各种类型对象的数量及这些对象的shallow heap和retained heap。结合MAT提供的不同显示方式,往往能够直接定位问题

    • shallow heap:指的是某一个对象所占内存大小。
    • retained heap:指的是一个对象与所包含对象所占内存的总大小。

     

    out查看这个对象持有的外部对象引用

    incoming查看这个对象被哪些外部对象引用

     

    我们现在希望查看为什么这个对象还存在,那么

    排除软弱虚引用。

    关于这个问题是android系统的一个bug。

    原因是Activity的DecorView请求了InputMethodManager,而InputMethodManager一直持有DecorView的引用,导致无法释放Activity。

    解决办法是:

     

     

     

     

    这个问题是一个第三方库中持有引用,导致的无法释放。

    这个问题可能是这个第三方库本身的问题,也可能是我们使用不当引起的。为了确定这个问题。我们进入被混淆了的g.e这个类,

    注意这里是g.e中的a成员是一个HashMap,

    结合代码

     

    这里面保存的都是一些Observer对象。这里就需要结合对代码的熟悉度,加上一些猜测来寻找问题。

    在Activity中搜索Observer,可以找到很多个new Observer。

    但是我们注意,调用的地方第二个参数是true,表示注册;如果传false表示注销

     

    而这里只有注册,没有注销。

     

    我们在onDestory中加入注销。

     

    修改完成再次运行,然后退出app一次再打开:

    结果:

    还有两个Activity。

    第一个问题仍然是observer,但是是在MessageFragment。

    第二个问题也还是InputMethodmanager,但是泄漏点变了。

    修改:

     

     

    内存泄漏解决完成!

        这个app还有很多内存泄漏的地方。大家可以自己尝试去寻找并解决。比如MyInfoActivity。。。。大家自己去解决啊,有什么不懂得再问我。如果问的人多,下节课先解决掉这个泄漏。

     

    除了检查单个hprof文件之外,还能够使用多个hprof进行对比。

    比如我们在进入一个新页面之前先dump下来内存。然后再进入这个页面之后退出,再dump一份内存。通过对比就能够知道,进入这个页面之后增加了多少内存/对象等信息:

     

    之后在mat中打开这个两个文件并都切换到直方图查看。

    再然后把两个直方均加入对比,点击 ,或者

     

    执行对比:

     

    再把视图切换到difference from base table(与第一个的不同)

     

    然后能看到

     

    第二行就是指第二个文件相比第一个文件多出来了几个对象。

    如果存在增加了不合理的对象,同样可以查看其GC root。

     

     

    =====================================================================

     

    Android内存泄露 们还可以使用著名的LeakCanary (Square出品,SquareAndroid开源界中的界良心,开源的目包括okhttp, retrofitotto, picasso, Android大神Jake Wharton曾今就是Square)检测

    https://github.com/square/leakcanary

    这个库也有一些bug,但总体来说还是能起到一定的辅助作用。

     

    总结:

    内存泄漏常见原因:

    1.集合类

    集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。

             一开始的微信工程中使用的ButterKnife中的linkeahashmap就存在这个问题。

    2、静态成员

             Static成员作为gc root,如果一个对象被static声明,这个对象会一直存活直到程序进程停止。

    2.单例模式

    不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在 JVM 的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被 JVM 正常回收,导致内存泄露。

            

    这里如果传递Activity作为Context来获得单例对象,那么单例持有Activity的引用,导致Activity不能被释放。

    不要直接对 Activity 进行直接引用作为成员变量,如果允许可以使用Application。如果不得不需要Activity作为Context,可以使用弱引用WeakReference,相同的,对于Service 等其他有自己声明周期的对象来说,直接引用都需要谨慎考虑是否会存在内存泄露的可能。

     

    3.未关闭/释放资源

    BraodcastReceiverContentObserverFileObserverCursorCallback等在 Activity onDestroy 或者某类生命周期结束之后一定要 unregister 或者 close 掉,否则这个 Activity 类会被 system 强引用,不会被内存回收。

             我们经常会写出下面的代码

    当然这样写代码没问题,但是如果我们在close之前还有一些可能抛出异常的代码

    那么现在这段代码存在隐患的。因为如果运行到fos2时候抛出了异常,那么fos也没办法close

    所以正确的方式应该是

    因为如果write发生异常那么这个fos会因为没有close造成内存泄漏。 

    4. Handler

    只要 Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。特别是handler执行延迟任务。所以,Handler 的使用要尤为小心,否则将很容易导致内存泄露的发生。

    这种创建Handler的方式会造成内存泄漏,由于mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏,所以另外一种做法为:

    创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息,

    使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message。

     

    5. Thread 内存泄露

    handler一样,线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。比如线程是 Activity 的内部类,则线程对象中保存了 Activity 的一个引用,当线程的 run 函数耗时较长没有结束时,线程对象是不会被销毁的,因此它所引用的老的 Activity 也不会被销毁,因此就出现了内存泄露的问题。

    ThreadHandler都可以划分到为非静态包括匿名内部类的内存泄漏。

     

    6、系统bug,比如InputMethodManager

     

    展开全文
  • 系统的物理内存是有限的,而对内存的需求是变化的, 程序的动态性越强,内存管理就越重要,选择合适的内存管理算法会带来明显的性能提升。 比如nginx, 它在每个连接accept后会malloc一块内存,作为整个连接生命周期...

    转载于:http://www.cnhalo.net/2016/06/13/memory-optimize/

    概述

    需求

    系统的物理内存是有限的,而对内存的需求是变化的, 程序的动态性越强,内存管理就越重要,选择合适的内存管理算法会带来明显的性能提升。
    比如nginx, 它在每个连接accept后会malloc一块内存,作为整个连接生命周期内的内存池。 当HTTP请求到达的时候,又会malloc一块当前请求阶段的内存池, 因此对malloc的分配速度有一定的依赖关系。(而apache的内存池是有父子关系的,请求阶段的内存池会和连接阶段的使用相同的分配器,如果连接内存池释放则请求阶段的子内存池也会自动释放)。

    目标

    内存管理可以分为三个层次,自底向上分别是:

    • 操作系统内核的内存管理
    • glibc层使用系统调用维护的内存管理算法
    • 应用程序从glibc动态分配内存后,根据应用程序本身的程序特性进行优化, 比如使用引用计数std::shared_ptr,apache的内存池方式等等。
      当然应用程序也可以直接使用系统调用从内核分配内存,自己根据程序特性来维护内存,但是会大大增加开发成本。

    本文主要介绍了glibc malloc的实现,及其替代品

    一个优秀的通用内存分配器应具有以下特性:

    • 额外的空间损耗尽量少
    • 分配速度尽可能快
    • 尽量避免内存碎片
    • 缓存本地化友好
    • 通用性,兼容性,可移植性,易调试

    现状

    目前大部分服务端程序使用glibc提供的malloc/free系列函数,而glibc使用的ptmalloc2在性能上远远弱后于google的tcmalloc和facebook的jemalloc。 而且后两者只需要使用LD_PRELOAD环境变量启动程序即可,甚至并不需要重新编译。

    glibc ptmalloc2

    ptmalloc2即是我们当前使用的glibc malloc版本。

    ptmalloc原理

    系统调用接口

    x86进程地址空间
    上图是 x86_64 下 Linux 进程的默认地址空间, 对 heap 的操作, 操作系统提供了brk()系统调用,设置了Heap的上边界; 对 mmap 映射区域的操作,操作系 统 供了 mmap()和 munmap()函数。
    因为系统调用的代价很高,不可能每次申请内存都从内核分配空间,尤其是对于小内存分配。 而且因为mmap的区域容易被munmap释放,所以一般大内存采用mmap(),小内存使用brk()。

    多线程支持

    • Ptmalloc2有一个主分配区(main arena), 有多个非主分配区。 非主分配区只能使用mmap向操作系统批发申请HEAP_MAX_SIZE(64位系统为64MB)大小的虚拟内存。 当某个线程调用malloc的时候,会先查看线程私有变量中是否已经存在一个分配区,如果存在则尝试加锁,如果加锁失败则遍历arena链表试图获取一个没加锁的arena, 如果依然获取不到则创建一个新的非主分配区。
    • free()的时候也要获取锁。分配小块内存容易产生碎片,ptmalloc在整理合并的时候也要对arena做加锁操作。在线程多的时候,锁的开销就会增大。

    ptmalloc内存管理

    • 用户请求分配的内存在ptmalloc中使用chunk表示, 每个chunk至少需要8个字节额外的开销。 用户free掉的内存不会马上归还操作系统,ptmalloc会统一管理heap和mmap区域的空闲chunk,避免了频繁的系统调用。
    • ptmalloc 将相似大小的 chunk 用双向链表链接起来, 这样的一个链表被称为一个 bin。Ptmalloc 一共 维护了 128 个 bin,并使用一个数组来存储这些 bin(如下图所示)。
      ptmalloc结构图
      数组中的第一个为 unsorted bin, 数组中从 2 开始编号的前 64 个 bin 称为 small bins, 同一个small bin中的chunk具有相同的大小。small bins后面的bin被称作large bins。

    • 当free一个chunk并放入bin的时候, ptmalloc 还会检查它前后的 chunk 是否也是空闲的, 如果是的话,ptmalloc会首先把它们合并为一个大的 chunk, 然后将合并后的 chunk 放到 unstored bin 中。 另外ptmalloc 为了提高分配的速度,会把一些小的(不大于64B) chunk先放到一个叫做 fast bins 的容器内。

    • 在fast bins和bins都不能满足需求后,ptmalloc会设法在一个叫做top chunk的空间分配内存。 对于非主分配区会预先通过mmap分配一大块内存作为top chunk, 当bins和fast bins都不能满足分配需要的时候, ptmalloc会设法在top chunk中分出一块内存给用户, 如果top chunk本身不够大, 分配程序会重新mmap分配一块内存chunk, 并将 top chunk 迁移到新的chunk上,并用单链表链接起来。如果free()的chunk恰好 与 top chunk 相邻,那么这两个 chunk 就会合并成新的 top chunk,如果top chunk大小大于某个阈值才还给操作系统。主分配区类似,不过通过sbrk()分配和调整top chunk的大小,只有heap顶部连续内存空闲超过阈值的时候才能回收内存。
    • 需要分配的 chunk 足够大,而且 fast bins 和 bins 都不能满足要求,甚至 top chunk 本身也不能满足分配需求时,ptmalloc 会使用 mmap 来直接使用内存映射来将页映射到进程空间。

    ptmalloc分配流程

    ptmalloc分配流程

    ptmalloc的缺陷

    • 后分配的内存先释放,因为 ptmalloc 收缩内存是从 top chunk 开始,如果与 top chunk 相邻的 chunk 不能释放, top chunk 以下的 chunk 都无法释放。
    • 多线程锁开销大, 需要避免多线程频繁分配释放。
    • 内存从thread的areana中分配, 内存不能从一个arena移动到另一个arena, 就是说如果多线程使用内存不均衡,容易导致内存的浪费。 比如说线程1使用了300M内存,完成任务后glibc没有释放给操作系统,线程2开始创建了一个新的arena, 但是线程1的300M却不能用了。
    • 每个chunk至少8字节的开销很大
    • 不定期分配长生命周期的内存容易造成内存碎片,不利于回收。 64位系统最好分配32M以上内存,这是使用mmap的阈值。

    tcmalloc

    tcmalloc是Google开源的一个内存管理库, 作为glibc malloc的替代品。目前已经在chrome、safari等知名软件中运用。
    根据官方测试报告,ptmalloc在一台2.8GHz的P4机器上(对于小对象)执行一次malloc及free大约需要300纳秒。而TCMalloc的版本同样的操作大约只需要50纳秒。

    小对象分配

    • tcmalloc为每个线程分配了一个线程本地ThreadCache,小内存从ThreadCache分配,此外还有个中央堆(CentralCache),ThreadCache不够用的时候,会从CentralCache中获取空间放到ThreadCache中。
    • 小对象(<=32K)从ThreadCache分配,大对象从CentralCache分配。大对象分配的空间都是4k页面对齐的,多个pages也能切割成多个小对象划分到ThreadCache中。
      tcmalloc小对象类型链表
      小对象有将近170个不同的大小分类(class),每个class有个该大小内存块的FreeList单链表,分配的时候先找到best fit的class,然后无锁的获取该链表首元素返回。如果链表中无空间了,则到CentralCache中划分几个页面并切割成该class的大小,放入链表中。

    CentralCache分配管理

    • 大对象(>32K)先4k对齐后,从CentralCache中分配。 CentralCache维护的PageHeap如下图所示, 数组中第256个元素是所有大于255个页面都挂到该链表中。
      tcmalloc-pageheap
    • 当best fit的页面链表中没有空闲空间时,则一直往更大的页面空间则,如果所有256个链表遍历后依然没有成功分配。 则使用sbrk, mmap, /dev/mem从系统中分配。
    • tcmalloc PageHeap管理的连续的页面被称为span.
      如果span未分配, 则span是PageHeap中的一个链表元素
      如果span已经分配,它可能是返回给应用程序的大对象, 或者已经被切割成多小对象,该小对象的size-class会被记录在span中
    • 在32位系统中,使用一个中央数组(central array)映射了页面和span对应关系, 数组索引号是页面号,数组元素是页面所在的span。 在64位系统中,使用一个3-level radix tree记录了该映射关系。

    回收

    • 当一个object free的时候,会根据地址对齐计算所在的页面号,然后通过central array找到对应的span。
    • 如果是小对象,span会告诉我们他的size class,然后把该对象插入当前线程的ThreadCache中。如果此时ThreadCache超过一个预算的值(默认2MB),则会使用垃圾回收机制把未使用的object从ThreadCache移动到CentralCache的central free lists中。
    • 如果是大对象,span会告诉我们对象锁在的页面号范围。 假设这个范围是[p,q], 先查找页面p-1和q+1所在的span,如果这些临近的span也是free的,则合并到[p,q]所在的span, 然后把这个span回收到PageHeap中。
    • CentralCache的central free lists类似ThreadCache的FreeList,不过它增加了一级结构,先根据size-class关联到spans的集合, 然后是对应span的object链表。如果span的链表中所有object已经free, 则span回收到PageHeap中。

    tcmalloc的改进

    • ThreadCache会阶段性的回收内存到CentralCache里。 解决了ptmalloc2中arena之间不能迁移的问题。
    • Tcmalloc占用更少的额外空间。例如,分配N个8字节对象可能要使用大约8N * 1.01字节的空间。即,多用百分之一的空间。Ptmalloc2使用最少8字节描述一个chunk。
    • 更快。小对象几乎无锁, >32KB的对象从CentralCache中分配使用自旋锁。 并且>32KB对象都是页面对齐分配,多线程的时候应尽量避免频繁分配,否则也会造成自旋锁的竞争和页面对齐造成的浪费。

    性能对比

    官方测试

    测试环境是2.4GHz dual Xeon,开启超线程,redhat9,glibc-2.3.2, 每个线程测试100万个操作。
    tcmalloc测试1
    上图中可以看到尤其是对于小内存的分配, tcmalloc有非常明显性能优势。
    tcmalloc测试2
    上图可以看到随着线程数的增加,tcmalloc性能上也有明显的优势,并且相对平稳。

    github mysql优化

    github使用tcmalloc后,mysql性能提升30%

    Jemalloc

    jemalloc是facebook推出的, 最早的时候是freebsd的libc malloc实现。 目前在firefox、facebook服务器各种组件中大量使用。

    jemalloc原理

    • 与tcmalloc类似,每个线程同样在<32KB的时候无锁使用线程本地cache。
    • Jemalloc在64bits系统上使用下面的size-class分类:
      Small: [8], [16, 32, 48, …, 128], [192, 256, 320, …, 512], [768, 1024, 1280, …, 3840]
      Large: [4 KiB, 8 KiB, 12 KiB, …, 4072 KiB]
      Huge: [4 MiB, 8 MiB, 12 MiB, …]
    • small/large对象查找metadata需要常量时间, huge对象通过全局红黑树在对数时间内查找。
    • 虚拟内存被逻辑上分割成chunks(默认是4MB,1024个4k页),应用线程通过round-robin算法在第一次malloc的时候分配arena, 每个arena都是相互独立的,维护自己的chunks, chunk切割pages到small/large对象。free()的内存总是返回到所属的arena中,而不管是哪个线程调用free()。

    jemalloc Arena Chunk layout
    上图可以看到每个arena管理的arena chunk结构, 开始的header主要是维护了一个page map(1024个页面关联的对象状态), header下方就是它的页面空间。 Small对象被分到一起, metadata信息存放在起始位置。 large chunk相互独立,它的metadata信息存放在chunk header map中。

    • 通过arena分配的时候需要对arena bin(每个small size-class一个,细粒度)加锁,或arena本身加锁。
      并且线程cache对象也会通过垃圾回收指数退让算法返回到arena中。
      jemalloc Arena and thread cache layout

    jemalloc的优化

    • Jmalloc小对象也根据size-class,但是它使用了低地址优先的策略,来降低内存碎片化。
    • Jemalloc大概需要2%的额外开销。(tcmalloc 1%, ptmalloc最少8B)
    • Jemalloc和tcmalloc类似的线程本地缓存,避免锁的竞争
    • 相对未使用的页面,优先使用dirty page,提升缓存命中。

    性能对比

    官方测试

    jemalloc官方测试报告
    上图是服务器吞吐量分别用6个malloc实现的对比数据,可以看到tcmalloc和jemalloc最好(facebook在2011年的测试结果,tcmalloc这里版本较旧)。

    4.3.2 mysql优化
    测试环境:2x Intel E5/2.2Ghz with 8 real cores per socket,16 real cores, 开启hyper-threading, 总共32个vcpu。 16个table,每个5M row。
    OLTP_RO测试包含5个select查询:select_ranges, select_order_ranges, select_distinct_ranges, select_sum_ranges,
    jemalloc mysql性能测试

    可以看到在多核心或者多线程的场景下, jemalloc和tcmalloc带来的tps增加非常明显。

    参考资料

    glibc内存管理ptmalloc源代码分析
    Inside jemalloc
    tcmalloc浅析
    tcmalloc官方文档
    Scalable memory allocation using jemalloc
    mysql-performance-impact-of-memory-allocators-part-2
    ptmalloc,tcmalloc和jemalloc内存分配策略研究
    Tick Tock, malloc Needs a Clock

    总结

    在多线程环境使用tcmalloc和jemalloc效果非常明显。
    当线程数量固定,不会频繁创建退出的时候, 可以使用jemalloc;反之使用tcmalloc可能是更好的选择。


    展开全文
  • 本系列文章,主要是总结我对Android开发过程中内存优化的理解,很多东西都是平常的习惯和一些细节问题,重在剖析优化的原理,养成一种良好的代码习惯。 概述 既然谈优化,就绕不开Android三个内存相关的经典问题...
  • 内存优化思路

    2019-06-16 00:27:49
    性能定位套路虽然内存的性能指标很多,但都是为了描述内存的原理,指标间自然不会完全孤立,一般都会有关联, 明白了原理, 在定位问题的时候就能更快更准举个最简单的例子,当你看到系统的剩余内存很低时,是不是就...
  • Android内存优化

    2018-06-08 17:24:12
    参考Android性能优化(四)之内存优化实战 内存优化的套路: 解决所有的内存泄漏 集成LeakCanary,可以方便的定位出90%的内存泄漏问题; 通过反复进出可疑界面,观察内存增减的情况,Dump Java Heap获取当前堆栈...
  • 前言成为一名优秀的Android开发,需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样~。内存优化可以说是性能优化中最重要的优化点之一,可以说,如果你没有掌...
  • Android内存优化大总结

    2018-11-14 16:43:28
    内存简介: RAM(random access memory)随机存取存储器。说白了就是内存。 一般Java在内存分配时会涉及到以下区域: 寄存器(Registers):速度最快的存储场所,因为寄存器位于处理器内部,我们在程序...
  • GC)完成的,程序员不需要通过调用函数来释放内存,但也随之带来了内存泄漏的可能,上篇博客,我介绍了 Android性能优化系列之布局优化,本篇博客,我将介绍内存优化的相关知识。内存的分配策略概述程序运行时的内存...
  • 今天来聊一聊Android中内存优化的一些手段。 首先问问自己为什么要内存优化呢? (1):App消耗内存过大,导致手机内存低于内存警戒线的时候,Low Memory Killer机制就会触发,App占用内存越多,被处理掉的机会就越...
  • 本文主要讲解性能优化中的内存优化,希望你们会喜欢 目录 1. 定义 优化处理 应用程序的内存使用、空间占用 2. 作用 避免因不正确使用内存 &amp; 缺乏管理,从而出现 内存泄露(ML)、内存...
  • c++的内存优化

    2019-07-11 19:29:57
    2.内存优化有哪些方式 3.怎样做内存优化 概述: 我们常常在开发场景下提及的内存是指程序内存. 程序内存可以分为以下五种: 1、 栈区(stack):栈的空间是连续的, 先进后出能保证不会产生内存碎片, 由高地址向低...
  • ANDROID内存优化大汇总完全版,涵盖内存介绍,内存计算,减少内存,重用内存,回收内存等内存优化总结。
  • 前言在前两篇对Android如何管理内存有一定的了解下,所以本篇文章就是如何进行内存优化。因为Android给每个应用分配的内存是有限的,所以要保证应用占用的内存最小,这样提高让进程长时间存活的概率,并且不易发生...
  • mysql内存优化总结

    2018-05-09 20:36:21
    1.mysql内存优化原则 a. 将尽量多的内存分配给mysql做缓存,但是也要给操作系统和其他程序预留足够内存。 b. MyISAM的数据文件读取依赖于操作系统的io,因此如果有MyISAM表,就要预留更多的内粗给操作系统做io缓存...
  • 很多Android的小伙伴们在面试中都会被面试官问到如何处理内存优化,那么应该如何答复面试官呢,本文就来讲讲如何来介绍Android中遇到的内存优化,让你轻松笑对面试。 大家先看下面的我总结的思维导图: 从上面的...
  • 前言在上篇 Android 性能优化 内存优化 基本概念对Android整个系统有了初步认识,即Android在系统上做了哪些操作来节约内存,这篇文章就主要介绍Android是如何进行内存管理的。在 Android应用开发性能优化完全分析这...
  • 本文让我们深入到Redis细节中,学习内存优化的技巧。分为如下几个部分: 一. redisObject对象 二. 缩减键值对象 三. 共享对象池 四. 字符串优化 五. 编码优化 六. 控制key的数量 一. redisObj...
1 2 3 4 5 ... 20
收藏数 783,279
精华内容 313,311
关键字:

内存优化