ASP.NET站点性能提升-内存

本篇讨论以下内容:

  • 网站中消耗内存的主要两类对象:托管资源和非托管资源。
  • 托管资源的生命周期。
  • 减少托管资源和非托管资源内存使用的方法。
  • 减少session使用的内存。如果使用不当,session会占用很多内存。

托管资源

托管资源是在代码中使用new关键字在堆上创建的对象。

生命周期

当调用new创建对象时,会在托管堆上分配内存。这只会发在在分配区域的末尾,这样效率会很高。

当CLR在分配区域的末尾为一个对象分配内存时,如果发现没有足够的空间,就会启动垃圾收集。这会移除所有没有被引用的对象,并压缩存活的对象。这会在分配区域的末尾产生连续的空间。

当一个对象所有引用离开它们的作用域或它们被设为null,这个对象就可以被垃圾收集了。然而,它只有在下次垃圾收集时,才会被物理删除,这可能不会立即发生。这意味着一个对象会在不再使用它后,继续使用一段时间内存。有意思的是,使用时间越长的对象,越有可能在失去所有引用后继续保留在内存中,这完全取决于它的代。

垃圾收集器进行垃圾收集时,会访问大量的对象,检测它们是否可以被收集,这个操作是非常昂贵的。为了減少代价,CLR假设这个一个事实:使用时间越长的对象比最近创建的对象更不可能失去它们的引用。这意味着优先检查年经的对象是有意义的。

实现这种算法的方式是将所有的对象划分成三代:0代、1代和2代。每一个对象对从0代开始,如果在一次垃圾收集中存活下来,就升级为1代。如果1代对象在下一次垃圾收集中存活下来,就升级为2代。2代对象在下一次垃圾收集中存活下来,还是2代。

0代对象会被频繁收集,1代次之,2代再次之。所以一个对象存活的越久,它所有引用都失效后被移除花费的时间越长。

当1代对象被收集时,0代对象也会被收集。2代对象被收集时,1代和0代都会被收集。所以,高代对象收集代价更高。

大对象堆

除了以下提到的堆,还在一个超过85KB对象使用的推,称作大对象堆。如果大对象堆或者2代对象空间不够,就会触发大对象堆收集和2代对象收集。因为2代对象收集会收集所有代的对象,所以这个操作代价是很高的。

大对象堆上对象在收集时,不会进行压缩。所以,如果在大对象堆上分配了不同大小的大对象,会在对象间留下很多不能再分配的空间。

计数器

在perfmon中使用.NET CLR Memory分类中的以下计数器:

  • Percent Time in GC: 从上次收集完后生,花费在GC上的时间百分比。
  • Gen 0 heap size: 在下次0代垃圾收集前可以分配给0代对象的最大字节数。这不是分配给0代对象的当前字节数。
  • Gen 1 heap size: 1代对象占用的字节数。这不是1代对象可能使用的最大字数。
  • Gen 2 heap size: 2代对象占用的字节数。这不是2代对象可能使用的最大字数。
  • Large Object Heap size: 大对象堆的大小。
  • # Bytes in all heaps: Gen 0 heap size、Gen 1 heap size、Gen 2 heap size、Large Object Heap size的总和。它表示所有托管对象占用的内存。
  • # Gen 0 Collections: 自从程序启动后,0代对象被收集的次数。
  • # Gen 1 Collections: 自从程序启动后,1代对象被收集的次数。
  • # Gen 2 Collections: 自从程序启动后,2代对象被收集的次数。

CLR profiler

CLR Profiler是微软的一个显示程序内存占用的免费工具。从以下地址下载CLR Profiler:

CLR Profiler使用方法参考它的说明文档。

Garbage collector

根据垃圾收集器的工作方式,可以通过以下方法优化性能:

  • 迟获取
  • 早释放
  • 使用StringBuilder连接字符串
  • 使用Compare进行不区分大小写比较
  • 使用Response.Write缓冲区
  • 池化超过85KB的对象

迟获取

尽可能迟得创建对象。这会节省内存,并减短它们的存活时长,这样它们就少可能升级到高代对象。特别地:

  • 不要在长操作前创建对象。
    不要
    LargeObject largeObject = new LargeObject();
    // Long running database call ...
    largeobject.MyMethod();
    而是
    // Long running database call ...
    LargeObject largeObject = new LargeObject();
    largeobject.MyMethod();
  • 除了超过85KB的对象,不要预分配内存。预分配内存在那些不基于.NET没有垃圾收集的系统,例如实时系统,可能会很有效。然而,在.NET下这样做没有任何优势。
  • 如果使用.NET 4,考虑使用Lazy<T>。
    Lazy<ExpensiveObject> expensiveObject = new Lazy<ExpensiveObject>();
    与通常情况下的类实例化不同,这不会创建对象。这个对象只会在第一次引用它的时候被创建。

早释放

如果只需要使用一个对象很短的时间,就要确保它没有一个长时引用。否则,垃圾收集器将不知道它可以被收集,甚至将它升级到高代对象。

如果需要在一个长时对象中引用一个短时对象,当你不再需要这个短时对象时,将它的引用设为null:

LargeObject largeObject = new LargeObject();

// Create referece from long lived object to new large object
longLivedObject.largeObject = largeObject;

// Reference no longer needed
longLivedObject.largeobject = null;
 
在一个类中,如果一个属性引用了一个短时对象,然后要执行一个长时操作前,如果你不再需要这个短时对象,将引用设为null。
private LargeObject largeObject { get;set; }

public void MyMethod() {
    largeObject = new LargeObject();
    // some processing ...
    largeObject = null;
    // more lengthy processing ...
}

对于本地变量(local variables, 方法中定义的变量)没有必要这么做,因为编译器可以判定变量什么时候不会再使用.

使用StringBuilder连接字符串

什么时候不使用StringBuilder

  • 当字符串连接少于7次。
  • 当在一条语句中连接字符串时,只会产生一个最终字符串。
    s = s1 + s2 + s3 + s4;

StringBuilder的容量

StringBuilder的构造器可以接受一个容量参数,默认值是16。每次StringBuilder超过容量,就会分配一起原来容量两倍大的缓冲区,并将老的缓冲区中的内容复制到新人缓冲区中,并将老的缓冲区留给垃圾收集器。

所以,如果知道结果字符串的大小,在StringBuilder构造器中设置容量参数,会提高性能。

使用Compare进行不区分大小写比较

不使用:

// ToLower allocates two new strings
if (s.ToLower() == s2.ToLower()) {

}

使用:

if(string.Compare(s1, s2, true) == 0) {

}

以上代码会使用current culture。使用字节依次比较会更快一点:

if(string.Compare(s1, s2, StringComparison.OrdinalIgnorCase) == 0){

}
 

使用Response.Write缓冲区

不使用:

// Concatenation creates intermediate string
Response.Write(s1 + s2);

使用

Response.Write(s1);
Response.Write(s2);

在使用自定义控件时,对HtmlTextWrite对象使用相同的技术。

池化超过85KB的对象

频繁地分配和收集超过85KB的对象是很昂贵的操作,这也会导致内存碎片。

如果网站需要实例化超过85KB的类,考虑为这些对象在程序启动时创建一个池,而不要每次都创建。

在决定一个对象是否超过85KB时,确定它是自己占用了这些空间,还是仅仅引用了占用这么多空间的对象。例如,一个有85 * 1024 = 87040个元素的字节数组会占用85KB:

byte[] takes85KB = new byte[87040];  // 85 * 1024 = 87040

但是,一个引用85个对象,每个对象占用1024字节的数组是不会占用那么多空间的,因为这个数组仅仅包括了对象的引用而已。

非托管资源

一些经常使用的对象是基于非托管资源的,例如,文件句柄和数据库连接。这些资源都是稀缺资源。垃圾收集器也在会这些对象失去所有引用后,删除它们,并释放它们使用的稀缺资源。但最好在不使用它们后,立即释放,以供其它线程使用。

IDisposable

实现IDisposable的对象提供了Dispose()方法,这个方法会释放对象。它们也经常实现与Dispose方法功能相同的Close()方法,虽然Close()方法并不是IDisposable的成员。

如果一个对象实现了IDisposable接口,就要尽快调用Dispose()方法,而不要缓存这些对象。

为了保证即使抛出了异常,非托管资源也能尽快释放,一般在final代码块中调用Dispose():

SqlConnection connection = new SqlConnection(connectionString);
try {
    // use connection...
}
finally {
    connection.Dispose();
}

在C#中可以使用using语句:

using (SqlConnection connection = new SqlConnection(connectionString)) {
    // use connection ...
}  // connection.Dispose called implicitly

没有必要等到using语句自动释放连接。如果对已释放的对象调用Dispose(),不会产生错误。

using (SqlConnection connection = new SqlConnection(connectionString))
{
    // use connection ...
    connection.Dispose();
    // long running code that doesn't use the connection ...
}  // connection.Dispose is called again implicitly

如果创建了一个包含实现IDisposable的对象的类,或者从一个实现IDisposable的类继承,或者包括操作系统提供的非托管对象,就需要实现IDisposable。更多的信息,查看:

计数器

分类:.NET CLR Memory

# of Pinned Objects:上一次垃圾收集中遇到的pinned对象。非托管代码使用pinned对象。垃圾收集器不能在中移动pinned对象,所有有可能造成堆碎片。

分类:.NET CLR LocksAndThread

# of current logical Threads:应用程序中使用的.NET线程对象的数量。如果这个数字持续上升,那就是在不断地创建新线程,而没有移除老线程和它们的堆栈。

分类:Process

Private Bytes:当前进程分配的不能与其它进程共享的内存大小。非托管对象分配的内存等于Private Bytes – #Bytes in all Heaps(.NET CLR Memory分类)。

Sessions

Session state允许在多个请求间为访问者存储信息。在默认的InProc模式下,这些信息是存储在内存中的。这样速度最快,但如果有多个服务器,这种方式是不起作用的,因为下一个来自同一个访问者的请求可能由另一台服务器处理。StateServer和Sql Server模式满足多服务器的需要,它们将信息存储在中央服务器或数据库中。

ASP.NET为了区别不同用户的Session,将session ID存储在浏览器cookie中。另一种方式是存储中URL中。

ASP.NET没有一个方法判定访问者是否不再使用程序,这样session state就可以删除了。为了解决这个问题,ASP.NET假定如如果20分钟后没有收到访问者的请求,这个访问者的session state就可以删除了。20分钟这个数值是可配置的。这意味着,session state至少占用20分钟内存。

计数器

分类:ASP.NET Applications

Sessions Active:当前活动的session数量。

如果你确认session state占用了过多的,有一些解决方案:

  • 减少session state存活时间。
  • 减少session state占用的空间。
  • 使用另一种session模式。
  • 不使用session state。

如果sessions很快过期,它们会占用更少的内存:

  • 在web.config中配置sessionState元素:
    <configuration>
      <system.web>
        <sessionState mode="InProc" timeout="20" />
      </system.web>
    </configuration>
    也可以使用代码配置:
    Session.Timeout = 20;
  • 当访问者注销时,可以显式移除session:
    Session.Abandon();

减少session state占用的空间

  • 不要存储有额外开销的对象。例如,不要存储用户界面元素或数据表。只存储需要的数据。
  • 在缓存中存储session无关的数据,这样所有用户都可以共享这些数据。例如,不要在session中存储国家列表。
  • 不要存储来自数据库中的数据。这样,就会加重数据库的负载,换取内存使用的减少。这样做在取数据速度快,并且不经常发生,而且数据本身占用很多内存的情况下是可行的。

使用其它的session模式

如果内存不够,可以使用SqlServer模式。这种方式带来的一个好处是当web服务器或应用程序池重启,session不会丢失。但是这会加重数据库的负载。使用这种模式,在不更新session state的页面中指定只读模式:

<%@ Page EnableSessionState="ReadOnly" %>

不使用session state

在很多情况下,根本不需要使用session state。有一些替代方案:

  • 使用AJAX减少页面刷新。这使得可以在页面中保存session数据。
  • 在ViewState中保存session state。session state不是很大,并且都是页面相关的时候,可以这么做。但这样会占用带宽,也有安全问题。

一个不使用session的原因是这会在cookie或URL中存储session ID,而这些会损害缓存功能。

转载于:https://www.cnblogs.com/ntwo/archive/2010/11/17/1879819.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值