自动内存分配及性能优化 | 青训营笔记

121 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天

软件工程上的性能优化知识

性能优化的层面:业务代码 -> SDK -> 基础库 -> 语言运行时(runtime) -> OS

  • 业务层优化:针对特定场景,具体问题具体分析,容易获得比较大的性能收益;

  • 语言运行时的优化:考虑更多的场景,解决更通用的性能问题(比如内存分配问题)。

数据来驱动优化,采用pprof等自动化性能分析工具来进行优化。

软件质量至关重要,在保证接口稳定的情况下如何实现性能优化:

image.png

  • 测试用例:覆盖尽可能多的场景,用测试驱动开发;
  • 文档:做了什么,没做什么,能达到怎样的效果;
  • 隔离:保持行为的一致性,通过选项控制是否开启优化;
  • 日志:必要的日志输出达到功能的可观测性;

自动内存管理

自动内存管理都是针对动态内存,即程序在运行时根据需求动态分配的内存。

自动内存管理(垃圾回收),由程序语言的运行时系统管理动态内存,避免程序员的手动内存管理,保证内存使用的正确性和安全性。

自动内存管理的三个任务:为新对象分配空间;找到存活对象;回收死亡对象的内存空间。

1 Garbage collector (GC)相关概念

  • Mutator:业务线程,分配新对象,修改对象指向关系
  • Collector:GC线程,找到存活对象,回收死亡对象的内存空间

Garbage collector (GC)算法

  • Serial GC:只有一个collector回收的GC算法;
  • Parallel GC:支持多个collectors同时回收的GC算法,性能比Serial GC更高;
  • Concurrent GC:mutator(s) 和 collector(s) 可以同时执行的GC算法,Collectors必须感知对象指向关系的改变;

评价GC算法的标准

  • 安全性:不能回收存活的对象
  • 吞吐率:花在GC上的时间(越短越好)
  • 暂停时间:业务是否感知(越短越好)
  • 内存开销:GC元数据开销(越小越好)

2 追踪垃圾回收

当一个指针指向的对象关系不可达时,该对象就可以被回收;

追踪垃圾回收一共三个步骤:标记根对象、找到可达对象(标记)、清理不可达对象

  • 标记根对象:如静态变量、全局变量、常量、线程栈等 image.png

  • 找到可达对象:求指针指向关系的闭包

    image.png

  • 清理不可达对象:将存活对象复制到另外的内存空间,然后将死亡对象的内存标记为可分配,最后移动并且整理存活对象。

    image.png

3 分代GC (Generational GC)

分代假说:很多对象在分配出来后就会很快回收,每个对象根据GC次数设置一个“年龄”(例如一个对象经历两次GC后依然存活,则该对象“年龄”为2)

分代GC目的:对年轻的对象和老年的对象制定不同的GC策略,从而降低内存管理的开销,不同的年龄对象处于heap的不同区域。

  • 年轻代(Young Generation):常规的对象分配,GC吞吐率高,可以采用Copying collection,如下图所示

    image.png

  • 老年代(Old Generation):对象趋于一直存活,反复复制的话开销较大,可以采用make-sweep collection,如下图所示

    image.png

4 引用计数

每个对象都有一个与之相关联的引用数目,当且仅当一个对象的引用数大于0时,该对象存活。

举个例子:当第一个指针p指向某个内存空间(对象)O时,该对象计数为1,当第二个指针q也指向同一对象O时,该对象计数变成2,最后指针p和指针q都指向null时,该对象O计数变成0,这时对象O就可以被回收。

引用计数的优点:

  • 内存管理操作被平摊到了程序执行过程中;
  • 内存管理不需要了解runtime的实现细节,例如C++的只能指针。

引用计数的缺点:

  • 维护引用计数的开销大,通过原子操作保证引用计数操作的原子性和可见性;
  • 无法回收环形数据结构(可以参考 weak reference);
  • 每个对象都会引入额外的内存空间存储引用数目;
  • 回收大数据结构的内存时依然可能引发暂停。