hbzhang

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  8 随笔 :: 0 文章 :: 6 评论 :: 0 引用

公告

2012年2月27日 #

问题简述

客户的一台测试服务器出现IIS进程无法启动的现象。Windows事件查看器给出如下信息:

image

现场已经检查过IIS相关配置信息,没有发现问题。另外,也使用过aspnet_regiis.exe来重新注册,但仍然不能解决问题。

初步分析

仔细检查Windows事件查看器给出的信息,基本都是连续5次警告后,接着一个错误。警告信息如下:

image

这说明是IIS连续5次都未能为应用程序池”DefaultAppPool”成功创建w3wp进程,于是自动禁用了应用程序池。通过EventID=5022可以检索到更多信息:

http://technet.microsoft.com/en-us/library/cc735004(WS.10).aspx

 

在这个网页中,指示微软提供了一个Err.exe工具,可以查看错误号的具体含义:

1. 下载地址:http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=985

2. Err工具相关说明:http://blogs.msdn.com/b/astebner/archive/2008/06/17/8611734.aspx

 

Ok,首先通过查看事件的详细信息可以得到具体的错误号(0x80070002):

image

接着就用这个Err工具来查看错误号的具体含义:

image

从以上信息可初步推断,FileNotFound是最可能的问题原因

问题确认

既然怀疑的方向是FileNotFound,那么就该确认究竟是什么文件不能被找到?这就要使用ProcMon工具了(Sysinternals著名的系列工具之一)。

 

运行ProcMon,先仅选中Show File System Activity。然后尝试登录Portal,复现w3wp无法启动的现象,从文件跟踪过程中找到与w3wp有关的一段信息:

image

有趣,似乎是要用一个aqDebuggerWrapper6x64.exe的程序来启动w3wp.exe?

 

再次重现问题,这次我们同时启用ProcMon的Show Registry Activity:

image

Ok,至此问题已经清楚,原来是启用了ImageFileExecutionOptions调试功能,该功能允许启动任意一个程序时将控制权转由另外一个程序来执行。通常,这个功能被调试器所用。检查C:\Windows\System32下,并无aqDebuggerWrapper6x64.exe程序,难怪报错FileNotFound(0x80070002)。

 

经了解,原来这台机器上刚安装过一个AQTime 6.2的分析工具,但使用时总是出现AV错误,所以又被卸掉了。看来,就是在卸载过程中出现了异常,在注册表中还残留了一些与AQTime相关的信息,导致w3wp启动时转由AQTime调试器来执行,而AQTime调试器已经被卸掉了,所以w3wp无法启动。

总结

分析这个案例有两个收获:

  1. 找到一个Err.exe工具
    这个工具可用于查找微软底层程序报出的错误号的具体含义,很有用
  2. 了解到注册表ImageFileExecutionOptions控制的调试功能
    以前也接触过,但略过去了。这次细致研究了一下,发现的确是一个很有用的特性。例如,可以利用这个功能在w3wp启动阶段挂windbg或Visual Studio调试器。
    参考文章如下:
    http://msdn.microsoft.com/en-us/library/a329t4ed(v=vs.71).aspx
    http://blogs.technet.com/b/marcelofartura/archive/2007/08/09/how-to-attach-a-debugger-from-the-creation-time-of-the-worker-process-w3wp-exe.aspx
    http://mvolo.com/blogs/serverside/archive/2007/05/19/Troubleshooting-IIS7-503-_2200_Service-unavailable_2200_-errors-with-startup-debugging.aspx
posted @ 2012-02-27 23:59 hbzhang 阅读(82) 评论(1) 编辑

2012年2月12日 #

摘要: 缘起 最近在用AQTime分析一个功能节点的性能问题时,注意到AQTime给出的性能数据存疑: BulkCopyTool是程序员写的一个工具类,利用Ado.net的SqlBulkCopy特性来快速...阅读全文
posted @ 2012-02-12 01:37 hbzhang 阅读(288) 评论(2) 编辑

2012年1月15日 #

问题背景

由于性能方面的考虑,我们基于B/S架构的程序要求客户最好使用IE8及以后的版本。由于IE8支持兼容性视图模式,同事写的检查IE版本的代码在工作中发现并不可靠,为此花费时间研究了一下这个问题。本文记录下一些研究心得。

注:约束是在前端使用JS脚本检测,因此忽略掉Asp.net服务端HttpBrowserCapabilities相关内容。

如何限制只检测一次

如果checkIEVersion分布在各个页面的onload事件中(例如通过asp.net的母板),那么访问每个页面都会有一次消耗。合适的方法是只在网站的default.html文件中进行IE版本检查,这样可确保访问网站时只检测一次。

如何使用navigator检测版本

与第一感不同,navigator的appVersion属性并没有userAgent属性可靠。

 

userAgentd的内容示例如下:

Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; .NET CLR 1.1.4322)

 

结合RegEx可解析出正确的版本号:

function getInternetExplorerVersion()

// Returns the version of Internet Explorer or a -1

// (indicating the use of another browser).

{

var rv = -1; // Return value assumes failure.

if (navigator.appName == 'Microsoft Internet Explorer')

{

var ua = navigator.userAgent;

var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");

if (re.exec(ua) != null)

rv = parseFloat( RegExp.$1 );

}

return rv;

}

如何应对IE8兼容性模式的问题

当IE8处于兼容性视图模式时,userAgent给出的版本信息为MSIE 7.0。有两种解决方法:

1. 检测userAgent中新增的Trident标志,Trident/4.0指示IE引擎版本为IE 8,Trident/5.0指示IE引擎版本为IE 9

2. 检测document.documentMode属性是否存在,该属性首次在IE 8引入

 

参考列表

主要的参考资料如下:

1. Detecting Internet Explorer More Effectively

2. Using the navigator object to detect client's browser

3. The Internet Explorer 8 User-Agent String (Updated Edition)

4. Defining Document Compatibility

posted @ 2012-01-15 21:37 hbzhang 阅读(176) 评论(0) 编辑

2012年1月6日 #

引言

.Net 4.0重构了StringBuilder的实现,采用了新的数据存储方式,不仅在效率上有大的提高,并且彻底避免了中间处理过程出现临时String对象进入LOH大对象堆的情况。本文对此进行分析。

回顾.Net 2.0的StringBuilder实现

Reflector查看StringBuilder的实现:

clip_image002

其内部数据存储结构为string(对应成员变量m_StringValue)。StringBuilder的构造函数可以依据指定的字符串和容量来初始化,默认为空串(string.Empty),默认容量为16。构造函数使用string的GetStringForStringBuilder方法对m_StringValue变量赋值:

clip_image004

 

查看GetStringForStringBuilder的实现,m_StringValue被视为char数组,其大小由capacity容量决定,该值可以大于实际存储的string对象的大小:

clip_image006

在Append等方法中,若发现当前字符数组已不能满足存储要求,则调用GetNewString方法扩展,否则用wstrcpy拷贝字符到存储区域:

clip_image008

GetNewString采用容量翻倍的方式扩展字符数组,如果发现翻倍的扩展不能满足要求,则直接设置为requiredLength大小

clip_image010

由以上分析可知,容量增长并非一定严格以16为基数增长,例如16、32、64、128,某次扩展可能导致基数发生变化。例如,假设用字符串“ABCDE”初始化一个StringBuilder,则字符数组容量为16,目前使用了其中的5个位置。随后Append一个长度为40的字符串,由于翻倍增长到32仍不能满足要求,将调整capacity=45,容量增长序列将变为16、45、90、180。

 

.Net 2.0 StringBuilder的容量扩展有几个缺陷:

1. 每次容量扩展都会生成一个新的string对象,其内容来自于复制老的string对象。之后,老的string对象就被废弃掉。在此过程中,若string对象大于85k(严格讲应该是85k / 2,因为是unicode字符),将会进入LOH大对象堆

2. 随容量的增长,例如大于1M,在LOH大对象堆中寻找连续的内存空间会变得困难,尤其若LOH碎片化严重时

3. 存在额外的拷贝消耗,会有轻微的性能损失

4. 要处理线程安全问题,包括m_StringValue本身就是被定义为volatile,也会有轻微的性能损失

 

可见,.Net 2.0实现的StringBuilder,内部的存储结构就是一个简单的string对象。当内部的string对象其容量不足以容纳新的Append数据时,就需要扩展容量,扩展方式为容量加倍。在容量扩展后,老的string对象及新Append的数据要拷贝到新的内存区域。这个过程中,会生成新的string对象及废弃老的string对象。如果string对象的尺寸已经大于85k,那么每次容量扩展就会有两个string对象进入到LOH中。本质上,相对于直接用“+”方式连接两个string对象,StringBuilder只是大幅降低了临时string对象的生成频率,而并不能彻底规避临时string对象的生成

.Net 4.0新的StringBuilder实现

.Net 4.0实现StringBuilder的方式非常精彩:

clip_image012

存储结构不再是string对象,而明确为字符数组(对应成员变量m_ChunkChars)。每个StringBuilder字符数组的最大大小由Max_ChunkSize限定,取值为8000(对应16进制的0x1F40)。如果需要存储的数据超过8000,怎么处理呢?奥秘就在m_ChunkPrevious成员变量上,每个StringBuilder内部会维护一个指向StringBuilder实例的指针,由此构成一个StringBuilder链表。即一个逻辑上的存储,实际是被链表上所有StringBuilder实例共同分担的。

 

构造函数初始化m_ChunkChars的代码如下,默认字符数组大小仍为16:

clip_image014

容量扩展由如下方法完成:

clip_image016

在容量扩展时,让m_ChunkPrevious指向自己!很精彩的实现,避免了.Net 2.0时的Copy开销,仅仅就是一个指针的赋值:

clip_image018

而Math.Max计算决定了新字符数组的分配,其大小被控制在8000以内,从而确保不会进入LOH大对象堆。

 

总体看,.Net 2.0主要的实现缺陷都被优化掉了。当然,由于链表方式的引入,部分方法的处理变得复杂。例如:

clip_image020

不过实现同样精彩,并不需要从头到尾开始复制字符,从尾到头是高效的做法。

总结

.Net 4.0的StringBuilder实现,将原来单一String对象的存储方式,优化为一系列的内存块(Memory Chunk)。每个StringBuilder包含一个内存块,通过引用其它StringBuilder对象的方式,形成了一个内存块的链表。通过限定每个内存块的最大大小,确保StringBuilder在容量扩展过程中不会有大对象的产生。从实现算法看,最常用的Append方法效率较.Net 2.0要高,ToString大致等效,而Insert方法效率较.Net 2.0会下降。总体看,实现相当精彩。

 

个人认为,StringBuilder默认容量沿用16实在太小了,常常导致最初需要多次容量扩张。如果16的容量就能满足要求,那实际上就不会用StringBuilder,而直接用String+了。个人认为512或1024是较为合适的选择。

posted @ 2012-01-06 00:35 hbzhang 阅读(292) 评论(1) 编辑

2011年12月15日 #

引言

想升级到.Net 4.0已经很有一段时间了,本来是应该把各个工程文件都升级的,但由于种种原因,一直没有太合适的时机。考虑.Net 4.0可以做到版本想下兼容,因此在近期采用保持dll不变,而仅仅将程序运行环境升级到.Net 4.0的策略进行了一次升级工作。我们的程序为一个Web应用,在升级过程中遇到了两个比较有意思的问题,特予以记录。

主要的升级工作

对于Web应用,主要的升级工作其实就是手工更改web.config文件,主要参考了以下文档:

   How to: Upgrade an ASP.NET Web Application to ASP.NET 4

 

另外,为httpRuntime节增加了一个requestValidationMode属性:

clip_image002

否则在操作页面时会出现下面的错误:

clip_image004

 

问题1:ClientIDModel

修改web.config后,在32位环境下已基本可运行。但部分功能节点出现问题,表现为弹出窗口关闭后,主界面并未按预期刷新。

 

通过Visual Studio调试器跟踪,发现是服务端找不到btnFind控件,所以没有触发btnFind_Click事件。服务端控件创建没有问题,但客户端传递的EventTarget是M$P1$BtnFind,而正确的值应该是u$M$P1$BtnFind。

clip_image006

服务端控件中,CachedUniqueID是期望的,CachedPredictableID则输出了不正确的ID。原因是.net使用了Predictable方式进行ID输出:

clip_image008

Predictable是4.0提供的一种优化的ID生成算法,也是默认的配置。将ClientIDMode修改成 "AutoID",使用.Net 2.0的方式进行ID输出,问题解决:

clip_image010

问题2:64位环境下w3wp进程启动时崩溃

解决问题1后,在32位环境下web应用一切正常。但在64位环境下,发现w3wp.exe启动时出现Crash现象,IIS在自动连续尝试3次后停止,而IE客户端指示无法显示该网页:

clip_image012

注:即便是64位环境,如果将AppPool配置为32位模式也没有问题。

 

收集Dump文件,发现是由于ExecutionEngineException异常导致w3wp崩溃

clip_image014

调用栈指示最后的出错位置是在通过反射获取Assembly相关Attribute时:

clip_image016

查看相关代码,作用是查找AppDomain中加载的Assembly,如果有Assembly打上了AjaxFrameworkAssemblyAttribute标记,则将该Assembly作为DefaultAjaxFrameworkAssembly,否则使用系统自带的System.Web.Extensions程序集:

clip_image018

每个ScriptManager在创建时均会触发上述代码,通过两个静态变量_defaultAjaxFrameworkAssembly和_ajaxFrameworkAssemblyConfigChecked控制相关逻辑只触发1次。

 

在了解了代码的大致逻辑后,解决问题的思路自然就是要查看究竟是在获取哪个Assmbly的CustomAttributes时引发了异常?但捕获的dump文件已无法确定assembly的详细信息。于是直接用Reflector Pro反编译生成CLR源码进行调试。调试时发现,尽管设置断点可以顺利命中,但各个变量的值却无法用调试器查看,原因是CLR代码已优化。

 

换一个做法,把反编译得到的相关代码拷贝出来,单独放置到一个独立的.aspx页面文件中执行,顺利得到了异常信息:

clip_image020

引发问题的是一个FarPoint.CalcEngine.dll,将其从Portal\bin下删除后w3wp就可以正常启动了。FarPoint是一个第三方图形控件,其Target Runtime指示为.Net 1.0。但这并非问题所在,FarPoint有多个dll,均为.Net 1.0,但只有这个FarPoint.CalcEngine.dll会引发问题:

  clip_image022

问题解法

显然,每次通过手工排查的方式去查找引发异常的Assembly太累了,需要一种较为彻底的解决方案。选择的解决方案为通过反射技术将_defaultAjaxFrameworkAssembly强制赋值为true,以屏蔽掉查找DefaultAjaxFrameworkAssembly的逻辑

 

具体做法是编辑网站的Global.asax文件,在Application_Start事件处理函数中加入如下两行代码:

   System.Reflection.FieldInfo fldInfo = typeof(System.Web.UI.ScriptManager).GetField("_ajaxFrameworkAssemblyConfigChecked",

      System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);

   fldInfo.SetValue(fldInfo.GetValue(null), true);

至此问题圆满解决。

问题探讨

其实问题1并非真的问题,微软只是没有在升级Asp.net 4的文档中指明而已,但另外有一个同样来自微软的文档,已经详细描述了包括ClientIDModel在内的一些由.Net 4.0引发的变动问题:

   ASP.NET 4 Breaking Changes

 

但问题2我认为是一个大问题,很难理解设计者的意图。难道是允许开发者替换默认的MS-Ajax框架?如果真是这样,那ScriptManager也会被替换掉啊,怎么可能保留MS-Ajax的ScriptManager,由其指向一个第三方的Ajax框架?另外,代码实现缺乏健壮性,至少应该加一个try..catch异常处理,避免程序崩溃。估计开发者没有考虑到会有Assembly导致出现致命异常的情况。

 

这里还有一个疑问,就是为什么64位环境和32位环境会存在差异?用windbg的!Analyze –v命令对dump文件进行分析,最后的Native代码为调用clr!BlobToAttributeSet方法,触发的是一个访问违例异常(AccessViolationException):

  clip_image024

从以上信息看,最后出错是因为访问了一个空指针。按理,32位的windows和64位的windows在虚拟空间的最底层都留了64K用于空指针检查,并无特别差异,何况这里是真正为0的空指针啊?

 

用ILDasm查看FarPoint.CalcEngine.dll,推测Bug应该是由这个reqrefuse引用为空造成的

clip_image026

这是该dll与其它几个FarPoint dll最显著的一个不同之处。至于问题的准确答案,也只有能用CLR Native源码进行调试的微软程序员可以给我们解惑了。

posted @ 2011-12-15 00:26 hbzhang 阅读(93) 评论(1) 编辑

2011年12月3日 #

摘要: 引言 以个人的经验,在.Net下所有的OOM问题都是有解的,但有一个唯一的例外,就是GC大对象堆的碎片化问题。.Net的GC程序有一个致命缺陷,就是从来不对大对象堆LOH(Large Object Heap)进行内存整理。随着时间的推移,一个持续运行的程序必然面对LOH碎片化的困境。如果程序足够好,例如只有很少量的大对象或者创建大对象的频率足够低,那么LOH碎片化的几率就会降低,但也仅仅是降低而已。对于一个实际的企业应用程序,由于不可能避免大对象的分配,也就不可能避免LOH的碎片化问题,从而也就无法从根本上避免OOM问题。以后有时间再详细探讨.Net GC程序的各个特性,本文先点到即止。 问题阅读全文
posted @ 2011-12-03 00:14 hbzhang 阅读(34) 评论(1) 编辑

2011年11月28日 #

摘要: 引言 在32位环境下,虚拟内存空间是一种宝贵的计算资源。普通情况下,4G虚拟内存中只有2G可以供应用程序使用。即便打开Windows操作系统的3GB开关,也只有接近3G的虚拟内存可用。许多在32位环境下运行的大型企业应用程序,都会面临OOM(OutOfMemory)问题。以个人的经验,.Net的GC Heap在800M以内时,系统会有较好的性能表现。当GC Heap超过800M时,GC效率即开始大幅下降。若GC Heap达到1.1G ~ 1.3G这个区间,往往就会出现OOM问题。从操作系统分配内存的角度看,OOM的原因只有一个,就是无法在VM中找到可用的连续内存空间,不能满足内存分配需求。造成阅读全文
posted @ 2011-11-28 14:39 hbzhang 阅读(72) 评论(0) 编辑

2007年12月20日 #

posted @ 2007-12-20 20:45 hbzhang 阅读(350) 评论(0) 编辑

仅列出标题