什么样的情况会导致网页不断的崩溃
有許多種原因可能導致 Web 站點無法正常工作,這使得系統地檢查所有問題變得很困難。下面將集中分析總結導致 Web 站點崩潰的最常見的問題。如果可以解決這些常規問題,那麼也將有能力對付出現的一些意外情況。
磁片已滿
導致系統無法正常運行的最可能的原因是磁片已滿。一個好的網路管理員會密切關注磁片的使用情況,隔一定的時間,就需要將磁片上的一些負載轉存到備份存儲介質中 ( 例如磁帶 ) 。
日誌檔會很快用光所有的磁碟空間。 Web 伺服器的日誌檔、 SQL*Net 的日誌檔、 JDBC 日誌檔,以及應用程式伺服器日誌檔均與記憶體洩漏有同等的危害。可以採取措施將日誌檔保存在與作業系統不同的檔系統中。日誌檔系統空間已滿時 Web 伺服器也會被掛起,但機器自身被掛起的幾率已大大減低。
C 指標錯誤
用 C 或 C++ 編寫的程式,如 Web 伺服器 API 模組,有可能導致系統的崩潰,因為只要間接引用指標 ( 即,訪問指向的記憶體 ) 中出現一個錯誤,就會導致作業系統終止所有程式。另外,使用了糟糕的 C 指標的 Java 模擬量 (analog) 將訪問一個空的物件引用。 Java 中的空引用通常不會導致立刻退出 JVM ,但是前提是程式師能夠使用異常處理方法恰當地處理錯誤。在這方面, Java 無需過多的關注,但使用 Java 對可靠性進行額外的度量則會對性能產生一些負面影響。
記憶體洩漏
C/C++ 程式還可能產生另一個指標問題:丟失對已分配記憶體的引用。當記憶體是在副程式中被分配時,通常會出現這種問題,其結果是程式從副程式中返回時不會釋放記憶體。如此一來,對已分配的記憶體的引用就會丟失,只要作業系統還在運行中,則進程就會一直使用該記憶體。這樣的結果是,曾佔用更多的記憶體的程式會降低系統性能,直到機器完全停止工作,才會完全清空記憶體。
解決方案之一是使用代碼分析工具 ( 如 Purify) 對代碼進行仔細分析,以找出可能出現的洩漏問題。但這種方法無法找到由其他原因引起的庫中的洩漏,因為庫的源代碼是不可用的。另一種方法是每隔一段時間,就清除並重啟進程。 Apache 的 Web 伺服器就會因這個原因創建和清除子進程。
雖然 Java 本身並無指針,但總的說來,與 C 程式相比, Java 程式使用記憶體的情況更加糟糕。在 Java 中,物件被頻繁創建,而直到所有到物件的引用都消失時,垃圾回收程式才會釋放記憶體。即使運行了垃圾回收程式,也只會將記憶體還給虛擬機 VM ,而不是還給作業系統。結果是: Java 程式會用光給它們的所有堆,從不釋放。由於要保存即時 (Just In Time , JIT) 編譯器產生的代碼, Java 程式的大小有時可能會膨脹為最大堆的數倍之巨。
還有一個問題,情況與此類似。從連接池分配一個資料庫連接,而無法將已分配的連接還回給連接池。一些連接池有活動計時器,在維持一段時間的靜止狀態之後,計時器會釋放掉資料庫連接,但這不足以緩解糟糕的代碼快速洩漏資料庫連接所造成的資源浪費。
進程缺乏檔描述符
如果已為一台 Web 伺服器或其他關鍵進程分配了檔描述符,但它卻需要更多的檔描述符,則伺服器或進程會被掛起或報錯,直至得到了所需的檔描述符為止。文件描述符用來保持對開放文件和開放套接字的跟蹤記錄,開放檔和開放套接字是 Web 伺服器很關鍵的組成部分,其任務是將檔複製到網路連接。默認時,大多數 shell 有 64 個檔描述符,這意味著每個從 shell 啟動的進程可以同時打開 64 個檔和網路連接。大多數 shell 都有一個內嵌的 ulimit 命令可以增加檔描述符的數目。
線程鎖死
由多線程帶來的性能改善是以可靠性為代價的,主要是因為這樣有可能產生線程鎖死。線程鎖死時,第一個線程等待第二個線程釋放資源,而同時第二個線程又在等待第一個線程釋放資源。我們來想像這樣一種情形:在人行道上兩個人迎面相遇,為了給對方讓道,兩人同時向一側邁出一步,雙方無法通過,又同時向另一側邁出一步,這樣還是無法通過。雙方都以同樣的邁步方式堵住了對方的去路。假設這種情況一直持續下去,這樣就不難理解為何會發生鎖死現象了。
解決鎖死沒有簡單的方法,這是因為使線程產生這種問題是很具體的情況,而且往往有很高的負載。大多數軟體測試產生不了足夠多的負載,所以不可能暴露所有的線程錯誤。在每一種使用線程的語言中都存在線程鎖死問題。由於使用 Java 進行線程編程比使用 C 容易,所以 Java 程式師中使用線程的人數更多,線程鎖死也就越來越普遍了。可以在 Java 代碼中增加同步關鍵字的使用,這樣可以減少鎖死,但這樣做也會影響性能。如果負載過重,資料庫內部也有可能發生鎖死。
如果程式使用了永久鎖,比如鎖檔,而且程式結束時沒有解除鎖狀態,則其他進程可能無法使用這種類型的鎖,既不能上鎖,也不能解除鎖。這會進一步導致系統不能正常工作。這時必須手動地解鎖。
伺服器超載
Netscape Web 伺服器的每個連接都使用一個線程。 Netscape Enterprise Web 伺服器會線上程用完後掛起,而不為已存在的連接提供任何服務。如果有一種負載分佈機制可以檢測到伺服器沒有回應,則該伺服器上的負載就可以分佈到其他的 Web 伺服器上,這可能會致使這些伺服器一個接一個地用光所有的線程。這樣一來,整個伺服器組都會被掛起。作業系統級別可能還在不斷地接收新的連接,而應用程式 (Web 伺服器 ) 卻無法為這些連接提供服務。用戶可以在流覽器狀態行上看到 connected( 已連接 ) 的提示消息,但這以後什麼也不會發生。
解決問題的一種方法是將 obj.conf 參數 RqThrottle 的值設置為線程數目之下的某個數值,這樣如果越過 RqThrottle 的值,就不會接收新的連接。那些不能連接的伺服器將會停止工作,而連接上的伺服器的回應速度則會變慢,但至少已連接的伺服器不會被掛起。這時,檔描述符至少應當被設置為與線程的數目相同的數值,否則,檔描述符將成為一個瓶頸。
資料庫中的臨時表不夠用
許多資料庫的臨時表 (cursor) 數目都是固定的,臨時表即保留查詢結果的記憶體區域。在臨時表中的資料都被讀取後,臨時表便會被釋放,但大量同時進行的查詢可能耗盡數目固定的所有臨時表。這時,其他的查詢就需要列隊等候,直到有臨時表被釋放時才能再繼續運行。
這是一個不容易被程式師發覺的問題,但會在負載測試時顯露出來。但可能對於資料庫管理員 (DataBase Administrator , DBA) 來說,這個問題十分明顯。
此外,還存在一些其他問題:設置的表空間不夠用、序號限制太低,這些都會導致表溢出錯誤。這些問題表明了一個好的 DBA 對用於生產的資料庫設置和性能進行定期檢查的重要性。而且,大多數資料庫廠商也提供了監控和建模工具以幫助解決這些問題。
另外,還有許多因素也極有可能導致 Web 站點無法工作。如:相關性、子網流量超載、糟糕的設備驅動程式、硬體故障、包括錯誤檔的通配符、無意間鎖住了關鍵的表。
其实在网页在装载的过程中,常常由于种种原因使浏览器的反映变的很慢,或造成浏览器失去响应,甚至会导致机器无法进行其他的操作。
对于访客,如果登录您网站,浏览器就立刻崩溃,我想这对谁都是无法容忍的,对此总结了网站导致浏览器崩溃的原因:
第二章节
1. 内存泄漏
还是先谈下内存泄漏,网站由于内存泄漏的而照成崩溃有两种情况,服务器的崩溃和浏览器的崩溃。内存泄漏所造成的问题是显而易见的,它使得已分配的内存的引用就会丢失,只要系统还在运行中,则进程就会一直使用该内存。这样的结果是,曾占用更多的内存的程序会降低系统性能,直到机器完全停止工作,才会完全清空内存。
Apache 的 Web 服务器是用 C/C++ 编写的, C/C++ 的内存泄漏问题不必多说,系统中存在无法回收的内存,有时候会造成内存不足或系统崩溃。在 Java 中,内存泄漏就是存在一些被分配的可达而无用的对象,这些对象不会被 GC 所回收,然而它却占用内存。
而在客户端, JavaScript 所造成的内存泄漏,也将可能使得浏览器崩溃。关于 JavaScript 的内存泄漏的文章,较权威的有《 Memory leak patterns in JavaScript 》和《 Understanding and Solving Internet Explorer Leak Patterns 》。
JavaScript 是一种垃圾收集式( garbage collector , GC )语言,这就是说,内存是根据对象的创建分配给该对象的,并会在没有对该对象的引用时由浏览器收回。再根据《 Fabulous Adventures In Coding 》一文的说法:“ JScript uses a nongenerational mark-and-sweep garbage collector. ”,对” nongenerational mark-and-sweep ”的可以这样理解,浏览器处理 JavaScript 并非采用纯粹的垃圾收集,还使用引用计数来为 Native 对象 ( 例如 Dom 、 ActiveX Object) 处理内存。
在引用计数系统,每个所引用的对象都会保留一个计数,以获悉有多少对象正在引用它。如果计数为零,该对象就会被销毁,其占用的内存也会返回给堆。 当对象相互引用时,就构成循环引用,浏览器( IE6 , Firefox2.0 )对于纯粹的 JavaScript 对象间的循环引用是可以正确处理的,但由于在引用计数系统,相互引用的对象都不能被销毁,因为是引用计数永远不能为零,因此浏览器无法处理 JavaScript 与 Native 对象 ( 例如 Dom 、 ActiveX Object) 之间循环引用。所以,当我们出现 Native 对象与 JavaScript 对象间的循环引用时,就会出现内存泄漏的问题。
简单来说就是,浏览器使用引用计数来为 Native 对象处理内存,而引用计数的对象无法被销毁,涉及 Native 对象的循环引用将会出现内存泄漏。配合下面的例子,理解这句话,基本上就可以理解 JavaScript 造成的内存泄漏了。
var obj;
window.onload = function(){
// JavaScript 对象 obj 到 DOM 对象的引用,根据 id 获得
obj=document.getElementById("DivElement");
// DOM 对象则有到此 JavaScript 对象的引用,由 expandoProperty 实现
document.getElementById("DivElement").expandoProperty=obj;
};
可见, JavaScript 对象和 DOM 对象间就产生了一个循环引用。由于 DOM 对象是通过引用计数管理的,所以两个对象将都不能销毁。
另一种情况是闭包中,当碰到闭包,我们在 Native 对象上绑定事件响应代码时,很容易制造出 Closure Memory Leak 。其关键原因和前者是一样的,也是一个跨 JavaScript 对象和 Native 对象的循环引用。只是代码更为隐蔽。
window.onload = function AttachEvents(element){
//element 有个引用指向函数 ClickEventHandler()
element.attachEvent( " onclick " , ClickEventHandler);
function ClickEventHandler(){
// 该函数有个引用指向 AttachEvents(element) 调用 Scope , // 也就是执行了参数 element 。
}
}
这里简单理解了 JavaScript 造成内存泄漏的原因,内存泄漏加大浏览器的负担,很有可能导致浏览器崩溃,我们要做的就是尽量去避免这种情况,做法可参考刚刚所说《 Memory leak patterns in JavaScript 》和《 Understanding and Solving Internet Explorer Leak Patterns 》两篇文章加以理解。处理 JavaScript 内存泄漏最终目的还是要打破 JavaScript 对象和 Native 对象间的循环引用或者清零引用计数,释放对象。
一些内存泄漏如闭包内存泄漏,我们可能比较难以发现,内存泄漏的检测我们可能参考《 javascript 内存泄露工具使用》。
2. 网页代码复杂和浏览器 bug
大量个人网站和低质量网站代码的涌现造成对浏览标准的普遍不支持,如果正好碰上浏览器存在的一些 bug ,浏览器渲染引擎在处理这些网页代码的时候会出错,比如陷入死循环或直接崩溃等。
HTML 代码导致网站崩溃
这是 HTML 结构错误而导致 IE6 的崩溃,在 <col width="100"/> 前或后添加任何字符均会导致 IE6 Crash 。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"" http://www.w3.org/TR/html4/loose.dtd"> <html><head></head><body> <table> <tr> <td> <table style="width:100%;table-layout:fixed;"> <colgroup><col width="100px"><col></colgroup> </table> </td> </tr><table></body></html>
该代码来个韩国的一个网站,无论是使用 XHTML 或者 HTML 的什么版本,只要带了 DOCTYPE 声明, IE6 就会立即崩溃,当不带 DOCTYPE 声明的时候就没有错误,原因可能跟文档类型声明有关。
令 IE6 崩溃的 CSS 代码
该代码参考自网站 Cats who Code 。该 Bug 发现与 2007 年,据说是一名日本人发现的:
<style>*{position:relative}</style><table><input></table>
原因在于 table 中直接放置内容,在 IE6 会引起 mshtml.dll 模块损坏而关闭浏览器,非 IE6 则安全无恙。
除此之外,存在于 IE6 的 Bug 还有下面这种情况,当伪类为 a:active 时同样会遇到此问题:
<style type="text/css"> a{position:relative;} a:hover{float:left;} </style><a href=""> 崩溃 IE6 , crash ie6</a>
解决方案:为 <a> 添加 zoom:1; 令其触发 haslayout 。
<style type="text/css"> a{position:relative;zoom:1;} a:hover{float:left;} </style>
令 IE7 崩溃的 CSS 代码
此 Bug 来自偷米饭,它只存在 IE7 中据估计是处理省略字的时候导致 IE7 崩溃。
<style type="text/css"> div{float:left;width:175px;} ul{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;} li{position:relative;} </style><div> <ul> <li> 崩溃崩溃崩溃崩溃崩溃 crash ie7</li> <li> 崩溃崩溃崩溃崩溃崩溃 crash ie7</li> </ul></div>
解决方案:为 <li> 添加 zoom:1; 令其触发 haslayout
令 IE6 崩溃的 JavaScript 代码
来自 Internet Explorer Sucks ,这个网站就是使用了一下代码,当你使用 IE6 访问的时候,浏览器将立刻崩溃。代码如下:
<script>for (x in document.write) { document.write(x);}</script>
具体引起的原因暂时无法解析,但在兼容性和执行效率来看一般不会采取这样的写法。
3. 网页数据过多
网页含有大量需要处理的数据,造成系统繁忙,如多图页面,超长页面等,或者网页内嵌的各种控件会导致浏览器处理大量数据,造成系统繁忙。如 Flash 游戏, ActiveX 控件等。当浏览器访问网站的时候,如果网站的数据量大,会使得浏览器一般在处理过程中会占用很大的 CPU 使用率和内存、造成浏览器失去响应,甚至会使电脑系统死机。在网站开发的时候,如果充分考虑 Web 性能,很大程度上能避免这个问题。
4. Ajax 的 Web 服务漏洞
Ajax 的是基于 XML 的异步传输,文本格式的 XML 消息可能是二进制数据带宽量的两倍之多。传输 XML 消息所需的带宽越多,系统或应用程序用来执行其他任务的可用资源就越少。例如执行复杂算法来获取期望结果。
过高的带宽可能导致由系统超载引起的性能减退。过高的带宽将导致 Ajax 应用程序输出破损的数据,因为没有足够的资源生成干净的数据。这意味着 Web 服务门户 (Ajax 应用程序属于其中的一部分 ) 将把破损数据暴露给门户的其他部分,从而导致畸形消息和过度解析。如果威胁者利用了这个漏洞,则会引起浏览器崩溃。
另外一方面,频繁的、较小的 HTTP 请求会加重后端服务器、负载均衡程序和防火墙的负担,结果是造成过高的带宽,最终导致性能降低。如果客户端长期停留在该页面或没有关闭浏览器,会使得浏览器的内存持续上涨,得不到释放,导致客户端浏览器崩溃。
为此,在较多的时候 Ajax 的时候,我们要考虑通过专门的硬件加速器、优化软件、消除代码冗余、 XML 加速功能和解决互操作性问题等方式加速 Ajax 应用程序。另外,积极地监视通信流可以持续地度量 Ajax 应用程序的网络流量性能。通过将数据放入实时日志中,您可以查看在哪些位置何时出现大量的包丢失和抖动事件,响应变慢的原因以及如何通过修改应用程序的优先级来改善通信流性能。
5. 其他原因
除以上提到的原因之外,还有其他许多原因,虽然有些不会导致浏览器直接崩溃,但也会造成网站无法访问,如日志文件导致磁盘已满、 Web 服务器 C 指针错误、进程缺乏文件描述符、线程死锁、数据库中的临时表不够用和服务器超载等,可参考《导致 Web 站点崩溃最常见的七大原因》。
总结
对于访客,如果登录您网站,浏览器就立刻崩溃,我想这对谁都是无法容忍的,通过总结《网站令浏览器崩溃的原因》,在我们从事网站开发维护的时候,我们应该尽量去避免内存泄漏、代码错误和冗余及数据量过大等问题,构建更佳性能的站点。
AppName: iexplore.exe AppVer: 6.0.2900.2180 ModName: ncity3dengine.dll ModVer: 4.5.0.22 Offset: 000e2710
系统配置:
机型: 联想 T400
硬盘:250 G
CPU :双核。
内存:2G
显卡:独立显卡, ATI MOBILITY RADEON HD 3450
1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块且仅一块内存发生泄漏。比如,在一个 Singleton 类的构造函数中分配内存,在析构函数中却没有释放该内存。而 Singleton 类只存在一个实例,所以内存泄漏只会发生一次。
4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。
内存泄漏会因为减少可用内存的数量从而降低计算机的性能。最终,在最糟糕的情况下,过多的可用内存被分配掉导致全部或部分设备停止正常工作,或者应用程序崩溃。
内存泄漏可能不严重,甚至能够被常规的手段检测出来。在现代操作系统中,一个应用程序使用的常规内存在程序终止时被释放。这表示一个短暂运行的应用程序中的内存泄漏不会导致严重后果。
在以下情况,内存泄漏导致较严重的后果:
* 程序运行后置之不理,并且随着时间的流失消耗越来越多的内存(比如服务器上的后台任务,尤其是嵌入式系统中的后台任务,这些任务可能被运行后很多年内都置之不理);
* 新的内存被频繁地分配,比如当显示电脑游戏或动画视频画面时;
* 程序能够请求未被释放的内存(比如共享内存),甚至是在程序终止的时候;
* 泄漏在操作系统内部发生;
* 泄漏在系统关键驱动中发生;
* 内存非常有限,比如在嵌入式系统或便携设备中;
* 当运行于一个终止时内存并不自动释放的操作系统(比如 AmigaOS )之上,而且一旦丢失只能通过重启来恢复。
检测内存泄漏的关键是要能截获住对分配内存和释放内存的函数的调用。截获住这两个函数,我们就能跟踪每一块内存的生命周期,比如,每当成功的分配一块内存后,就把它的指针加入一个全局的 list 中;每当释放一块内存,再把它的指针从 list 中删除。这样,当程序结束的时候, list 中剩余的指针就是指向那些没有被释放的内存。这里只是简单的描述了检测内存泄漏的基本原理,详细的算法可以参见 Steve Maguire 的 <<Writing Solid Code>> 。
如果要检测堆内存的泄漏,那么需要截获住 malloc/realloc/free 和 new/delete 就可以了(其实 new/delete 最终也是用 malloc/free 的,所以只要截获前面一组即可)。对于其他的泄漏,可以采用类似的方法,截获住相应的分配和释放函数。比如,要检测 BSTR 的泄漏,就需要截获 SysAllocString/SysFreeString ;要检测 HMENU 的泄漏,就需要截获 CreateMenu/ DestroyMenu 。(有的资源的分配函数有多个,释放函数只有一个,比如, SysAllocStringLen 也可以用来分配 BSTR ,这时就需要截获多个分配函数)
在 Windows 平台下,检测内存泄漏的工具常用的一般有三种, MS C-Runtime Library 内建的检测功能;外挂式的检测工具,诸如, Purify , BoundsChecker 等;利用 Windows NT 自带的 Performance Monitor 。这三种工具各有优缺点, MS C-Runtime Library 虽然功能上较之外挂式的工具要弱,但是它是免费的; Performance Monitor 虽然无法标示出发生问题的代码,但是它能检测出隐式的内存泄漏的存在,这是其他两类工具无能为力的地方。