中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

深入分析內存泄漏的原因和后果

發布時間:2020-05-11 17:51:06 來源:億速云 閱讀:393 作者:Leah 欄目:編程語言

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

內部泄漏錯誤代碼:

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

觀察php程序內存使用情況

php提提供了兩個方法來獲取當前程序的內存使用情況。
memorygetusage(),這個函數的作用是獲取目前PHP腳本所用的內存大小。

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

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

函數默認得到的是調用emalloc()占用的內存,如果設置參數為TRUE,則得到的是實際程序向系統申請的內存。因為 PHP 有自己的內存管理機制,所以有時候盡管內部已經釋放了內存但并沒有還給系統。

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

場景一:程序操作數據過大

情景還原:一次性讀取超過php可用內存上限的數據導致內存耗盡

實例:

<?php  ini_set('memory_limit', '128M');  
$string = str_pad('1', 128 * 1024 * 1024);    
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 134217729 bytes) 
in /Users/zouyi/php-oom/bigfile.php on line 3

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

解決方法:

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

2、如果程序需要考慮在小內存機器上也能正常使用,那就需要優化程序了。如下,代碼復雜了很多。

<?php  
//php7 以下版本通過 composer 引入 paragonie/random_compat ,為了方便來生成一個隨機名稱的臨時文件  
require "vendor/autoload.php";    
ini_set('memory_limit', '128M');  
//生成臨時文件存放大字符串  
$fileName = 'tmp'.bin2hex(random_bytes(5)).'.txt';  
touch($fileName);  
for ( $i = 0; $i < 128; $i++ ) {      
$string = str_pad('1', 1 * 1024 * 1024);      
file_put_contents($fileName, $string, FILE_APPEND);  
}  
$handle = fopen($fileName, "r");  
for ( $i = 0; $i <= filesize($fileName) / 1 * 1024 * 1024; $i++ )  {     
//do something     
$string = fread($handle, 1 * 1024 * 1024);  
}    
fclose($handle);  
unlink($fileName);

場景二、程序操作大數據時產生拷貝

情景還原:執行過程中對大變量進行了復制,導致內存不夠用。

<?php  
ini_set("memory_limit",'1M');    
$string = str_pad('1', 1* 750 *1024);  
$string2 = $string;  $string2 .= '1';    
Fatal error: Allowed memory size of 1048576 bytes exhausted (tried to allocate 768001 bytes) 
in /Users/zouyi/php-oom/unset.php on line 8    
Call Stack:      
0.0004     235440   1. {main}() /Users/zouyi/php-oom/unset.php:0    zend_mm_heap corrupted

問題現象:局部代碼執行過程中占用內存翻倍。

問題分析:
php 是寫時復制(Copy On Write),也就是說,當新變量被賦值時內存不發生變化,直到新變量的內容被操作時才會產生復制。

解決方法:

及早釋放無用變量,或者以引用的形式操作原始數據。

<?php  
ini_set("memory_limit",'1M');    
$string = str_pad('1', 1* 750 *1024);  
$string2 = $string;  unset($string);  
$string2 .= '1';    
<?php  
ini_set("memory_limit",'1M');    
$string = str_pad('1', 1* 750 *1024);  
$string2 = &$string;  
$string2 .= '1';    
unset($string2, $string);

場景三、配置不合理系統資源耗盡

情景還原:因配置不合理導致內存不夠用,2G 內存機器上設置最大可以啟動 100 個 php-fpm 子進程,但實際啟動了 50 個 php-fpm 子進程后無法再啟動更多進程 。

問題現象:線上業務請求量小的時候不出現問題,請求量一旦很大后部分請求就會執行失敗 。

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

解決方法:合理設置post_max_size、max_file_uploads、upload_max_filesize、max_input_vars、max_input_nesting_level等參數并調優 php-fpm 相關參數。

php.ini代碼

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

如果上傳設置不合理那么出現大量內存被占用的情況也不奇怪,比如有些內網場景下需要 post 超大字符串post_max_size=200M,那么當從表單提交了 200M 數據到服務端, php 就會分配 200M 內存給這條數據,直到請求處理完畢釋放內存。

Php-fpm.conf代碼

pm = dynamic //僅dynamic模式下以下參數生效  
pm.max_children = 10 //最大子進程數  
pm.start_servers = 3 //啟動時啟動子進程數  
pm.min_spare_servers = 2 //最小空閑進程數,不夠了啟動更多進程  
pm.max_spare_servers = 5 //最大空閑進程數,超過了結束一些進程  
pm.max_requests = 500 //最大請求數,注意這個參數是一個php-fpm如果處理了500個請求后會自己重啟一下,
可以避免一些三方擴展的內存泄露問題

一個 php-fpm 進程按 30MB 內存算,50 個 php-fpm 進程就需要 1500MB 內存,這里需要簡單估算一下在負載最重的情況下所有 php-fpm 進程都啟動后是否會把系統內存耗盡。

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的配置,文件描述符的設置是比較小的,一般生產環境配置要大得多。

場景四、無用的數據未及時釋放

情景還原:這種問題從程序邏輯上不是問題,但是無用的數據大量占用內存導致資源不夠用,應該有針對性的做代碼優化。

Laravel開發中用于監聽數據庫操作時有如下代碼:

代碼:

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

啟用數據庫監聽后,每當有 SQL 執行時會 new 一個 QueryExecuted 對象并傳入匿名函數以便后續操作,對于執行完畢就結束進程釋放資源的php程序來說沒有什么問題,而如果是一個常駐進程的程序,程序每執行一條 SQL 內存中就會增加一個 QueryExecuted 對象,程序不結束內存就會始終增長。

問題現象:程序運行期間內存逐漸增長,程序結束后內存正常釋放。

問題分析:此類問題不易察覺,定位困難,尤其是有些框架封裝好的方法,要明確其適用場景。

解決方法:本例中要通過DB::listen方法獲取所有執行的 SQL 語句記錄并寫入日志,但此方法存在內存泄露問題,在開發環境下無所謂,在生產環境下則應停用,改用其他途徑獲取執行的 SQL 語句并寫日志。

深入了解

1、名詞解釋

內存泄漏(Memory Leak):是程序在管理內存分配過程中未能正確的釋放不再使用的內存導致資源被大量占用的一種問題。在面向對象編程時,造成內存泄露的原因常常是對象在內存中存儲但是運行中的代碼卻無法訪問他。由于產生類似問題的情況很多,所以只能從源碼上入手分析定位并解決。

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

追蹤法(Tracing):從某個根對象開始追蹤,檢查哪些對象可訪問,那么其他的(不可訪問)就是垃圾。

引用計數法(reference count):每個對象都一個數字用來標示被引用的次數。引用次數為0的可以回收。當對一個對象的引用創建時他的引用計數就會增加,引用銷毀時計數減少。引用計數法可以保證對象一旦不被引用時第一時間銷毀。但是引用計數有一些缺陷:1.循環引用,2.引用計數需要申請更多內存,3.對速度有影響,4.需要保證原子性,5.不是實時的。

2、php內存管理

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

3、php-fpm內存泄漏問題

在一臺常見的 nginx + php-fpm 的服務器上:
nginx 服務器 fork 出 n 個子進程(worker), php-fpm 管理器 fork 出 n 個子進程。

當有用戶請求, nginx 的一個 worker 接收請求,并將請求拋到 socket 中。

php-fpm 空閑的子進程監聽到 socket 中有請求,接收并處理請求。

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

模塊初始化(MINIT)-> 請求初始化(RINIT)-> 請求處理 -> 請求結束(RSHUTDOWN) -> 請求初始化(RINIT)-> 請求處理 -> 請求結束(RSHUTDOWN)……. 請求初始化(RINIT)-> 請求處理 -> 請求結束(RSHUTDOWN)-> 模塊關閉(MSHUTDOWN)。

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

4、常駐進程內存泄漏問題

Valgrind 包括如下一些工具:
Memcheck。這是 valgrind 應用最廣泛的工具,一個重量級的內存檢查器,能夠發現開發中絕大多數內存錯誤使用情況,比如:使用未初始化的內存,使用已經釋放了的內存,內存訪問越界等。

Callgrind。它主要用來檢查程序中函數調用過程中出現的問題。

Cachegrind。它主要用來檢查程序中緩存使用出現的問題。

Helgrind。它主要用來檢查多線程程序中出現的競爭問題。

Massif。它主要用來檢查程序中堆棧使用中出現的問題。

Extension。可以利用core提供的功能,自己編寫特定的內存調試工具。

Memcheck 對調試 C/C++ 程序的內存泄露很有幫助,它的機制是在系統 alloc/free 等函數調用上加計數。 php 程序的內存泄露,是由于一些循環引用,或者 gc 的邏輯錯誤, valgrind 無法探測,因此需要在檢測時需要關閉 php 自帶的內存管理。

代碼:

$ export USE_ZEND_ALLOC=0   
# 設置環境變量關閉內存管理  
 valgrind --tool=memcheck --num-callers=30 --log-file=php.log
/Users/zouyi/Downloads/php-5.6.31/sapi/cli/php  leak.php

引用:

definitely lost: 肯定內存泄露
indirectly lost: 非直接內存泄露
possibly lost: 可能發生內存泄露
still reachable: 仍然可訪問的內存
suppressed: 外部造成的內存泄露

Callgrind 配合 php 擴展 xdebug 輸出的 profile 分析日志文件可以分析程序運行期間各個函數調用時占用的內存、 CPU 占用情況。

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

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

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

永修县| 泰州市| 宁乡县| 子长县| 甘南县| 天水市| 永城市| 湘潭市| 屯留县| 麻江县| 大洼县| 波密县| 双辽市| 深圳市| 吴忠市| 连城县| 康定县| 花莲县| 大丰市| 娄底市| 潼南县| 犍为县| 兴海县| 大厂| 拉萨市| 富顺县| 文山县| 会东县| 兰州市| 曲水县| 新丰县| 上蔡县| 稻城县| 繁昌县| 平泉县| 灌云县| 子洲县| 南昌县| 凯里市| 淄博市| 永德县|