日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > php >内容正文

php

PHP内核——内存管理

發布時間:2023/12/15 php 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 PHP内核——内存管理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

PHP中的變量是不需要手動釋放的,內核幫我們實現了變量的內訓管理,包括內存的分配與回收。本文主要介紹PHP中與內存相關的知識點,包括變量的GC機制、垃圾回收以及底層的內存池實現,除此還有一些線程安全相關的知識點。

變量的自動GC機制

現代高級語言普遍提供了變量的自動GC機制,由語言自己進行管理,這樣開發者就無需關注變量的分配與釋放。PHP同樣實現了這種機制,通過“$”聲明變量后,使用完成,內核會自動進行釋放。

簡單實現方式:函數定義變量時分配一內存,用于保存zval及對應的value結構,在函數返回時再將內存釋放,若函數執行階段,該變量作為參數調用了其他函數或者賦值給了其他變量,則把變量復制一份,這樣使得變量間相互獨立。

這種方式存在的一個問題是:效率低且內存浪費嚴重。針對這種問題,提出了下列通用的解決方法。

通用實現:引用+寫時復制。PHP變量的內存管理就是這種方式實現的。

  • 引用:當變量賦值、傳遞時不直接進行深度拷貝,多個變量同時共用一個value,引用計數來記錄value被使用的變量數目;
  • 寫時復制:當某個變量的value發生改變而無法與其他變量共用value時,通過深度拷貝進行分離value。

引用計數

引用計數用來記錄當前有多少個zval只想同一個zval_value。當有新的zval指向這個value時,計數器加1,當zval銷毀時,計數器減1。當引用計數為0時,表示此value已經沒有被任何變量使用,這是value就可以進行釋放了。

注意:PHP7中將引用計數保存在了zval_value中。

寫時復制

寫時復制在計算機系統中應用非常廣泛,只在必要的時候才會進行深度拷貝。換句話說,資源的復制是在需要寫入的時候才會進行,在此之前,以只讀的方式共享。

回收時機

  • 變量的回收時機:在自動GC機制中,在zval斷開value的指向時,如果發現refcount=0,則會直接釋放value,這就是變量的回收時機。

    除了GC,PHP也可以通過unset()函數主動銷毀一個變量。

垃圾回收

  • 提出的背景:通過引用計數PHP實現了變量的自動GC機制,但是有一種情況是這個機制無法解決的,從而因變量無法回收導致內存始終得不到釋放,造成內存泄漏,這種情況指的是循環引用

    循環引用:簡單來說,就是變量的內部成員引用了變量自身,比如數組中的某個元素指向了數組,這樣一來數組的引用計數中就有一個來自自身成員,當所有的外部引用全部斷開時,數組的refcount仍然大于0而得不到釋放,而實際上這種變量不可能再被使用了。

  • 垃圾:由于循環引用而導致的無法釋放的變量稱為垃圾,PHP引入垃圾回收機制來回收這種垃圾。

    注意:首先明確兩個準則:

    • 如果一個變量value的refcount減少到0,那么此value可以被釋放掉,不屬于垃圾;
    • 如果一個變量value的refcount減少之后大于0,那么此value還不能被釋放掉,此value可能成為一個垃圾。
  • 復合類型的回收時機:在value的refcount減少之后如果仍然大于0,垃圾回收器會把可能成為垃圾的value收集起來,等達到一定數量后開始啟動垃圾鑒定程序,把真正的垃圾釋放掉。

    目前垃圾只會出現在array和object這兩種類型中,需要注意的是:垃圾回收器判斷是否要收集意思垃圾時,并不是根據類型進行判斷的,而是與前面介紹的是否用到引用計數一樣,用過zval.u1.type_flag進行標識的,只有包含IS_TYPE_COLLECTABLE標識的變量類型才會被收集。

  • 垃圾緩存區:垃圾回收器把收集的可能垃圾保存在一個buffer緩存區,收集的時機是refcount減少時,每次refcount減少都會觸發收集動作,如果已收集過就不會重復。

  • 回收算法:既然垃圾是由于成員引用自身引起的,那么就對value的所有成員減一遍引用計數(理解的是:將現有的value的refcount減去目前的所有成員數目),如果結果refcount變為0,則就是表明其引用全部來自自身成員,不會產生垃圾。具體步驟:

    • 步驟(1): 遍歷垃圾回收器的buffer緩存區,把當前value標為灰色(zend_refcounted_h.gc_info置為GC_GREY),然后對當前value的成員進行深度優先遍歷,把成員value的refcount減1,并且也標為灰色。
    • 步驟(2):重復遍歷buffer,檢查當前value引用是否為0,為0則表示確實是垃圾,把它標為白色(GC_WHITE),如果不為0則排除了引用全部來自自身成員的可能,表示還有外部引用,并不是垃圾,這時候因為步驟(1)對成員進行了refcount減1操作,需要還原回去,對所有成員進行深度遍歷,把成員refcount加1,同時標為黑色。
    • 步驟(3):再次遍歷buffer,將并非GC_WHIT的節點從buffer中刪除,最終buffer緩存區中全部為真正的垃圾,最后將這些垃圾釋放,回收完成。

      垃圾回收器主要通過zend_gc_globals這個結構對垃圾進行管理,收集到的可能成為垃圾的value就保存在這個結構的buf中,及垃圾緩存區。

內存池

  • 提出背景:在C語言中,通常使用malloc進行內存的分配,而頻繁地分配、釋放內存無疑會產生內存碎片。在PHP中,變量的分配、釋放非常頻繁,如果所有的變量都通過malloc的方式分配,將會造成嚴重的性能問題,作為語言級的應用,這種損耗是無法接受的。因此,PHP實現了一套內存池(Zend Memery Manager,ZendMM),用來替換malloc、free,以解決內存頻繁分配、釋放問題。

  • 內存池的作用

    • 減少內存分配及釋放的次數
    • 有效控制內存碎片的產生

內存池是PHP內核中最底層內存操作,它是非常獨立的一個模塊,可以移植到其他C語言應用中去。

內存池,定義了三種粒度的內存塊,如下:

內存塊Huge(chunk)Large(page)Small(slot)
內存大小2MB4KB8,16,24,32···3027B(30種)
內存分配策略RM>2MB,直接調用系統分配3092B< RM <2044KBRM<=3092B

此處RM表示申請內存的大小。3092B相當于3/4個page,2044KB相當于511個page。

三種粒度的內存塊間的關系:

一個或者若干個page可以被分割為多個slot。內存池提前定義好了30種同等大小的內存(8,16,24,32···3027),他們分配在不同的page上(不同大小的內存可能會分配在多個連續的page),申請內存時,直接在對應的page上查找可用的slot。

線程安全

  • 提出背景:在多線程環境中,使用全局變量(聲明在函數之外的變量為全局變量)實現多個函數間共享數據,全局變量為各個線程共享,不同的線程引用同一地址空間,如果一個線程修改了全局變量,全局變量就會影響所有的線程。

  • 定義:線程安全指的就是多線程環境下如何安全地獲取公共資源。

  • 應用場景:PHP的SAPI多數是單線程環境,比如Cli、Fpm和Cgi,每個進程只啟動一個主線程,這種模式下是不存在線程安全問題的,但是也存在多線程環境,如Apache或用戶自己嵌入的PHP實現環境,這是就需要考慮線程安全了。PHP通過線程安全資源管理器(Thread Safe Resource Manageer,TSRM),用于解決多線程環境下公共資源沖突問題,實現線程之間安全的操作公共資源。

  • 基本思路:針對共用資源存在的問題,采取各個線程各自復制同一份全局變量,使用數據時各線程各取自己的副本,互不干擾。其核心思想就是為不同的線程分配獨立的內存空間。

  • 基本流程:如果一個資源被多個線程使用,首先需要預先想TSRM注冊資源,TSRM會為這個資源分配一個唯一的id,并把這種資源的大小、初始化函數等保存到一個tsrm_resource_type結構中,各線程只能通過TSRM分配的那個id訪問這個資源。

參考: 秦朋 《PHP7內核剖析》第4章

總結

以上是生活随笔為你收集整理的PHP内核——内存管理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。