类似枪械有手动步枪-M1903春田,半自动步枪-M1,自动步枪-M16之分,程序语言的内存管理也有手动、半自动和全自动三种方式。手动内存管理的代表语言:C、C++,半自动内存管理的代码语言:Modern C++,Swift,Rust,全自动的代表语言:Java,C#,Go,Python。本文通过不同程序语言来描述三种内存管理方式,以及如何应对循环引用的典型场景。

手动内存管理:

为了实现代码的极致效率以及灵活性C和C++语言采用了手动内存管理方式,通过自己编写代码实现堆上的内存分配与回收。

//C++代码示例
export class Manual {
public:
  Manual(int s) :elem{ new double[s] }, sz{ s } {}

  ~Manual() {
    delete[] elem;
  }

private:
  double* elem;
  int sz;
};

半自动内存管理:

内存泄漏是手动内存管理遇到最大的挑战,在复杂的场景下可能出现分配的内存没有释放,导致程序在特殊的情况下内存耗尽停止工作。为了保证内存安全,新一代语言为了不牺牲效率采用编译时期来分析内存的分配与释放,典型的方式是自动引用计数(Automatic Reference Counting )

//swift代码示例
class ARC {
    
}

func usingARC() {
    var ref1: ARC? = ARC()
    var ref2: ARC? = ref1

    ref1 = nil
    ref2 = nil
    //ref1、ref2 已释放
}
//Modern C++
export class SmartPointers {
public:
  //represents unique ownership (its destructor destroys its object)
  void uniquePtr() {
    std::unique_ptr<X> sp{ new X };
    std::unique_ptr<X> sp3 = std::make_unique<X>();

    std::unique_ptr<X> sp2 = std::move(sp);
  }

  //represents shared ownership (the last shared pointer¡¯s destructor destroys the object)
  void sharedPtr() {
    std::shared_ptr<X> sp = std::make_shared<X>();
    std::shared_ptr<X> sp2 = sp;

    sp.reset();
    sp2.reset();
  }

  //A pointer to an object owned by a shared_ptr
  void weakPtr() {
    std::shared_ptr<X> sharedPtr = std::make_shared<X>();

    // Creating a weak pointer from the shared pointer
    std::weak_ptr<X> weakPtr = sharedPtr;

    // Using the weak pointer to access the object
    if (auto ptr = weakPtr.lock()) {
    }
    else {
      std::cout << "Weak pointer is expired." << std::endl;
    }

    // Resetting the shared pointer
    sharedPtr.reset();

    // Using the weak pointer again after resetting the shared pointer
    if (auto ptr = weakPtr.lock()) {
    }
    else {
      std::cout << "Weak pointer is expired." << std::endl;
    }
  }
};

全自动内存管理:

同样保证内存安全,同时减少内存管理心智负担,还有一种全自动内存管理-垃圾回收机制(GC)。但这种方式引入了臭名昭著的STOP THE WORLD。

//Go代码示例
type Automate struct {
}

func usingGC() {
  a := Automate{}
  fmt.Println(a)
}

循环引用问题:

两个类的成员相互引用对方,导致内存分配永远无法释放。

class A {
    var b: B?
}

class B {
    var a: A?
}

func rc() {
    var ca: A? = A()
    var cb: B? = B()
    ca!.b = cb
    cb!.a = ca
    
    ca = nil
    cb = nil
    //ca、cb并未释放
}

该场景其实是半自动内存管理引入的问题,因为手动内存管理不管有没有循环引用只要代码进行delete就会释放内存,而全自动内存管理会自己分析出循环引用在GC的时候释放掉相应的内存。

半自动内存管理这方面加重了程序员的心智负担,需要开发者分析出循环引用场景,并采用弱引用的方式打破循环引用。

//通过引入weak弱引用关键字来解决循环引用问题
class Person {
    var apartment: Apartment?
    deinit { print("Person deinit") }
}

class Apartment {
    weak var tenant: Person?
    deinit { print("Apartment deinit") }
}

func weakReference() {
    var tom: Person? = Person()
    var apartment: Apartment? = Apartment()
    
    tom?.apartment = apartment
    apartment?.tenant = tom
    
    tom = nil
    //Person deinit
    
    apartment = nil
    //Apartment deinit
}