真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

深入分析內(nèi)存泄漏的原因和后果

本篇文章主要探討內(nèi)存泄漏的原因和后果。通過(guò)這篇文章,希望你能收獲更多。下面是探討內(nèi)存泄漏的原因和后果的詳細(xì)內(nèi)容。

創(chuàng)新互聯(lián)建站是專業(yè)的鹽邊網(wǎng)站建設(shè)公司,鹽邊接單;提供網(wǎng)站建設(shè)、成都網(wǎng)站建設(shè),網(wǎng)頁(yè)設(shè)計(jì),網(wǎng)站設(shè)計(jì),建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行鹽邊網(wǎng)站開(kāi)發(fā)網(wǎng)頁(yè)制作和功能擴(kuò)展;專業(yè)做搜索引擎喜愛(ài)的網(wǎng)站,專業(yè)的做網(wǎng)站團(tuán)隊(duì),希望更多企業(yè)前來(lái)合作!

內(nèi)部泄漏錯(cuò)誤代碼:

Fatal error: Allowed memory size of X bytes exhausted (tried to allocate Y bytes)

觀察php程序內(nèi)存使用情況

php提提供了兩個(gè)方法來(lái)獲取當(dāng)前程序的內(nèi)存使用情況。
memorygetusage(),這個(gè)函數(shù)的作用是獲取目前PHP腳本所用的內(nèi)存大小。

memorygetpeak_usage(),這個(gè)函數(shù)的作用返回當(dāng)前腳本到目前位置所占用的內(nèi)存峰值,這樣就可能獲取到目前的腳本的內(nèi)存需求情況。

int memory_get_usage ([ bool $real_usage = false ] )  
int memory_get_peak_usage ([ bool $real_usage = false ] )

函數(shù)默認(rèn)得到的是調(diào)用emalloc()占用的內(nèi)存,如果設(shè)置參數(shù)為TRUE,則得到的是實(shí)際程序向系統(tǒng)申請(qǐng)的內(nèi)存。因?yàn)?PHP 有自己的內(nèi)存管理機(jī)制,所以有時(shí)候盡管內(nèi)部已經(jīng)釋放了內(nèi)存但并沒(méi)有還給系統(tǒng)。

linux 系統(tǒng)文件 /proc/{$pid}/status 會(huì)記錄某個(gè)進(jìn)程的運(yùn)行狀態(tài),里面的 VmRSS 字段記錄了該進(jìn)程使用的常駐物理內(nèi)存(Residence),這個(gè)就是該進(jìn)程實(shí)際占用的物理內(nèi)存了,用這個(gè)數(shù)據(jù)比較靠譜,在程序里面提取這個(gè)值也很容易 。

場(chǎng)景一:程序操作數(shù)據(jù)過(guò)大

情景還原:一次性讀取超過(guò)php可用內(nèi)存上限的數(shù)據(jù)導(dǎo)致內(nèi)存耗盡

實(shí)例:

這是告訴我們程序運(yùn)行時(shí)試圖分配新內(nèi)存時(shí)由于達(dá)到了PHP允許分配的內(nèi)存上限而拋出致命錯(cuò)誤,無(wú)法繼續(xù)執(zhí)行了,在 java 開(kāi)發(fā)中一般稱之為 OOM ( Out Of Memory ) 。
PHP 配置內(nèi)存上限是在php.ini中設(shè)置memory_limit,PHP 5.2 以前這個(gè)默認(rèn)值是8M,PHP 5.2 的默認(rèn)值是16M,在這之后的版本默認(rèn)值都是128M。
問(wèn)題現(xiàn)象:特定數(shù)據(jù)處理時(shí)可復(fù)現(xiàn),做任何 IO 操作都有可能遇到此類問(wèn)題,比如:一次 MySQL 查詢返回大量數(shù)據(jù)、一次把大文件讀取進(jìn)程序等。

解決方法:

1、能用錢解決的問(wèn)題都不是問(wèn)題,如果程序要讀大文件的機(jī)會(huì)不是很多,且上限可預(yù)期,那么通過(guò)ini_set('memory_limit', '1G');來(lái)設(shè)置一個(gè)更大的值或者memory_limit=-1。內(nèi)存管夠的話讓程序一直跑也可以。

2、如果程序需要考慮在小內(nèi)存機(jī)器上也能正常使用,那就需要優(yōu)化程序了。如下,代碼復(fù)雜了很多。

場(chǎng)景二、程序操作大數(shù)據(jù)時(shí)產(chǎn)生拷貝

情景還原:執(zhí)行過(guò)程中對(duì)大變量進(jìn)行了復(fù)制,導(dǎo)致內(nèi)存不夠用。

問(wèn)題現(xiàn)象:局部代碼執(zhí)行過(guò)程中占用內(nèi)存翻倍。

問(wèn)題分析:
php 是寫時(shí)復(fù)制(Copy On Write),也就是說(shuō),當(dāng)新變量被賦值時(shí)內(nèi)存不發(fā)生變化,直到新變量的內(nèi)容被操作時(shí)才會(huì)產(chǎn)生復(fù)制。

解決方法:

及早釋放無(wú)用變量,或者以引用的形式操作原始數(shù)據(jù)。

場(chǎng)景三、配置不合理系統(tǒng)資源耗盡

情景還原:因配置不合理導(dǎo)致內(nèi)存不夠用,2G 內(nèi)存機(jī)器上設(shè)置最大可以啟動(dòng) 100 個(gè) php-fpm 子進(jìn)程,但實(shí)際啟動(dòng)了 50 個(gè) php-fpm 子進(jìn)程后無(wú)法再啟動(dòng)更多進(jìn)程 。

問(wèn)題現(xiàn)象:線上業(yè)務(wù)請(qǐng)求量小的時(shí)候不出現(xiàn)問(wèn)題,請(qǐng)求量一旦很大后部分請(qǐng)求就會(huì)執(zhí)行失敗 。

問(wèn)題分析:一般為了安全方面考慮, php 限制表單請(qǐng)求的最大可提交的數(shù)量及大小等參數(shù),post_max_size、max_file_uploads、upload_max_filesize、max_input_vars、max_input_nesting_level。 假設(shè)帶寬足夠,用戶頻繁的提交post_max_size = 8M數(shù)據(jù)到服務(wù)端,nginx 轉(zhuǎn)發(fā)給 php-fpm 處理,那么每個(gè) php-fpm 子進(jìn)程除了自身占用的內(nèi)存外,即使什么都不做也有可能多占用 8M 內(nèi)存。

解決方法:合理設(shè)置post_max_size、max_file_uploads、upload_max_filesize、max_input_vars、max_input_nesting_level等參數(shù)并調(diào)優(yōu) php-fpm 相關(guān)參數(shù)。

php.ini代碼

$ php -i |grep memory  
memory_limit => 1024M => 1024M //php腳本執(zhí)行最大可使用內(nèi)存  
$php -i |grep max  max_execution_time => 0 => 0 //最大執(zhí)行時(shí)間,腳本默認(rèn)為0不限制,web請(qǐng)求默認(rèn)30s  
max_file_uploads => 20 => 20 //一個(gè)表單里最大上傳文件數(shù)量  
max_input_nesting_level => 64 => 64 //一個(gè)表單里數(shù)據(jù)最大數(shù)組深度層數(shù)  
max_input_time => -1 => -1 //php從接收請(qǐng)求開(kāi)始處理數(shù)據(jù)后的超時(shí)時(shí)間  
max_input_vars => 1000 => 1000 //一個(gè)表單(包括get、post、cookie的所有數(shù)據(jù))最多提交1000個(gè)字段  
post_max_size => 8M => 8M //一次post請(qǐng)求最多提交8M數(shù)據(jù)  
upload_max_filesize => 2M => 2M //一個(gè)可上傳的文件最大不超過(guò)2M

如果上傳設(shè)置不合理那么出現(xiàn)大量?jī)?nèi)存被占用的情況也不奇怪,比如有些內(nèi)網(wǎng)場(chǎng)景下需要 post 超大字符串post_max_size=200M,那么當(dāng)從表單提交了 200M 數(shù)據(jù)到服務(wù)端, php 就會(huì)分配 200M 內(nèi)存給這條數(shù)據(jù),直到請(qǐng)求處理完畢釋放內(nèi)存。

Php-fpm.conf代碼

pm = dynamic //僅dynamic模式下以下參數(shù)生效  
pm.max_children = 10 //最大子進(jìn)程數(shù)  
pm.start_servers = 3 //啟動(dòng)時(shí)啟動(dòng)子進(jìn)程數(shù)  
pm.min_spare_servers = 2 //最小空閑進(jìn)程數(shù),不夠了啟動(dòng)更多進(jìn)程  
pm.max_spare_servers = 5 //最大空閑進(jìn)程數(shù),超過(guò)了結(jié)束一些進(jìn)程  
pm.max_requests = 500 //最大請(qǐng)求數(shù),注意這個(gè)參數(shù)是一個(gè)php-fpm如果處理了500個(gè)請(qǐng)求后會(huì)自己重啟一下,
可以避免一些三方擴(kuò)展的內(nèi)存泄露問(wèn)題

一個(gè) php-fpm 進(jìn)程按 30MB 內(nèi)存算,50 個(gè) php-fpm 進(jìn)程就需要 1500MB 內(nèi)存,這里需要簡(jiǎn)單估算一下在負(fù)載最重的情況下所有 php-fpm 進(jìn)程都啟動(dòng)后是否會(huì)把系統(tǒng)內(nèi)存耗盡。

Ulimit代碼

$ulimit -a
-t: cpu time (seconds)              unlimited  
-f: file size (blocks)              unlimited  
-d: data seg size (kbytes)          unlimited  
-s: stack size (kbytes)             8192  
-c: core file size (blocks)         0  
-v: address space (kbytes)          unlimited  
-l: locked-in-memory size (kbytes)  unlimited  
-u: processes                       1024  
-n: file descriptors                1024

這是我本地mac os的配置,文件描述符的設(shè)置是比較小的,一般生產(chǎn)環(huán)境配置要大得多。

場(chǎng)景四、無(wú)用的數(shù)據(jù)未及時(shí)釋放

情景還原:這種問(wèn)題從程序邏輯上不是問(wèn)題,但是無(wú)用的數(shù)據(jù)大量占用內(nèi)存導(dǎo)致資源不夠用,應(yīng)該有針對(duì)性的做代碼優(yōu)化。

Laravel開(kāi)發(fā)中用于監(jiān)聽(tīng)數(shù)據(jù)庫(kù)操作時(shí)有如下代碼:

代碼:

DB::listen(function ($query) {      
// $query->sql      
// $query->bindings      
// $query->time  
});

啟用數(shù)據(jù)庫(kù)監(jiān)聽(tīng)后,每當(dāng)有 SQL 執(zhí)行時(shí)會(huì) new 一個(gè) QueryExecuted 對(duì)象并傳入匿名函數(shù)以便后續(xù)操作,對(duì)于執(zhí)行完畢就結(jié)束進(jìn)程釋放資源的php程序來(lái)說(shuō)沒(méi)有什么問(wèn)題,而如果是一個(gè)常駐進(jìn)程的程序,程序每執(zhí)行一條 SQL 內(nèi)存中就會(huì)增加一個(gè) QueryExecuted 對(duì)象,程序不結(jié)束內(nèi)存就會(huì)始終增長(zhǎng)。

問(wèn)題現(xiàn)象:程序運(yùn)行期間內(nèi)存逐漸增長(zhǎng),程序結(jié)束后內(nèi)存正常釋放。

問(wèn)題分析:此類問(wèn)題不易察覺(jué),定位困難,尤其是有些框架封裝好的方法,要明確其適用場(chǎng)景。

解決方法:本例中要通過(guò)DB::listen方法獲取所有執(zhí)行的 SQL 語(yǔ)句記錄并寫入日志,但此方法存在內(nèi)存泄露問(wèn)題,在開(kāi)發(fā)環(huán)境下無(wú)所謂,在生產(chǎn)環(huán)境下則應(yīng)停用,改用其他途徑獲取執(zhí)行的 SQL 語(yǔ)句并寫日志。

深入了解

1、名詞解釋

內(nèi)存泄漏(Memory Leak):是程序在管理內(nèi)存分配過(guò)程中未能正確的釋放不再使用的內(nèi)存導(dǎo)致資源被大量占用的一種問(wèn)題。在面向?qū)ο缶幊虝r(shí),造成內(nèi)存泄露的原因常常是對(duì)象在內(nèi)存中存儲(chǔ)但是運(yùn)行中的代碼卻無(wú)法訪問(wèn)他。由于產(chǎn)生類似問(wèn)題的情況很多,所以只能從源碼上入手分析定位并解決。

垃圾回收(Garbage Collection,簡(jiǎn)稱GC):是一種自動(dòng)內(nèi)存管理的形式,GC程序檢查并處理程序中那些已經(jīng)分配出去但卻不再被對(duì)象使用的內(nèi)存。最早的GC是1959年前后John McCarthy發(fā)明的,用來(lái)簡(jiǎn)化在Lisp中手動(dòng)控制內(nèi)存管理。 PHP的內(nèi)核中已自帶內(nèi)存管理的功能,一般應(yīng)用場(chǎng)景下,不易出現(xiàn)內(nèi)存泄露。

追蹤法(Tracing):從某個(gè)根對(duì)象開(kāi)始追蹤,檢查哪些對(duì)象可訪問(wèn),那么其他的(不可訪問(wèn))就是垃圾。

引用計(jì)數(shù)法(reference count):每個(gè)對(duì)象都一個(gè)數(shù)字用來(lái)標(biāo)示被引用的次數(shù)。引用次數(shù)為0的可以回收。當(dāng)對(duì)一個(gè)對(duì)象的引用創(chuàng)建時(shí)他的引用計(jì)數(shù)就會(huì)增加,引用銷毀時(shí)計(jì)數(shù)減少。引用計(jì)數(shù)法可以保證對(duì)象一旦不被引用時(shí)第一時(shí)間銷毀。但是引用計(jì)數(shù)有一些缺陷:1.循環(huán)引用,2.引用計(jì)數(shù)需要申請(qǐng)更多內(nèi)存,3.對(duì)速度有影響,4.需要保證原子性,5.不是實(shí)時(shí)的。

2、php內(nèi)存管理

在 PHP 5.3 以后引入了同步周期回收算法(Concurrent Cycle Collection)來(lái)處理內(nèi)存泄露問(wèn)題,代價(jià)是對(duì)性能有一定影響,不過(guò)一般 web 腳本應(yīng)用程序影響很小。PHP的垃圾回收機(jī)制是默認(rèn)打開(kāi)的,php.ini 可以設(shè)置zend.enable_gc=0來(lái)關(guān)閉。也能通過(guò)分別調(diào)用gcenable() 和 gcdisable()函數(shù)來(lái)打開(kāi)和關(guān)閉垃圾回收機(jī)制。
雖然垃圾回收讓php開(kāi)發(fā)者在內(nèi)存管理上無(wú)需擔(dān)心了,但也有極端的反例:php界著名的包管理工具composer曾因加入一行g(shù)c_disable();性能得到極大提升。

3、php-fpm內(nèi)存泄漏問(wèn)題

在一臺(tái)常見(jiàn)的 nginx + php-fpm 的服務(wù)器上:
nginx 服務(wù)器 fork 出 n 個(gè)子進(jìn)程(worker), php-fpm 管理器 fork 出 n 個(gè)子進(jìn)程。

當(dāng)有用戶請(qǐng)求, nginx 的一個(gè) worker 接收請(qǐng)求,并將請(qǐng)求拋到 socket 中。

php-fpm 空閑的子進(jìn)程監(jiān)聽(tīng)到 socket 中有請(qǐng)求,接收并處理請(qǐng)求。

一個(gè) php-fpm 的生命周期大致是這樣的:

模塊初始化(MINIT)-> 請(qǐng)求初始化(RINIT)-> 請(qǐng)求處理 -> 請(qǐng)求結(jié)束(RSHUTDOWN) -> 請(qǐng)求初始化(RINIT)-> 請(qǐng)求處理 -> 請(qǐng)求結(jié)束(RSHUTDOWN)……. 請(qǐng)求初始化(RINIT)-> 請(qǐng)求處理 -> 請(qǐng)求結(jié)束(RSHUTDOWN)-> 模塊關(guān)閉(MSHUTDOWN)。

在請(qǐng)求初始化(RINIT)-> 請(qǐng)求處理 -> 請(qǐng)求結(jié)束(RSHUTDOWN)這個(gè)“請(qǐng)求處理”過(guò)程是: php 讀取相應(yīng)的 php 文件,對(duì)其進(jìn)行詞法分析,生成 opcode , zend 虛擬機(jī)執(zhí)行 opcode 。
php 在每次請(qǐng)求結(jié)束后自動(dòng)釋放內(nèi)存,有效避免了常見(jiàn)場(chǎng)景下內(nèi)存泄露的問(wèn)題,然而實(shí)際環(huán)境中因某些擴(kuò)展的內(nèi)存管理沒(méi)有做好或者 php 代碼中出現(xiàn)循環(huán)引用導(dǎo)致未能正常釋放不用的資源。
在 php-fpm 配置文件中,將pm.max_requests這個(gè)參數(shù)設(shè)置小一點(diǎn)。這個(gè)參數(shù)的含義是:一個(gè) php-fpm 子進(jìn)程最多處理pm.max_requests個(gè)用戶請(qǐng)求后,就會(huì)被銷毀。當(dāng)一個(gè) php-fpm 進(jìn)程被銷毀后,它所占用的所有內(nèi)存都會(huì)被回收。

4、常駐進(jìn)程內(nèi)存泄漏問(wèn)題

Valgrind 包括如下一些工具:
Memcheck。這是 valgrind 應(yīng)用最廣泛的工具,一個(gè)重量級(jí)的內(nèi)存檢查器,能夠發(fā)現(xiàn)開(kāi)發(fā)中絕大多數(shù)內(nèi)存錯(cuò)誤使用情況,比如:使用未初始化的內(nèi)存,使用已經(jīng)釋放了的內(nèi)存,內(nèi)存訪問(wèn)越界等。

Callgrind。它主要用來(lái)檢查程序中函數(shù)調(diào)用過(guò)程中出現(xiàn)的問(wèn)題。

Cachegrind。它主要用來(lái)檢查程序中緩存使用出現(xiàn)的問(wèn)題。

Helgrind。它主要用來(lái)檢查多線程程序中出現(xiàn)的競(jìng)爭(zhēng)問(wèn)題。

Massif。它主要用來(lái)檢查程序中堆棧使用中出現(xiàn)的問(wèn)題。

Extension??梢岳胏ore提供的功能,自己編寫特定的內(nèi)存調(diào)試工具。

Memcheck 對(duì)調(diào)試 C/C++ 程序的內(nèi)存泄露很有幫助,它的機(jī)制是在系統(tǒng) alloc/free 等函數(shù)調(diào)用上加計(jì)數(shù)。 php 程序的內(nèi)存泄露,是由于一些循環(huán)引用,或者 gc 的邏輯錯(cuò)誤, valgrind 無(wú)法探測(cè),因此需要在檢測(cè)時(shí)需要關(guān)閉 php 自帶的內(nèi)存管理。

代碼:

$ export USE_ZEND_ALLOC=0   
# 設(shè)置環(huán)境變量關(guān)閉內(nèi)存管理  
 valgrind --tool=memcheck --num-callers=30 --log-file=php.log
/Users/zouyi/Downloads/php-5.6.31/sapi/cli/php  leak.php

引用:

definitely lost: 肯定內(nèi)存泄露
indirectly lost: 非直接內(nèi)存泄露
possibly lost: 可能發(fā)生內(nèi)存泄露
still reachable: 仍然可訪問(wèn)的內(nèi)存
suppressed: 外部造成的內(nèi)存泄露

Callgrind 配合 php 擴(kuò)展 xdebug 輸出的 profile 分析日志文件可以分析程序運(yùn)行期間各個(gè)函數(shù)調(diào)用時(shí)占用的內(nèi)存、 CPU 占用情況。

總結(jié):遇到了內(nèi)存泄露時(shí)先觀察是程序本身內(nèi)存不足還是外部資源導(dǎo)致,然后搞清楚程序運(yùn)行中用到了哪些資源:寫入磁盤日志、連接數(shù)據(jù)庫(kù) SQL 查詢、發(fā)送 Curl 請(qǐng)求、 Socket 通信等, I/O 操作必然會(huì)用到內(nèi)存,如果這些地方都沒(méi)有發(fā)生明顯的內(nèi)存泄露,檢查哪里處理大量數(shù)據(jù)沒(méi)有及時(shí)釋放資源,如果是 php 5.3 以下版本還需考慮循環(huán)引用的問(wèn)題。多了解一些 Linux 下的分析輔助工具,解決問(wèn)題時(shí)可以事半功倍。
最后宣傳一下穿云團(tuán)隊(duì)今年最新開(kāi)源的應(yīng)用透明鏈路追蹤工具 Molten:https://github.com/chuan-yun/Molten。安裝好php擴(kuò)展后就能幫你實(shí)時(shí)收集程序的 curl,pdo,mysqli,redis,MongoDB,memcached 等請(qǐng)求的數(shù)據(jù),可以很方便的與 zipkin 集成。

關(guān)于內(nèi)存泄漏的原因和后果就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的參考價(jià)值,可以學(xué)以致用。如果喜歡本篇文章,不妨把它分享出去讓更多的人看到。


當(dāng)前名稱:深入分析內(nèi)存泄漏的原因和后果
轉(zhuǎn)載注明:http://weahome.cn/article/iphdhi.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部