LoginSignup
35
21

More than 5 years have passed since last update.

Unity コンポーネントがDestroyされても、オブジェクトがGCで回収されないかもしれない話

Last updated at Posted at 2018-11-28

はじめに

まずはこちらの Unityのnullはnullじゃないかもしれない を読んでから、この記事をお読みください。

起きる現象

「コンポーネントが別のコンポーネントを参照しており、その参照先のコンポーネントがDestroy()されたときにリークが発生する可能性がある。」

どういうこと?

下準備

例えば適当なクラスがあったとして

public class Apple
{
}

このオブジェクトをフィールドに持つコンポーネント(被参照側)があり

using UnityEngine;

public class DeathObject : MonoBehaviour
{
    /// <summary>
    /// Appleはこのコンポーネントに紐づく適当なオブジェクトのつもり
    /// </summary>
    private Apple Apple = new Apple();

    void Start()
    {
        Destroy(gameObject, 3.0f); //3秒後にGameObjectを削除
    }

    void OnDestroy()
    {
        Debug.Log("OnDestroy");
    }
}

このコンポーネントをフィールドに持つコンポーネントがあったとする。

using UnityEngine;

/// <summary>
/// おくりびと
/// </summary>
public class Okuribito : MonoBehaviour
{
    /// <summary>
    /// DeathObjectをフィールドに持つ
    /// </summary>
    [SerializeField] private DeathObject _deathObject;
}

挙動

DeathObjectがDestroy()されても、Appleのインスタンスが残り続けてしまう。

なぜ?

まず、クラスの参照関係はこうなります。

Okuribito → DeathObject → Apple

ここでDeathObjectがDestroyされるとどうなるかですが、DeathObject見かけ上はnullになります。

ですが、OkuribitoDeathObjectを参照したままなので、参照カウントは残っている状態になります。
つまりガベージコレクタ的にはDeathObjectはまだ生きている判定になります。
そのため、DeathObjectに対してアクセスするとnullを返すのに、実はインスタンスへの参照は残っているという状況になってしまいます。

Okuribito → DeathObject(Destroyはしたけどインスタンスは残ってる) → Apple

すなわち、Appleがリークした原因はDeathObjectもリークしているからです。
OkuribitoがDestroyされない限り、DeathObjectが解放されず、連動してAppleも解放されない、という状態になってしまいます。

これ問題になるの?

結局はOkuribitoがDestroyされたら、次のGCで全部まとめて回収されることになります。
なので実際はリークにはなりません。

ただ「次のGCで回収されるだろうな」と思っていたオブジェクトがシーン遷移のタイミングまでメモリに積まれぱなしだった、ということは起きるかも。

対処療法

気になるなら、OnDestroy()時にフィールドで持っているオブジェクトへの参照を外してあげればいいと思います。
対象へ参照している部分をnullで上書きしてあげればOKです。

ただ、null代入するよりかは、IDisposable.Dispose()を呼び出すとかそっちの方が重要だと思います。というかDispose()は使い終わったら必ず呼べ。

あとは、フィールドにIEnumerable<T>を持っていたりした場合は、そこに入ってるオブジェクトもずっと保持されたまま残ってしまいます。こういったものは明示的にnullを代入するなり、ListならClear()するなりした方が良いです。

using UnityEngine;

public class DeathObject : MonoBehaviour
{
    /// <summary>
    /// Appleはこのコンポーネントに紐づく適当なオブジェクトのつもり
    /// </summary>
    private Apple Apple = new Apple();

    void Start()
    {
        Destroy(gameObject, 3.0f); //3秒後にGameObjectを削除
    }

    void OnDestroy()
    {
        Apple = null; // Destroyのタイミングで参照を外す
    }
}

所感

Destroy()したからといって、リソースの解放がただちに完了するわけではないという点に注意しないとダメそう。

35
21
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
21