OC對象佔用內存原理 (一文完全搞懂)

要想真真切切看到一個OC對象佔用多少內存, 實踐是必不可少的.c++

初始OC對象佔用內存

建立一個 Command Line Tool 工程 , 打開 main.mmain 函數建立一個 NSObject.bash

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *objc = [[NSObject alloc] init];
    }
    return 0;
}
複製代碼

打開終端/iTerm2 , 進入到 main.m 目錄. 將其轉換爲 c++ 源碼.markdown

clang -rewrite-objc main.m -o main.cpp
複製代碼

文件夾目錄裏多出一個 main.cpp 文件 , 打開. 看到98242行代碼,不要慌.咱們只須要關注 NSObject 便可. 搜索 NSObject_IMPL.函數

struct NSObject_IMPL {
    Class isa;
};
複製代碼

這個就是 NSOject 對象對應的 C++ 結構體. 裏面包含了一個 Class 指針. 搜索發現工具

typedef struct objc_class *Class;
複製代碼

其實就是一個指向 struct objc_class 結構體類型的指針. 那麼也就是說目前咱們只發現 NSObject 對象對應的結構體只包含一個 isa 指針變量 , 一個指針變量在 64 位的機器上大小是 8 個字節.ui

那是否是說一個 NSObject 對象就佔用8個字節大小的內存呢?實際上不是的. 答案實際上是: 全部的OC對象至少爲16字節.atom

咱們先來驗證一下. (有興趣的能夠去看看剛剛 main.cpp 中最下面 main 函數中 對象的建立源碼)spa

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *lbobjc = [[NSObject alloc] init];
        
        NSLog(@"lbobjc對象實際須要的內存大小: %zd",class_getInstanceSize([lbobjc class]));
        NSLog(@"lbobjc對象實際分配的內存大小: %zd",malloc_size((__bridge const void *)(lbobjc)));
    }
    return 0;
}
複製代碼

打印結果指針

iOS-OC對象佔用內存探索[2903:181348] lbobjc對象實際須要的內存大小: 8code

iOS-OC對象佔用內存探索[2903:181348] lbobjc對象實際分配的內存大小: 16

先彆着急猜. 咱們來看下內存. 打印語句加個斷點. 走你.

lldb -> po lbobjc

查看內存具體內容方法:

  • 1️⃣ 打開內存查看工具:

地址欄中輸入對象地址: 0x1007579c0

  • 2️⃣ lldb

兩種方法都代表, 目前咱們建立的對象 後面幾個字節所有爲 00 .

咱們能夠經過閱讀 objc4 的源碼來找到答案。經過查看跟蹤 obj4allocallocWithZone 兩個函數的實現,會發現這個連個函數都會調用一個 instanceSize 的函數:

size_t instanceSize(size_t extraBytes) {
     size_t size = alignedInstanceSize() + extraBytes;
      // CF requires all objects be at least 16bytes.
      if (size < 16) size = 16;
      return size; 
}
複製代碼

上面源碼中咱們看出了答案, 最少會開闢16個字節. 那麼爲何非要用 16 個字節來存儲 8 個字節的內容呢? 這裏簡單解釋一下 .

其實這裏主要是涉及到硬件問題, 由於不一樣廠商之間須要一套標準化方案來解決不一樣廠商之間規則不一樣致使內存讀取使用出現不統一的狀況.爲了解決這種問題而產生的 字節對齊.

講到這裏,我還想繼續看下 當這個對象包含多個屬性時使用內存狀況. 以便咱們完全搞明白 OC 對象使用內存狀況.

包含其餘屬性佔用內存狀況

建立一個 LBPerson 類,繼承與 NSObject , 其包含三個 int 屬性

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface LBPerson : NSObject
@property (nonatomic,assign) int age;
@property (nonatomic,assign) int height;
@property (nonatomic,assign) int row;
@end
複製代碼

回到 main 函數

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
#import "LBPerson.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LBPerson * obj = [[LBPerson alloc] init];
        obj.age = 4;
        obj.height = 5;
        obj.row = 6;
        NSLog(@"lbobjc對象實際須要的內存大小: %zd",class_getInstanceSize([obj class]));
        NSLog(@"lbobjc對象實際分配的內存大小: %zd",malloc_size((__bridge const void *)(obj)));
    }
    return 0;
}
複製代碼

打印結果

iOS-OC對象佔用內存探索[3012:201559] lbobjc對象實際須要的內存大小: 24

iOS-OC對象佔用內存探索[3012:201559] lbobjc對象實際分配的內存大小: 32

lldb查看內存

這裏就出現一個比較奇怪的現象 , 實際須要內存大小 24 , 爲何呢 ? 其實這裏就是 結構體內存分配的原理 了.

  • 結構體每一個成員相對於結構體首地址的偏移量都是這個成員大小的整數倍,若是有須要,編譯器會在成員之間加上填充字節

  • 結構體的總大小爲結構體最寬成員大小的整數倍。

  • 結構體變量的首地址可以被其最寬基本類型成員的大小所整除。

  • 對於結構體成員屬性中包含結構體變量的複合型結構體,在肯定最寬基本類型成員時,應當包括複合類型成員的子成員。但在肯定複合類型成員的偏移位置時則是將複合類型做爲總體看待。

因爲本來結構體 isa 指針佔用8個 , age 屬性佔用4個, height 佔用 4個, row 屬性再佔用4個 , 這中間因爲知足整除並無自動偏移補充. 而因爲 : 結構體的總大小爲結構體最寬成員大小的整數倍 , 並且對線開闢知足 16 字節對齊原則 ( 能夠在 libmaclloc 源碼查找到 ) , 所以實際總佔用內存爲24. 而實際開闢則知足對齊標準開闢爲 32.

下圖爲 libmaclloc 源碼 , nano_malloc.c

繼續將 部分 int 類型修改成 double. 你會發現新的內容,篇幅緣由再也不講述. 直接上結果

  • age: int , height : double , row :int

    重點圖

  • age: int , height : Double , row :Double

總結 (只考慮64位):

  • OC對象 最少佔用 16 個字節內存 .
  • 當對象中包含屬性, 會按屬性佔用內存開闢空間. 在結構體內存分配原則下自動偏移和補齊 .
  • 對象最終知足 16 字節對齊標準 .
  • 屬性最終知足 8 字節對齊標準 .
  • 能夠經過 #pragma pack() 自定義對齊方式 .
相關文章
相關標籤/搜索