Evictor模式描述了何时以及如何释放资源以优化资源管理。这个模式让我们可以配置不同的策略来自动决定哪些资源应该释放,以及应该在什么时候释放。

实例

考虑一个网络管理系统——管理多个网络元素。这些网络元素通常采用拓扑树表示。拓扑树将网络基础设施中的关键元素进行虚拟化层级表现。网络管理系统允许用户查看这样的树,并且可以获得关于一个或者多个网络元素的细节。这个细节可能对应大量的数据(数据量的多少取决于网络元素的类型。),例如,复杂网络元素的细节可能包含了它的状态以及它的组件的状态的信息。

拓扑树的创建时间

1、应用程序启动时;

2、用户要求查看网络元素树时(用户请求);

3、1与2之间的某个时刻。

因此获取网络元素的细节可以是树创建时;用户请求时。但是不管何时保存这些元素细节到内存中,在内存中过长的保存这些细节可能都会代价高昂。因此就会产生两个缺陷场景:

1、某个网络元素的细节再也不会被用户访问,那么它们对于内存资源的消耗将是毫无意义的;

2、某一网络元素的细节会被用户频繁访问,而它有没有被缓存,那就会导致频繁的用于获取该元素的调用,从而导致系统性能的降低;该类细节如果能够保存在内存中,就会增进性能。

所以就延伸出了一个问题:

何时释放资源?释放哪些资源?

Evictor的提出背景Context

需要控制对于资源的释放以确保对资源的高效管理的系统

问题Problem

1、应用程序保存一些只用过几次就不再用的资源而不释放——内存占用、性能下降、系统不稳定;

2、为解决问题1,系统在使用资源后立刻释放资源,这会导致某些高频使用的资源被频繁请求获取,从而引发高昂的请求代价。这些频繁使用的资源应当保存在内存中。

为解决这两个问题,需要考虑几个性质:

①最优性(Optimality):资源的生命周期 受 资源使用的频率 影响;

②可配置性(Configurability):综合资源类型、可用内存、CPU负载 之类的参数来决定资源的释放;

③透明性(Transparency):解决方案对用户透明

解决方案Solution

监控资源的使用,并使用某种策略,如LRU(Least Recently Used,最近最久未使用)、LFU(Least Frequently Used,最近最少使用)等缓存算法来控制其生命周期。每当资源被使用时,就会为应用程序所标记。当资源最近没有被使用,或者不是频繁被使用时,就不会被标记。应用程序会周期性的或者根据实际需要选择那些没有被标记的资源并且释放或者清除它们。而被标记的资源则会被继续留在内存内,因为它们会被频繁使用。

另外,也可以采用其它策略来判断应该清除哪些资源。例如,对于内存受限的应用程序,可以从资源的尺寸来决定应该清除哪些资源。这种情境下,消费大量内存的资源可能会被清除,即使它曾被频繁使用。

Evictor模式的结构Structure

资源使用者:使用资源的实体,可以是应用程序或者操作系统;

资源:可用实体,比如内存、CPU等;

清除者(Evictor):基于一个或多个清除策略来清除资源

清除策略(Eviction Strategy):描述用于判断是否应当清除资源的标准。

动态Dynamics

资源使用者首先获取资源,之后将该资源注册到Evictor,从此时起,Evictor监视资源。当Evictor检测到资源根据清除策略应该被清除时,它就询问资源,判断是否应该清除。如果资源可以被清除,那么就会获得清除资源的机会,之后就会将该资源清除。

大多数资源的清除步骤是一样的,但也存在特殊情况,如资源可能无法被标记,结果就是Evictor会截获资源的请求以获得其清除策略所依赖的统计信息。

实现Implementation

实现Evictor模式涉及4步:

1)定义清除接口。应该定义一个清除接口,所有能够被清除的资源都要实现这个接口。

例如,在Java中的清除接口实现起来可能像这样:

pucblic interface EvictionInterface {
public boolean isEvictable(); //是否可以被清除
public Object info(); //获取同策略相关的信息,以便确定是否清除
public void beforeEviction();//清除前调用。给对象清除它可能获取到的资源的机会
}

例如,EJB Session Bean [Sun04b] 和Entity Bean的接口包含了一个叫做ejbPassivate()的方法,会在Entity Bean或者Session Bean清除之前调用。这就给了Bean释放所有已经获得的资源的机会。

2)决定可清除的资源。开发者必须决定哪些资源可以并且应该被清除。例如,应用程序频繁使用的资源或者无法重新获取的资源就不应该被清除。任何可以清除的资源必须实现清除接口。在清除资源之前,应用程序应该调用这个接口,给资源一个机会来做“善后”工作,这包括把任何必要的状态持久化。

在1)中描述的Java接口中,应用程序可以采用isEvictable()方法来表明资源是否可以被清除。如果返回true,那么Evictor会认为可以考虑把资源清除;如果方法返回false,那么Evictor会忽略此资源

3)决定清除策略。基于应用程序的需求,可以用不同的清除策略来判断是否清除资源,以及清除哪些可以清除的资源。一些用来判断清除那些资源的策略包括LRU、LFU。

此外,可以使用能够接受不同参数的用户定义的策略。例如,清除策略可以考虑重新获取被清除的资源的代价有多昂贵。利用这种策略,重新获取的代价比较低的资源可能会被清除,哪怕它们会比获取起来更昂贵的资源使用的更加频繁。

4)定义系统中对于清除的使用。需要在Evictor中增加清除资源的业务逻辑。这包含判断应该何时以及如何清除资源,以及实际标记要被清除的资源。通常情况下,Evictor在应用程序中作为一个单独对象或者组件而存在,并且会被应用程序以必要的清除策略来配置。例如,应用程序可能会选择在内存低于某个阈值时清除资源。另一个不同的应用程序可能会实现更主动的策略并会周期性地清除资源,即使内存没有低于某个阈值。

Evictor可以使用Interceptor模式[POSA2]来截获用户对对象的访问。Interceptor可以以对资源使用者完全透明的方式来把对象标记为最近正在使用。Evictor会周期性或者根据需要查询所有可清除的对象,以便决定如果需要清除对象的话应该清除哪一些。在前面描述的Java接口中,Evictor会对每个对象调用info()方法并使用获得的信息在清除策略的背景下判断是否应该清除这个对象。对于实际的清除,可以使用Disposal Mehtod模式[Henn03],有时它也叫做Explicit Termination Method模式[Bloc01]。Disposal Method模式描述了类应该如何提供显式的方法来允许它们在被清除之前执行“善后”工作。

实例解析(Example Resolved)

考虑网络管理系统的例子,这个系统负责管理多个网络元素组成的网络。网络元素的细节可以在系统启动时获取,也可以在资源使用者请求时再获取。事先不知道用户意图,所以需要优化资源使用,以便只让那些被资源使用者频繁访问的网络元素保留在内存中。所以这个例子中使用的清除策略是,清除过了某段设定的时间依然没有被资源使用者访问的网络元素。

每个网络元素都需要实现Eviction接口:

public call NetworkElement implements EvictionInterface {

        private NetworkElementComponent [] components ;
private Date lastAccess; public boolean isEvictable() {
return true;
} public Object info() {
return lastAccess;
} public boid beforEviction() {
for ( int i =0; i < components.length; i++) {
components[i].beforeEviction();
}
}
//其他网络元素操作
}

类似的,所有网络元素组件和子组件都需要实现Eviction接口,这样它们被清除时就可以递归地释放所有资源。

Evictor可以实现为一个在自己的线程控制中运行的对象,这样它就能周期性的检查是否有网络元素过了一段特定的时间依旧没被访问。

public class Evictor implements Runnable {
private NetworkElement [] nes;
public Evictor() {
new Thread(this).start();
} public void run() {
while(true) {
try {
Thread.sleep(pollTime);
}
catch(InterruptedException e){
break;
}
for (int i = 0; i < nes.length; i++){
NetworkElement ne = (NetworkElement) nes[i];
if (ne.isEviciable()) {
Date d=(Date) ne.info();
if (d.befor(threshold)) {
ne.beforeEviction();
}
}
}
}
}
}

info()方法返回的信息以及Evictor解释信息的方法是同应用程序相关的,可以按照需要部署的清除策略来裁剪。

变体Variants

Deferred Eviction模式。清除一个或者多个对象的过程可以细化为两步的过程。不是立刻删除对象,而是可以把它们放进一个FIFO队列。当队列满了的时候,队列前边的对象就被清除了。因此,队列又扮演了待清除对象的中间持有者的角色。这和Caching模式有点类似。如果这些对象中的任一个在从队列删除前又被访问了,那么它们就不需要有创建和初始化的开销。当然,这一变体会带来维护队列的开销,而且把清除对象保持在队列中也会占用资源。

Evictor with Object Pool模式。可以用一个对象池来保存被清除的对象。在这个变体中,当对象被清除,它不是从内存中被完整地删除,而只是失去了标识,成为了匿名对象。之后,这个匿名对象会被添加到对象池中,如果对象池满了,那么对象就会被从内存中删除。当需要创建新对象时,队列中的匿名对象就可以出队并且获得新的标识,这就降低了对象创建的开销。对象池的尺寸可以根据可用内存来设置。

Eviction Wrapper模式。可以清除的对象不需要直接实现Eviction接口。可以用实现了Eviction接口的Wrapper Facade[POSA2]来包含对象。Evictor会调用包装对象的beforeEviction()方法,这个方法负责清除实际的对象。这一变体使得整合遗留代码变得简单,不必要求现有的类实现Eviction接口。这个变体的一个例子是在Java中使用对象引用作为包装对象。更多细节可以见“已知应用”一节。

结果Consequences

Evictor模式的优点:

①可伸缩性(Scalability):Evictor模式允许应用程序对使用的资源数目保留一个上限,从而在任何给定的时间内都在内存中。这使得应用程序可以伸缩而不影响总体内存消耗。

②低内存占用(Low memory footprint):Evictor模式允许应用程序通过可配置策略来控制哪些资源应该保留在内存中,哪些资源应该释放。通过只在内存中保留最关键的资源,应用程序可以保持低内存占用、高效运行。

③透明性(Transparency):使用Evictor模式使得资源释放对于资源使用者完全透明。

④稳定性(Stability):Evictor降低了资源枯竭的可能性,从而增加了应用程序的稳定性。

缺点:

①额外开销(Overhead):Evictor模式需要额外的业务逻辑来判断要清除哪些资源,以及实现清除策略。此外,资源的实际清除也可能会带来明显的执行开销。

②重新获取的损失(Re-acquisition penalty):如果再次需要用到被清除的资源,那么就需要重新获取该资源。这可能代价高昂,会影响应用程序的性能。可以通过调整Evictor用来判断清除哪些资源的策略来避免发生这种情况。

已知应用Know Uses

Enterprise JavaBeans(EJB):EJB规约定义了一个activation和deactivation机制,可以被容器用来把bean从内存中换出到次级存储器,从而为其他需要激活的bean释放了内存。bean实例必须实现ejbPassivate()方法,并且释放所有获得的资源。这个方法会被容器在换出bean之前调用到。

.NET:.NET的Coommon Language Runtime(CLR)内部使用了垃圾收集器来释放不用的对象。垃圾收集器根据对象的寿命,分3步给对象归类。如果对象想要在完全清楚之前被通知,那么它必须实现Finalize()方法。.NET建议使用Disposal Method模式的Dispose()方法,客户应该用这个方法来显式地释放对象。

换页:进程所用的内存会被划分成页面。当操作系统需要的内存不够时,不使用的页面就会从内存中清除,并写到磁盘文件上去。当一个页面被再次访问,那么OS就会把需要的信息从磁盘复制到内存。这使得OS可以限制内存中页面数目的上限。换页与通常的交换不同:交换一次清除一个进程的所有页面,而换页则只是清除单独的页面。

最新文章

  1. flask+sqlite3+echarts2+ajax数据可视化报错:UnicodeDecodeError: &#39;utf8&#39; codec can&#39;t decode byte解决方法
  2. 重读高程3: c2-3 script元素
  3. ext 树节点操作
  4. 转 Android Dalvik虚拟机初识
  5. 一个Public的字段引起的,谈谈继承中的new
  6. div均匀分布代码实例
  7. TypeScript 素描 - 模块
  8. 初始化一台linux server来做项目管理和测试
  9. PHP对表单提交特殊字符的过滤和处理
  10. linux比较指令comm、diff、grep -Ff
  11. 项目管理Project
  12. 转delphi中nil的用法
  13. SpringMVC 学习-入门篇
  14. BotVS开发基础—2.7 指标MACD
  15. Socket类 以及 ServerSocket类 讲解
  16. myeclipse的快捷键
  17. Charles 如何破解与连接手机进行抓包
  18. OpenCV3计算机视觉Python语言实现笔记(四)
  19. [CodeForces - 447E] E - DZY Loves Fibonacci Numbers
  20. 缓慢变化维 (Slowly Changing Dimension) 常见的三种类型及原型设计(转)

热门文章

  1. Kubernetes的Controller进阶(十二)
  2. 如何加载本地下载下来的BERT模型,pytorch踩坑!!
  3. plsql 数据库事件触发器
  4. Ubuntu Typora安装
  5. JavaCV的摄像头实战之五:推流
  6. Android Studio 插件(不定期更新)
  7. 关于IBAction、IBOutlet前缀IB的解释
  8. Ajax向服务器发起请求
  9. .NET 云原生架构师训练营(权限系统 RGCA 开发任务)--学习笔记
  10. 浅谈Java之属性赋值的先后顺序