将长生命周期对象和大对象池化

请记住最开始说的原则:对象要么立即回收要么一直存在。它们要么在0代被回收,要么在2代里一直存在。有些对象本质是静态的,生命周期从它们被创建开始,到程序停止才会结束。其它对象显然不需要永远存在下去,但他们的生命周期会存在程序的某些上下文里。它们的存活时间会超过0代(1代)回收。这些类型的对象可以作为池化对象的备选。这虽然需要你手动管理内存,但实际情况下这是一个很好的选择。另外一个重要的需要池化的对象是分配在LOH里的大对象。

没有一个单一的标准方案或者API来实现对象的池化。 这需要你根据你的程序和对象的类型来设计对应方案。

对于如何管理池化对象,你可以将其当做非托管资源(内存)来进行管理。.NET对于这类资源有一个种管理模式:IDisposable。在本章前面我们介绍了如何实现这种模式。一个比较合理的方式是实现IDisposable接口,在Dispose方法里将对象丢回对象池。

实现一个好的对象池策略并不简单,他取决于你程序要如何使用,以及那种类型的对象需要进行池化。

下面的栗子,实现了一个简单的对象池,你可以从里面知道对象池会涉及那些内容。这个代码可以从 PooledObjects 的栗子工程里看到。


interface IPoolableObject : IDisposable
{
int Size { get; }
void Reset();
void SetPoolManager(PoolManager poolManager);
} internal class PoolManager
{
private class Pool
{
public int PooledSize { get; set; } public int Count
{
get { return this.Stack.Count; }
} public Stack<IPoolableObject> Stack { get; private set; } public Pool()
{
this.Stack = new Stack<IPoolableObject>();
}
} private const int MaxSizePerType = 10*(1 << 10); // 10 MB private Dictionary<Type, Pool> pools = new Dictionary<Type, Pool>(); public int TotalCount
{
get
{
int sum = 0;
foreach (var pool in this.pools.Values)
{
sum += pool.Count;
}
return sum;
}
} public T GetObject<T>() where T : class, IPoolableObject, new()
{
Pool pool;
T valueToReturn = null;
if (pools.TryGetValue(typeof (T), out pool))
{
if (pool.Stack.Count > 0)
{
valueToReturn = pool.Stack.Pop() as T;
}
}
if (valueToReturn == null)
{
valueToReturn = new T();
}
valueToReturn.SetPoolManager(this);
return valueToReturn;
} public void ReturnObject<T>(T value) where T : class, IPoolableObject, new()
{
Pool pool;
if (!pools.TryGetValue(typeof (T), out pool))
{
pool = new Pool();
pools[typeof (T)] = pool;
}
if (value.Size + pool.PooledSize < MaxSizePerType)
{
pool.PooledSize += value.Size;
value.Reset();
pool.Stack.Push(value);
}
}
} internal class MyObject : IPoolableObject
{
private PoolManager poolManager;
public byte[] Data { get; set; }
public int UsableLength { get; set; } public int Size
{
get { return Data != null ? Data.Length : 0; }
} void IPoolableObject.Reset()
{
UsableLength = 0;
} void IPoolableObject.SetPoolManager(PoolManager poolManager)
{
this.poolManager = poolManager;
} public void Dispose()
{
this.poolManager.ReturnObject(this);
}
}

强制让每个对象都实现接口会麻烦一些,但它除了方便外,还有一个重要的事实:为了使对象池重用对象,你必须能完全理解并控制它们。每次对象回到对象池前,你的代码需要将对象重新设置到一个移植的,安全的状态。这意味着你不应该天真的直接用第三方的对象池组件。你需要设计接口,并让对象实现该接口,用来处理每个对象获取时的初始化过程。你还需要特别小心对.NET框架对象做池化。

特别需要注意的是用来做对象池的集合,因为它们的性质决定--你并不希望它们销毁所存储的数据(毕竟这是池的重点),但你需要一个可以表示可以为空和可用空间的集合。幸运的是,大多数集合类型都实现了长度和容量的参数。考虑到使用现有的.NET集合类型会存在风险,建议最好自己实现集合类型,并实现一些标准的集合接口(如:IList,ICollection等)。相关创建自己定义集合的内容,可参考本书第六章。另外一个策略就是让你设计的可回收对象实现一个终结器(析构函数)。如果终结器运行,则意味着Dispose方法没有执行,这将会是一个小小的bug。你也可以在你的程序里一些地方记录日志,崩溃信息或者一些信号信息。

请牢记,如果不清理对象池里的数据,这等同于内存泄漏。你的对象池应该有一个边界大小(无论是字节数量或者对象的数量),一旦超过,它应该通知GC清理多余的对象。理想情况下,你的对象池足够大,可以正常操作而不回收对象,但也会造成GC在执行回收时暂停时间变长,对象池里对象越多回收算法耗时也越多。当然最重要的还是对象池能满足你的需要。

我通常不会将对象池作为默认的解决方案。它作为一种通用机制,显得很笨重以及容易出错。但你可能会发现你的程序在某些类型上很适用对象池。在一个应用里分配了大量的LOH对象,我们调查后发现,可以将一个单一的对象池化就能解决99%的问题。这个就是MemoryStream,我们使用它来序列化网络传输数据。实际的实现不仅仅是将构建了一个MemoryStream的队列,因为要避免内存碎片,有一些更复杂的设计,但从本质上来说还是将它池化。每次使用完MemoryStream对象,它都会被放入对象池里。

下一篇:第二章 GC -- 减少大对象堆的碎片,在某些情况下强制执行完整GC,按需压缩大对象堆,在GC前收到消息通知,使用弱引用缓存对象

[翻译] 编写高性能 .NET 代码--第二章 GC -- 将长生命周期对象和大对象池化的更多相关文章

  1. [翻译] 编写高性能 .NET 代码--第二章 GC -- 避免使用终结器,避免大对象,避免复制缓冲区

    避免使用终结器 如果没有必要,是不需要实现一个终结器(Finalizer).终结器的代码主要是让GC回收非托管资源用.它会在GC完成标记对象为可回收后,放入一个终结器队列里,在由另外一个线程执行队列里 ...

  2. [翻译] 编写高性能 .NET 代码--第二章 GC -- 减少分配率, 最重要的规则,缩短对象的生命周期,减少对象层次的深度,减少对象之间的引用,避免钉住对象(Pinning)

    减少分配率 这个几乎不用解释,减少了内存的使用量,自然就减少GC回收时的压力,同时降低了内存碎片与CPU的使用量.你可以用一些方法来达到这一目的,但它可能会与其它设计相冲突. 你需要在设计对象时仔细检 ...

  3. [翻译] 编写高性能 .NET 代码--第二章 GC -- 减少大对象堆的碎片,在某些情况下强制执行完整GC,按需压缩大对象堆,在GC前收到消息通知,使用弱引用缓存对象

    减少大对象堆的碎片 如果不能完全避免大对象堆的分配,则要尽量避免碎片化. 对于LOH不小心就会有无限增长,但LOH使用的空闲列表机制可以减轻增长的影响.利用这个空闲列表,我们可以在两块分配区域中间找到 ...

  4. [翻译] 编写高性能 .NET 代码--第二章 GC -- 配置选项

    配置选项 在基于"less rope to hang yourself with"思想下,.NET 框架没有给开发提供很多太多的配置选项.但在大多数情况下,GC会跟你的硬件配置,及 ...

  5. [翻译]编写高性能 .NET 代码 第二章:垃圾回收

    返回目录 第二章:垃圾回收 垃圾回收是你开发工作中要了解的最重要的事情.它是造成性能问题里最显著的原因,但只要你保持持续的关注(代码审查,监控数据)就可以很快修复这些问题.我这里说的"显著的 ...

  6. [翻译]编写高性能 .NET 代码 第二章:垃圾回收 基本操作

    返回目录 基本操作 垃圾回收的算法细节还在不断完善中,性能还会有进一步的提升.下文介绍的内容在不同的.NET版本里会略有不同,但大方向是不会有变动的. 在.net进程里会管理2个类型的内存堆:托管和非 ...

  7. [翻译]编写高性能 .NET 代码 第一章:性能测试与工具 -- 平均值 vs 百分比

    <<返回目录 平均值 vs 百分比 在考虑要性能测试的目标值时,我们需要考虑用什么统计口径.大多数人都会首选平均值,但在大多数情况下,这个正确的,但你也应该适当的考虑百分数.但你有可用性的 ...

  8. [翻译]编写高性能 .NET 代码 第一章:工具介绍 -- Visual Studio

    <<返回目录 Visual Studio vs虽然不是全宇宙唯一的IDE,但它是.net开发人员最常用的开发工具.它自带一个性能分析工具,你可以使用它来做开发,不同的vs版本在工具上会略有 ...

  9. [翻译]编写高性能 .NET 代码 第一章:工具介绍 -- Performance Counters(性能计数器)

    <<返回目录 Performance Counters(性能计数器) 性能计数器是监视应用程序和系统性能的最简单的方法之一.它有几十个类别数百个计数器在,包括一些.net特有的计数器.要访 ...

随机推荐

  1. 2017-06-26(groupadd groupmod groupdel)

    groupadd groupadd  组名  (创建用户组) groupadd -g  组编号   组名 (创建组名 并且指定编号) groupmod groupmod  -n   新组名  旧组名 ...

  2. AI_深度学习为何兴起?

    深度学习和神经网络,在此技术背后的理念,已经发展了好几十年了,为何现在流行起来了? 最直接因素: 将帮助你在自己的组织中,发现好机会,来应用这些东西 为什么深度学习这么厉害? x轴表示完成任务的数据数 ...

  3. 爬取知名社区技术文章_items_2

    item中定义获取的字段和原始数据进行处理并合法化数据 #!/usr/bin/python3 # -*- coding: utf-8 -*- import scrapy import hashlib ...

  4. 学Java分布式和高架构,必懂的两大知识点!

    今天小编为你们分享阿里巴巴2018年招聘应届毕业生,Java工程师的面试考题,主要分为三种 Java中获取 mysql连接的方式: 第一部分:分布式   三步变成:分布式 1.将你的整个软件视为一个系 ...

  5. asp.net core 部署到服务器之后外网访问不了

    部署发现问题 今天在部署.net core的时候,发现访问http://localhost:xxxx可以,但是用外网访问并不行! 开始尝试解决问题 一开始以为是nginx的问题.各种折腾,各种改配置文 ...

  6. java枚举 用于声明持久化常量 和volley 请求头

    在JDK1.5 之前,我们定义常量都是: public static fianl.... .现在好了,有了枚举,可以把相关的常量分组到一个枚举类型里,而且枚举提供了比常量更多的方法. public e ...

  7. 权限问题导致zabbix无法监控mysql

    说说一个困扰自已两天的问题. 首先是用常规的方法安装上了mysql数据库.做了主从. 在监控从库的时候,发现所有的监控数据库的监控项都获取不到key值 . zabbix server端也不报错.获取到 ...

  8. web移动端常见问题解决方案 (转)

    总结:本文总结了web移动端的常见问题并附上解决方案,包括:Meta标签.获取滚动条的值.禁止选择文本.屏蔽阴影.css之border-box.css3多文本换行.Retina屏幕高清图片.html5 ...

  9. Jenkins初识

    Jenkins Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能. 功能 Jenkins功能包括 ...

  10. C#:导入Excel通用类(CSV格式)

    一.引用插件LumenWorks.Framework.IO.dll(CsvReader) 插件下载地址:https://pan.baidu.com/s/1c3kTKli  提取密码 dz7j 二.定义 ...