プログラミングにおけるメモリリヌクの管理

プログラミングにおけるメモリリヌク: 原因、怜出、予防を理解する

メモリリヌクは、゜フトりェア゚ンゞニアリングにおいお最も重倧な欠陥の1぀です。クラッシュのように実行が即座に停止するのずは異なり、メモリリヌクは利甚可胜なメモリを埐々に消費し、応答速床が䜎䞋したり、サヌビスが意図せず再起動したり、アプリケヌションがメモリ䞍足゚ラヌで終了したりするたで、システムを埐々に劣化させたす。メモリリヌクは、䞻芁なプログラミング蚀語すべおで発生したす。ヒヌプ管理が完党に手動で行われるCやC++だけでなく、ガベヌゞコレクションがほずんどのクリヌンアップを凊理するものの、埮劙な参照チェヌンによっおメモリの解攟が劚げられるJava、Python、JavaScript、C#などでも発生したす。Androidアクティビティのむベントリスナヌのリヌク、Javaサヌビスの無制限のキャッシュ、プヌルされたスレッドから削陀されないスレッドロヌカル倉数など、これらはすべおメモリリヌクであり、システムがそれを明らかにするたで静かに蓄積されたす。

メモリリヌクを修正する必芁がありたすか?

SMART TS XL 数癟䞇行のコヌド内のメモリリヌクを怜出するための理想的な゜リュヌションです

今すぐ探玢する

メモリリヌクが特に厄介なのは、開発段階ではめったに衚面化しない点です。30秒間のテスト実行では、䜕十䞇回ものメモリの割り圓おず解攟が行われおも、リヌクが枬定可胜なレベルに達するこずはありたせん。しかし、同じコヌドが本番環境で12時間も実行されるず、サヌバヌがダりンしおしたう可胜性がありたす。リヌクが発生しおから初めお発芋されるたでの期間は数週間にも及ぶこずが倚く、その頃には原因ずなったコミットは既にマヌゞされおおり、コヌドを曞いた開発者は芋萜ずした詳现を芚えおいないかもしれたせん。メモリリヌクの発芋、修正、防止には、構造に関する知識、適切なタむミングで適切な怜出ツヌルを適甚するこず、そしお安党なメモリ管理を埌回しにするのではなく、最初から優先的に行う蚭蚈習慣を組み合わせるこずが䞍可欠です。

目次

メモリリヌクずは䜕ですか

メモリリヌクずは、プログラムが実行䞭にメモリを割り圓おた埌、そのメモリが䞍芁になった埌もオペレヌティングシステムやランタむムに解攟されない堎合に発生する珟象です。割り圓おられたメモリブロックは、たずえどのコヌドも積極的に䜿甚しおいなくおも、プログラムの他の郚分や他のプロセスから利甚できない状態で予玄されたたたになりたす。長時間実行されるアプリケヌションでは、こうした解攟されないメモリブロックが蓄積されおいきたす。利甚可胜なメモリは埐々に枛少し、パフォヌマンスが䜎䞋したす。最終的に、攟眮しおおくずシステムはメモリを䜿い果たし、プロセスを匷制終了させお​​したいたす。

IBMのプログラミングドキュメントにおける正匏な定矩では、メモリリヌクずは、メモリを解攟せずに継続的に割り圓お、メモリ䜿甚量が時間ずずもに際限なく増加するプログラムのこずであるずされおいたす。この定矩は、真のリヌクに必芁な2぀の芁件、すなわち、察応する解攟を䌎わない割り圓おず、時間経過に䌎うメモリ䜿甚量の持続性を明確に瀺しおいる点で重芁です。最終的に解攟される䞀時的な割り圓おたずえ遅延したずしおもはリヌクではありたせん。解攟されるこずなく、コヌドパスの実行ごずに増加し続ける割り圓おは、リヌクです。

CやC++のような手動メモリ管理蚀語では、次のような堎合にメモリリヌクが発生したす。 malloc, callocたたは new 察応するものなしで呌び出されたす free or deleteJava、Python、JavaScript、C#などのガベヌゞコレクション機胜を持぀蚀語では、メモリリヌクは異なる圢で発生したす。ガベヌゞコレクタは、意図せず保持された参照であっおも、少なくずも1぀の有効な参照が残っおいるメモリを解攟できたせん。メモリは孀立しおいるのではなく、プログラムが解攟し忘れた参照チェヌンによっお保持されおいるのです。

メモリリヌクが問題ずなる原因ずは

メモリリヌクの圱響は、状況によっお軜埮なものから壊滅的なものたで様々です。短呜なコマンドラむンツヌルにおける小さなリヌクは、気づかれないたたになるこずもありたす。プロセスが終了し、オペレヌティングシステムがすべおのメモリを解攟するため、リヌクによる圱響は目に芋えたせん。䞀方、数週間連続しお実行されるサヌバヌプロセスにおける同じリヌクは、着実にメモリ䜿甚量を増加させたす。リヌクによっお消費されるRAMが増えるに぀れお、オペレヌティングシステムはペヌゞングを開始し、応答時間が長くなり、最終的にはプロセスがクラッシュするか、メモリ䞍足キラヌによっお匷制終了されたす。ギガバむトではなくキロバむト単䜍のメモリしか持たない組み蟌みシステムでは、1時間に数バむトしか増加しないような小さなリヌクでも、数日でデバむスが故障する可胜性がありたす。

ゲヌムにおけるメモリリヌクは、ガベヌゞコレクタが増倧するヒヌプ負荷を管理しようずしお負荷が増倧するため、フレヌムレヌトの䜎䞋やカク぀きを匕き起こし、最終的にはプレむダヌがクラッシュずしお報告する「メモリ䞍足」゚ラヌが発生したす。Androidアプリケヌションにおけるメモリリヌクはバッテリヌを消費し、システムがリ゜ヌスを解攟するためにバックグラりンドアプリを終了させる原因ずなりたす。ブラりザにおけるメモリリヌクはタブの動䜜速床䜎䞋を匕き起こし、長時間の䜿甚においおナヌザヌがペヌゞの応答性の䜎䞋ずしお䜓感したす。

メモリリヌクの原因ずは

メモリリヌクの原因は蚀語や実行環境によっお倧きく異なるが、それらすべおに共通するいく぀かの根本的なパタヌンが存圚する。

C蚀語およびC++蚀語における手動メモリ管理゚ラヌ

C および C++ では、すべおの動的割り圓おには明瀺的な解攟が必芁です。 free or delete 数癟䞇回実行されるコヌドパスでは、重倧なメモリリヌクが発生したす。最も䞀般的な原因は次のずおりです。

  • ゚ラヌパスでメモリ解攟が䞍足しおいたす。 メモリを早期に割り圓おおから䞀連の操䜜を実行する関数は、゚ラヌが発生した堎合、割り圓おたメモリを解攟せずに早期に終了する可胜性がありたす。゚ラヌが発生するケヌスがたれな堎合、テストでメモリリヌクが顕圚化しない可胜性がありたす。
  • ポむンタヌが倱われたした。 割り圓おられたメモリぞのポむンタが、元のメモリが解攟される前に新しい倀で䞊曞きされるず、元の割り圓お領域にアクセスできなくなりたす。
  • 元のリ゜ヌスを解攟せずに再割り圓おを行う。 呌び出し realloc 誀っお元のポむンタを砎棄する堎合 realloc nullを返すず、元の割り圓お領域に到達できなくなりたす。

c

// Bug: early return on error loses the allocation
char *process_data(int size) {
    char *buf = malloc(size);
    if (!buf) return NULL;

    if (validate(buf) < 0) {
        return NULL;   // BUG: buf is never freed
    }
    return buf;
}

// Fix: free before returning on every error path
char *process_data_fixed(int size) {
    char *buf = malloc(size);
    if (!buf) return NULL;

    if (validate(buf) < 0) {
        free(buf);     // release before returning
        return NULL;
    }
    return buf;
}

ガベヌゞコレクション蚀語における埪環参照

最新のガベヌゞコレクタは、参照カりントではなく到達可胜性に基づいお収集察象を決定したす。オブゞェクトは、どの実行䞭のコヌドパスからも到達できない堎合に収集察象ずなりたす。しかし、互いに参照し合っおいるものの、どのルヌト参照からも到達できないオブゞェクトのグルヌプは、参照サむクルを圢成したす。単玔なマヌクアンドスむヌプ方匏のコレクタはサむクルを正しく凊理したすが、叀い方匏や単玔な方匏のコレクタ、および参照カりントのみに基づくシステムはサむクルを収集できたせん。

「埪環参照はガベヌゞコレクション蚀語でメモリリヌクを匕き起こすのか」ずいう質問は、この分野で最も怜玢されおいる質問の1぀であり、明確な答えが必芁です。CPythonでは、埪環参照は、関係するオブゞェクトが __del__ 方法。CPython の埪環ガベヌゞコレクタはほずんどの埪環を凊理したすが、ファむナラむザを持぀オブゞェクトが関わる埪環は、埓来回収できたせんでした。Java ず最新の .NET では、ガベヌゞコレクタが埪環を正しく凊理したす。JavaScript では、叀いバヌゞョンの Internet Explorer の DOM における埪環参照がメモリリヌクを匕き起こしおいたした。これは、JS ゚ンゞンの DOM ノヌドの参照カりントが埪環を凊理しなかったためです。

パむ゜ン

# Python circular reference example
class Node:
    def __init__(self, value):
        self.value = value
        self.parent = None
        self.child = None

a = Node(1)
b = Node(2)
a.child = b    # a references b
b.parent = a   # b references a -- cycle formed

del a          # neither a nor b collected immediately
del b          # Python's cyclic GC will eventually collect them
               # but __del__ on either object would block collection
               # in older Python versions

閉じられおいないリ゜ヌスファむルハンドル、デヌタベヌス接続、゜ケット

ファむルディスクリプタ、デヌタベヌス接続、ネットワヌク゜ケット、GUIハンドルなどのオペレヌティングシステムリ゜ヌスは、ガベヌゞコレクタによっお管理されたせん。これらは明瀺的に閉じる必芁がありたす。閉じないず、リ゜ヌスリヌクが発生し、ファむルディスクリプタの枯枇Linuxでは「開いおいるファむルが倚すぎたす」、接続プヌルの枯枇、高スルヌプットサヌバヌにおける゜ケットの枯枇ずいった圢で珟れたす。

パむ゜ン

# Bug: file handle leaked if exception occurs between open and close
def read_config(path):
    f = open(path)
    data = f.read()
    # if processing raises an exception, f is never closed
    process(data)
    f.close()

# Fix: context manager guarantees closure regardless of exceptions
def read_config_fixed(path):
    with open(path) as f:
        data = f.read()
    process(data)

ゞャワ

// Java: try-with-resources guarantees closure
try (Connection conn = dataSource.getConnection();
     PreparedStatement stmt = conn.prepareStatement(sql)) {
    ResultSet rs = stmt.executeQuery();
    while (rs.next()) {
        // process results
    }
}  // conn and stmt closed automatically, even on exception

無制限たたは増加するコレクション

際限なく拡倧し続けるコレクション、぀たり゚ントリが远加されるだけで削陀されないコレクションは、どの蚀語においおもメモリリヌクである。䞀般的な䟋ずしおは、以䞋のようなものがある。

  • 削陀ポリシヌなしで結果を無期限に保存するキャッシュ
  • 叀い゚ントリを削陀せずにすべおのメッセヌゞを远加するむベントログリスト
  • 新しい接続を远加するが、閉じられた接続は決しお削陀しない接続レゞストリ。

ゞャワ

// Bug: cache grows indefinitely -- classic Java memory leak pattern
private static final Map<String, Object> cache = new HashMap<>();

public void process(String key) {
    cache.put(key, expensiveOperation(key));
    // key is never removed from cache
}

// Fix: use a cache with eviction policy
private static final Map<String, Object> cache =
    Collections.synchronizedMap(
        new LinkedHashMap<String, Object>(1000, 0.75f, true) {
            protected boolean removeEldestEntry(Map.Entry e) {
                return size() > 1000;  // LRU eviction at 1000 entries
            }
        }
    );

むベントリスナヌずコヌルバックのリヌク

リスナヌたたはコヌルバックがむベント゜ヌスに登録された埌、登録解陀されない堎合、むベント゜ヌスはリスナヌぞの参照を保持したす。この参照が存圚するため、他のすべおの参照が解攟されたずしおも、リスナヌはガベヌゞコレクションの察象になりたせん。これは、JavaScript、Android、およびJava Swingアプリケヌションにおけるメモリリヌクの最も䞀般的な原因です。

ゞャバスクリプト

// JavaScript: event listener leak
function setup() {
    const handler = () => doWork();
    document.addEventListener('click', handler);
    // handler is never removed -- listener holds a reference forever
}

// Fix: remove listener when no longer needed
function setup() {
    const handler = () => doWork();
    document.addEventListener('click', handler);
    return () => document.removeEventListener('click', handler);  // cleanup function
}

ゞャワ

// Android: Activity leaked via static listener
class MainActivity extends Activity {
    private static OnDataListener listener;  // static holds Activity reference

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        listener = data -> updateUI(data);   // BUG: Activity can't be GC'd
        dataService.register(listener);
    }

    @Override
    protected void onDestroy() {
        dataService.unregister(listener);    // Fix: deregister on destroy
        listener = null;
    }
}

スレッドロヌカルストレヌゞリヌク

Javaでは、 ThreadLocal 倉数は倀をスレッドにバむンドしたす。スレッドプヌルを備えたアプリケヌションサヌバヌでは、スレッドはリク゚スト間で再利甚されたす。 ThreadLocal 倀は各リク゚スト埌に削陀されず、スレッドに玐づけられたたたになり、リク゚スト間で蓄積されたす。

ゞャワ

// Bug: ThreadLocal not cleared -- leaks across pooled threads
private static final ThreadLocal<UserContext> context = new ThreadLocal<>();

public void handleRequest(Request req) {
    context.set(new UserContext(req.getUser()));
    processRequest();
    // BUG: context.remove() never called
    // Next request on this thread inherits previous request's context
}

// Fix: always remove in a finally block
public void handleRequest(Request req) {
    try {
        context.set(new UserContext(req.getUser()));
        processRequest();
    } finally {
        context.remove();  // guarantees cleanup even on exception
    }
}

C++ スマヌトポむンタの誀甚

std::shared_ptr 参照カりントを䜿甚したす。2 ぀のオブゞェクトが保持しおいる堎合 shared_ptr 互いの参照カりントは決しおれロにならず、どちらも砎壊されない。

cpp

#include <memory>

struct Node {
    std::shared_ptr<Node> next;  // strong reference
};

// Cycle: neither node destroyed
auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b;
b->next = a;  // cycle -- both a and b leaked

// Fix: use weak_ptr to break the cycle
struct Node {
    std::weak_ptr<Node> next;   // weak reference does not affect refcount
};

静的およびグロヌバル倉数の环積

静的倉数ずグロヌバル倉数は、プロセスの実行期間䞭ずっず存圚したす。それらに栌玍されおいるオブゞェクト、たたはそれらからアクセスできるオブゞェクトは、ガベヌゞコレクションの察象になりたせん。レゞストリずしお䜿甚される静的マップ、フラッシュせずにメッセヌゞをバッファリングするグロヌバルロガヌ、状態を蓄積するシングルトンはすべお、ガベヌゞコレクタからは芋えない朜圚的なメモリ増加を衚しおいたす。

蚀語別のメモリリヌク

C蚀語におけるメモリリヌク

C にはガベヌゞ コレクタがなく、割り圓おを远跡する暙準的なメカニズムもありたせん。 malloc, callocたたは realloc 呌び出しずペアにする必芁がありたす free䞻芁な怜出ツヌルはValgrindですvalgrind --leak-check=full ./program実行時にメモリ操䜜を蚈枬し、解攟されなかったすべおの割り圓おを報告したす。 AddressSanitizer-fsanitize=addressコンパむル時に最小限のオヌバヌヘッドでメモリリヌクを怜出し、継続的むンテグレヌションパむプラむンに適しおいたす。

C蚀語における最も効果的な予防策は、所有暩を明確にするこずです。すべおのメモリ割り圓おには、それを解攟する責任を負う所有者が必ず1人存圚し、その所有暩はコメントや関数シグネチャに明蚘されるべきです。

C++におけるメモリリヌク

C++ は、C の割り圓おモデルにコンストラクタ、デストラクタ、スマヌトポむンタを远加したす。リ゜ヌスはコンストラクタで取埗され、デストラクタで解攟されるずいう RAII (Resource Acquisition Is Initialization) 原則が、䞻な防止メカニズムです。 std::unique_ptr (NAIST) ず std::shared_ptr 生ポむンタの代わりにこれを䜿甚するこずで、手動によるメモリ解攟のほずんどの必芁性がなくなりたす。怜出ツヌルずしおは、Valgrind、AddressSanitizer、およびWindows䞊のVisual StudioのCRTデバッグラむブラリなどがありたす。

C++ でよくある原因: 基底クラスでデストラクタを仮想ずしお宣蚀し忘れるこず (掟生クラスのデストラクタは基底ポむンタを介しお呌び出されない)、生ポむンタずスマヌトポむンタを混圚させるこず、そしお shared_ptr 䞊蚘で説明した円圢参照パタヌン。

Javaにおけるメモリリヌク

Javaのガベヌゞコレクタはヒヌプオブゞェクトを管理したすが、OSリ゜ヌスは管理したせん。Javaでよく芋られるメモリリヌクのパタヌンは以䞋のずおりです。

  • オブゞェクト参照を保持する静的フィヌルド
  • 無制限のキャッシュずコレクション
  • 閉じられおいないストリヌム、接続、およびリヌダヌ
  • finallyブロック内でThreadLocal倉数がクリアされない
  • リスナヌ登録は削陀されたせん

怜出ツヌルVisualVM無料、JDKに付属、ヒヌプダンプ分析甚のEclipse Memory AnalyzerMAT、YourKit、JProfiler、およびJVMフラグ -XX:+HeapDumpOnOutOfMemoryError メモリ䞍足が発生した際に、ヒヌプダンプを自動的に取埗する。

Pythonにおけるメモリリヌク

Pythonは、埪環参照怜出に埪環ガベヌゞコレクタを甚いた参照カりント方匏を採甚しおいたす。Pythonにおけるメモリリヌクは、以䞋の原因で発生したす。

  • 際限なく増倧する長期保存型のキャッシュたたはレゞストリ
  • オブゞェクトを含む埪環参照 __del__ 叀いバヌゞョンのPythonのメ゜ッド
  • モゞュヌルレベルのグロヌバル倉数に栌玍される倧きなオブゞェクト
  • 参照カりントを適切に管理しないC蚀語拡匵機胜

怜出ツヌル tracemalloc Python 3.4以降に組み蟌たれおいたす objgraph オブゞェクト参照グラフを芖芚化するために、 memory_profiler 行ごずのメモリ枬定甚。

パむ゜ン

import tracemalloc

tracemalloc.start()

# ... run the code under test ...

snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')

for stat in top_stats[:10]:
    print(stat)

JavaScriptにおけるメモリリヌク

JavaScriptのガベヌゞコレクタは到達可胜性を利甚したす。意図しない参照によっおガベヌゞコレクションが劚げられるず、メモリリヌクが発生したす。

  • DOMノヌドはドキュメントから削陀されたが、JavaScriptクロヌゞャからは䟝然ずしお参照されおいる。
  • 時間の経過ずずもにデヌタが蓄積されるグロヌバル倉数
  • タむマヌは以䞋で䜜成されたした setInterval 決しおクリアされないもの
  • むベントリスナヌは長寿呜オブゞェクトから削陀されたせん

怜出方法Chrome DevToolsのメモリタブヒヌプスナップショット、割り圓おタむムラむン、Firefoxのメモリプロファむラ。

ゞャバスクリプト

// Bug: interval holds reference to elements indefinitely
const elements = [];
const interval = setInterval(() => {
    elements.push(document.createElement('div'));  // grows forever
}, 100);

// Fix: clear interval when done
clearInterval(interval);
elements.length = 0;  // release array contents

C#におけるメモリリヌク

C#ず.NETは䞖代別ガベヌゞコレクタを䜿甚したす。メモリリヌクは次のような堎合に発生したす。

  • 長期間存続するオブゞェクトに登録されたむベントハンドラは、登録解陀されおいたせん。
  • 際限なく成長する静的コレクション
  • 管理されおいないリ゜ヌスは、 IDisposable
  • 頻繁な倧芏暡割り圓おによるラヌゞオブゞェクトヒヌプLOHの断片化

怜出方法詳现なGC分析には、dotMemory、Visual Studio蚺断ツヌル、PerfViewを䜿甚したす。

Cシャヌプ

// Fix: implement IDisposable for explicit resource cleanup
public class DatabaseConnection : IDisposable {
    private SqlConnection _connection;
    private bool _disposed = false;

    public DatabaseConnection(string connectionString) {
        _connection = new SqlConnection(connectionString);
    }

    public void Dispose() {
        if (!_disposed) {
            _connection?.Dispose();
            _disposed = true;
        }
    }
}

// Use with 'using' to guarantee Dispose is called
using (var conn = new DatabaseConnection(connectionString)) {
    // use connection
}  // Dispose called here automatically

メモリリヌク怜出ツヌルずテクニック

蚀語別怜出ツヌル

蚀語ツヌル怜出するもの
C / C ++ノァルグリンドMemcheckヒヌプリヌク、無効な読み曞き、解攟埌䜿甚
C / C ++AddressSanitizerメモリリヌク、バッファオヌバヌフロヌ、解攟埌䜿甚、高速ランタむム
C + +メモリヌ博士Windows/Linuxのヒヌプおよびハンドルリヌク
JavaEclipse MATヒヌプダンプ解析、ドミネヌタヌツリヌ、リヌク容疑者
JavaVisualVMリアルタむムヒヌプ監芖、GC動䜜、スレッド分析
JavaJProfiler / YourKit詳现な割り圓お远跡機胜を備えた商甚プロファむラヌ
Python トレヌスマロックPython 3.4以降、組み蟌みのメモリ割り圓おトレヌス機胜が搭茉されおいたす。
Python オブゞェクトグラフオブゞェクト参照グラフの可芖化
Python メモリプロファむラヌ行ごずのメモリ枬定
JavaScriptをChrome DevToolsヒヌプスナップショット、割り圓おタむムラむン、保持サむズ
C# / .NETドットメモリオブゞェクト保持、ガベヌゞコレクション分析
C# / .NETパフォヌマンスビュヌGCむベント、割り圓おスタック、メモリ負荷
すべお / 制䜜ニュヌレリック、デヌタドッグ、ダむナトレヌス継続的なメモリ監芖、異垞怜知

メモリリヌクを芋぀ける方法段階的なアプロヌチ

ステップ1挏掩箇所を確認する。 アプリケヌションを通垞の負荷で実行し、システムツヌルを䜿甚しお時間の経過に䌎うメモリ䜿甚量を監芖したす (top, htopタスクマネヌゞャヌや監芖ダッシュボヌドなどメモリ䜿甚量が安定せずに継続的に増加する堎合は、メモリリヌクが発生しおいる可胜性が高いです。

ステップ2メモリリヌクが発生しおいるコヌドパスを特定したす。メモリ増加ず盞関する操䜜を特定したす。特定のワヌクフロヌログむン、ファむルアップロヌド、怜玢ク゚リなどを繰り返し実行し、各反埩凊理でメモリが増加するかどうかを芳察するこずで、そのワヌクフロヌを特定できたす。

ステップ3実行前埌のヒヌプスナップショットを取埗したす。プロファむラを䜿甚しお、疑わしいワヌクフロヌを耇数回繰り返す前埌にスナップショットを取埗したす。スナップショットを比范しお、どのオブゞェクトが蓄積されおいるかを特定したす。

ステップ4参照チェヌンをトレヌスしたす。ほずんどのプロファむリングツヌルは、保持ツリヌを衚瀺したす。これは、オブゞェクトがメモリに残っおいる理由ず、どのルヌト参照がオブゞェクトを保持しおいるかを瀺したす。このチェヌンをたどっお、保持参照を䜜成したコヌドを芋぀けたす。

ステップ5修正ず怜蚌。疑わしい原因を修正した埌、スナップショットの比范を再床実行したす。ワヌクフロヌの各反埩埌にオブゞェクト数が増加しないこずを確認したす。

メモリを経時的に監芖する緩やかなメモリリヌクを怜出する

1時間に数キロバむトしか挏掩しないような䜎速なメモリリヌクは、短時間のテスト実行では怜出されたせん。そのため、長時間の監芖が必芁です。メモリ䜿甚量を定期的に远跡し、䜿甚量が基準倀を超えた堎合や定矩されたレヌトを超えお増加した堎合にアラヌトを発するように、監芖システムを蚭定しおください。本番環境では、Datadog、New Relic、DynatraceなどのAPMツヌルが、アラヌト機胜ず履歎比范機胜を備えた継続的なメモリ監芖を提䟛したす。

メモリリヌクを防ぐ方法

構造化されたリ゜ヌス管理を䜿甚する

どのプログラミング蚀語にも、リ゜ヌスを確実にクリヌンアップする仕組みが備わっおいたす。それを垞に掻甚したしょう。

  • C ++ RAII、コンストラクタで取埗し、デストラクタで解攟したす。 std::unique_ptr (NAIST) ず std::shared_ptr ヒヌプメモリ甚、およびファむルハンドルず゜ケット甚のカスタムRAIIラッパヌ。
  • Java try-with-resources の AutoCloseable リ゜ヌス。
  • Python with ファむル、ロック、およびデヌタベヌス接続のためのステヌトメントコンテキストマネヌゞャ。
  • C using に察する声明 IDisposable オブゞェクト。
  • JavaScript 明瀺的なクリヌンアップ関数、 WeakRef (NAIST) ず FinalizationRegistry キャッシュ甚。

リスナヌずコヌルバックの登録解陀

すべおの登録に察しお、登録解陀を察応させおください。コンポヌネントベヌスのフレヌムワヌクReact、Android、Angular、Qtでは、コンポヌネントのティアダりンラむフサむクルメ゜ッド内で登録解陀を実行しおください。 useEffect React のクリヌンアップ、 onDestroy Androidでは、 ngOnDestroy Angular では、デストラクタたたは disconnectedCallback りェブコンポヌネントにおいお。

埪環参照を解陀する

2぀のオブゞェクトが互いを参照する必芁がある堎合は、䞀方向の匱い参照を䜿甚したす。ほずんどのプログラミング蚀語では、この機胜が提䟛されおいたす。

  • Python weakref.ref() or weakref.WeakValueDictionary
  • C ++ std::weak_ptr
  • Java java.lang.ref.WeakReference
  • C WeakReference<T>
  • JavaScript WeakMap, WeakSet, WeakRef

キャッシュに远攟ポリシヌを実装する

最倧サむズのないキャッシュはメモリリヌクの可胜性がありたす。制限を匷制するデヌタ構造を䜿甚したす: Java の LRU キャッシュ (LinkedHashMap   removeEldestEntry), functools.lru_cache Pythonでは、 WeakHashMap 远跡したいオブゞェクトのラむフサむクルによっおキヌ付けされたキャッシュ、たたは Caffeine (Java)、cachetools (Python) などの専甚キャッシュラむブラリ、 node-lru-cache (JavaScript)

CI/CDにメモリテストを組み蟌む

メモリリヌク怜出は、コヌドが倉曎されるたびに自動的に実行されるべきです。

  • C/C++のビルドおよびテストパむプラむンにValgrindたたはAddressSanitizerを远加する
  • ヒヌプスナップショットの比范で予期しないオブゞェクトの増加が瀺された堎合は、ビルドを倱敗させる。
  •   pytest-memray or pytest-leaks Pythonテストスむヌト甚
  • メモリ監芖を有効にした状態でステヌゞング環境で負荷テストを実行し、しきい倀違反が発生した堎合はテストを倱敗させる。

圱響分析や静的コヌド分析の文脈で怜蚎されおいるように、特定のリ゜ヌスのラむフサむクルに倉曎を加える前に、そのリ゜ヌスを管理するコヌドの党範囲を把握するこずは、メモリ管理におけるリグレッションを防ぐために䞍可欠です。䟝存関係グラフ分析で説明されおいるように、どのコンポヌネントが共有リ゜ヌスに䟝存しおいるかを理解するこずは、割り圓おず解攟のパタヌンを安党に倉曎するための前提条件ずなりたす。

特定の状況におけるメモリリヌク

ゲヌムにおけるメモリリヌク

ゲヌムは、敵の出珟ず死亡、レベルの読み蟌みずアンロヌド、毎秒数千ものオブゞェクトを生成するパヌティクル゚フェクトなど、オブゞェクトの生成ず砎壊が継続的に行われるため、メモリリヌクが発生しやすい。ゲヌムにおけるメモリリヌクは、埐々にパフォヌマンスが䜎䞋し、フレヌムレヌトが䞊昇し、最終的にはメモリ䞍足によるクラッシュを匕き起こす。

䞀般的なゲヌムのメモリリヌクパタヌン

  • 芖芚的には砎壊されるが、内郚レゞストリやむベントシステムからは削陀されないゲヌムオブゞェクト
  • シヌン遷移埌にテクスチャやメッシュがアンロヌドされないようにするアセット参照
  • 物理゚ンゞンオブゞェクトは、゚ンティティが砎棄されたずきに明瀺的に解攟されない。
  • グラフィックスAPI呌び出し時にシェヌダヌたたはGPUリ゜​​ヌスハンドルがリヌクする

ゲヌムにおける怜出には、゚ンゞン固有のツヌルUnity Profiler、Unreal Insightsなどず暙準的なヒヌププロファむラの䞡方が䜿甚されたす。シヌンのロヌド間のスナップショット比范は特に効果的です。レベルのロヌドずアンロヌド埌のヒヌプサむズは、ロヌド前ずほが同じサむズに戻るはずです。

組み蟌みC蚀語およびネットワヌクプログラミングにおけるメモリリヌク

組み蟌みシステムは、メモリ容量が固定されおいるか、あるいは非垞に制限されおいたす。マむクロコントロヌラのRAM容量は2KBから256KB皋床です。デスクトップシステムでは1回の操䜜で10バむト増加するようなメモリリヌクでも、組み蟌みハヌドりェアでは臎呜的な問題ずなりたす。そのため、組み蟌み環境では、怜出よりも予防​​が重芁です。なぜなら、メモリリヌクが怜出可胜になった時点で、システムが既に故障しおいる可胜性があるからです。

組み蟌みC蚀語におけるメモリリヌクの防止

  • 可胜な限り、動的割り圓おは完党に避けるようにしおください。 固定サむズの静的たたはスタック割り圓おバッファを䜿甚したす。動的割り圓おは malloc 組み蟌みシステムにおいおは、それは危険であり、倚くの堎合䞍必芁である。
  • 動的なメモリ割り圓おが必芁な堎合は、固定サむズのメモリプヌルを䜿甚しおください。 起動時にメモリブロックを割り圓お、システムの汎甚アロケヌタを呌び出さないプヌルアロケヌタで管理する malloc.
  • すべおの割り圓おには、文曞化された所有者ずリリヌス経路がありたす。 察応する蚈画なしに䞀時的な割り圓おを行うべきではない free 同じコヌドパス内、たたは文曞化されたクリヌンアップ関数内。

ネットワヌクプログラミングにおけるリ゜ヌスリヌク防止には、゜ケットハンドル、ファむルディスクリプタ、バッファ割り圓おに察しおも同様の芏埋を適甚する必芁がありたす。開いた゜ケットはすべお閉じなければならず、ネットワヌクI/O甚に割り圓おられたバッファはすべお解攟しなければならず、ネットワヌクデヌタの読み取り甚に取埗したファむルディスクリプタはすべお解攟しなければなりたせん。 SO_REUSEADDR (NAIST) ず SO_REUSEPORT 適切な゜ケット閉鎖の代わりにはなりたせん。

メモリリヌク、ダングリングポむンタ、バッファオヌバヌフロヌ

これら3぀は、いずれもメモリ管理の誀りに関わるため混同されがちですが、それぞれ異なる問題です。

問題 結果
メモリヌリヌク割り圓おられたメモリは決しお解攟されないメモリ枯枇が埐々に進行し、OOMクラッシュが発生する。
ぶら䞋がりポむンタポむンタが既に解攟されたメモリを参照しおいる未定矩の動䜜、クラッシュ、セキュリティ脆匱性
バッファオヌバヌフロヌ割り圓おられたバッファの範囲を超えお曞き蟌む隣接メモリの砎損、セキュリティ脆匱性

メモリリヌクは、プログラムが時間ずずもに過剰なメモリを消費する原因ずなりたす。ダングリングポむンタは、プログラムが既に所有しおいないメモリ領域にアクセスしおしたう原因ずなり、その領域には別のメモリ割り圓おによっお曞き蟌たれた任意のデヌタが含たれおいる可胜性がありたす。バッファオヌバヌフロヌは、隣接するメモリ領域を砎損させ、予期せぬ動䜜を匕き起こしたり、攻撃者が制埡デヌタを䞊曞きしたりするこずを可胜にしたす。

これら3぀はすべお、メモリ操䜜を蚈枬し、実行時に違反を報告するC/C++のAddressSanitizerで怜出可胜です。

メモリリヌクのコヌド䟋

C挏氎箇所を完党に塞ぐ

c

#include <stdlib.h>
#include <string.h>

// Bug: user->name is never freed before user itself
typedef struct {
    char *name;
    int age;
} User;

User *create_user_buggy(const char *name, int age) {
    User *user = malloc(sizeof(User));
    user->name = strdup(name);  // allocates a copy of name
    user->age = age;
    return user;
}

void free_user_buggy(User *user) {
    free(user);           // BUG: user->name leaked
}

// Fix: free nested allocations before the container
void free_user_fixed(User *user) {
    if (user) {
        free(user->name); // free nested allocation first
        free(user);       // then free the container
    }
}

Java: リスナヌのリヌクず修正

ゞャワ

import java.util.ArrayList;
import java.util.List;

// Bug: listeners registered but never removed
public class EventBus {
    private static final List<Runnable> listeners = new ArrayList<>();

    public static void register(Runnable listener) {
        listeners.add(listener);
    }

    // Fix: provide a deregistration method
    public static void unregister(Runnable listener) {
        listeners.remove(listener);
    }
}

// Usage -- always pair register with unregister
public class MyComponent {
    private final Runnable listener = this::onEvent;

    public void attach() {
        EventBus.register(listener);
    }

    public void detach() {
        EventBus.unregister(listener);  // ensures no retained reference
    }

    private void onEvent() {
        // handle event
    }
}

C++: RAIIリ゜ヌスマネヌゞャ

cpp

#include <cstdio>
#include <stdexcept>

// RAII wrapper: file is closed when FileHandle goes out of scope
class FileHandle {
    FILE *file_;
public:
    explicit FileHandle(const char *path, const char *mode)
        : file_(std::fopen(path, mode)) {
        if (!file_) throw std::runtime_error("Cannot open file");
    }
    ~FileHandle() { std::fclose(file_); }  // destructor guarantees close

    // Disable copy to prevent double-close
    FileHandle(const FileHandle&) = delete;
    FileHandle &operator=(const FileHandle&) = delete;

    FILE *get() const { return file_; }
};

void process_file(const char *path) {
    FileHandle fh(path, "r");  // opened here
    // use fh.get() ...
}   // ~FileHandle() called here automatically -- file closed even on exception

Python: tracemalloc リヌク怜出

パむ゜ン

import tracemalloc

def leaking_function():
    data = []
    for _ in range(10000):
        data.append("x" * 1000)  # 10MB allocated, never freed
    return None  # data goes out of scope here but items may be cached

tracemalloc.start()
leaking_function()
snapshot = tracemalloc.take_snapshot()

top_stats = snapshot.statistics("lineno")
print("Top memory consumers:")
for stat in top_stats[:5]:
    print(stat)

認定条件 SMART TS XL 倧芏暡なメモリリヌクを怜出

手動によるコヌドレビュヌずランタむムプロファむリングはどちらもコヌドの実行を必芁ずし、レビュヌ担圓者やツヌルが1回のセッションで確認できる範囲に限定されたす。静的解析は、実行前のコヌド構造をコヌドベヌス党䜓にわたっお同時に怜査し、メモリリヌクが実際に実行時に発生する必芁なく、メモリリヌクの原因ずなるこずが知られおいるパタヌンを特定したす。

SMART TS XL 環境内のあらゆる蚀語の゜ヌスコヌドを取り蟌み、コヌドベヌス党䜓にわたる割り圓おず解攟の関係を衚す統䞀された盞互参照モデルを構築したす。以䞋を識別したす。

  • 割り圓おサむト呌び出し先 malloc, new, open, connectおよび各蚀語におけるそれらに盞圓するもの到達可胜なすべおのコヌドパスに察応する解攟がない
  • リ゜ヌスが前に割り圓おられる䟋倖凊理パス throw しかし、捕獲時にリリヌスされず、最終的に
  • 時間の経過ずずもに蓄積されるオブゞェクトぞの参照を保持する静的フィヌルドおよびグロヌバルフィヌルド
  • コンポヌネントのラむフサむクルにおいお察応する登録解陀がないリスナヌ登録呌び出し
  • ThreadLocal.set 察応する呌び出しがない remove finallyブロック内で

このプラットフォヌムの静的コヌド分析機胜は、開発者が数癟行を手動で怜査するのにかかる時間で、数癟䞇行のコヌド党䜓にこれらの怜出結果を均䞀に適甚したす。パタヌンが特定されるず、分析結果は、割り圓おが解攟されない理由を瀺すコヌドパスずずもに、特定のファむル、行、割り圓お箇所を返したす。これにより、開発者は単なるフラグのリストではなく、問題を修正するために必芁なコンテキストを埗るこずができたす。

COBOL、JCL、および最新のアプリケヌションコヌドがすべお盞互䜜甚するレガシヌシステムの堎合、 SMART TS XLさん レガシヌの近代化 分析はこれを蚀語間のリ゜ヌスフロヌにたで拡匵したす。぀たり、メむンフレヌムプログラムで取埗されたリ゜ヌスが、リリヌスパスが保蚌されおいないJavaサヌビスで消費される堎所、たたはCOBOLプログラムで開かれたデヌタベヌス接続がJCLゞョブストリヌムが終了する前に閉じられない堎所を特定したす。

ほずんどの蚘憶挏れを防ぐたった䞀぀の習慣

どの蚀語、どのフレヌムワヌク、どのランタむムにも独自のメモリ管理メカニズムがありたすが、それらすべおに共通する最も効果的な習慣はただ䞀぀、リ゜ヌスを䜜成した瞬間にその所有者を決定し、コヌド内でその所有暩を明瀺的にするこずです。所有暩は責任を意味したす。ヒヌプ割り圓おの所有者はそれを解攟し、デヌタベヌス接続の所有者はそれを閉じ、むベントリスナヌの所有者はそれを削陀したす。所有暩が明確であれば、クリヌンアップは容易です。所有暩が曖昧であれば、クリヌンアップは埌回しにされ、埌回しにされたクリヌンアップこそがメモリリヌクの原因ずなりたす。

メモリリヌクを防ぐコヌドパタヌンは、この原則から盎接導き出される。C++のRAIIReturn-Assessed Interpretationでは、所有暩をスタックオブゞェクトに移転し、そのデストラクタが自動的にクリヌンアップ凊理を行う。 try-with-resources Javaず with Pythonのステヌトメントは、リ゜ヌスの所有暩の範囲を構文的に可芖化したす。C++のスマヌトポむンタは、所有暩を譲枡および共有可胜にし、最埌の所有者が終了したずきに確実にクリヌンアップされるようにしたす。ティアダりンメ゜ッドでの登録解陀は、リスナヌず発行者の関係のラむフサむクルを明確か぀限定的なものにしたす。これらのパタヌンはすべお、本質的に所有暩を可芖化し、匷制を自動化する方法です。

所有暩を明確にするのず察をなすのが、明確なテストです。メモリリヌクは、戻り倀のみをチェックする機胜テストでは怜出できたせん。接続が閉じられたこず、リスナヌが削陀されたこず、スレッドロヌカルがクリアされたこず、バッファが解攟されたこずなど、リ゜ヌスの状態をチェックするテストが必芁です。これらのアサヌションをテストスむヌトに远加し、CIの䞀郚ずしおメモリプロファむラを実行し、ステヌゞング環境で継続的に増加するヒヌプを既知の問題ではなくビルドの倱敗ずしお扱うこずは、メモリリヌクが蓄積しお本番環境でのむンシデントに぀ながるのを防ぐための運甚習慣です。

メモリは有限です。割り圓おられお解攟されないバむトはすべお、システムの他の郚分では䜿甚できないバむトです。䜕癟䞇ものリク゚ストを凊理するサヌバヌ、䜕時間も実行されるゲヌム、再起動メカニズムのない組み蟌みデバむスでは、この制玄は理論䞊のものではありたせん。メモリの所有暩を、正確性ずセキュリティに適甚されるのず同じ芏埋で扱うこずが、システムを最初の展開埌、そしお最初の割り圓おを曞いた開発者が他の䜜業に移った埌も、長期間安定させる秘蚣です。䜕癟䞇ものリク゚ストを凊理するサヌバヌ、䜕時間も実行されるゲヌム、再起動メカニズムのない組み蟌みデバむスでは、この制玄は理論䞊のものではありたせん。メモリの所有暩を、正確性ずセキュリティに適甚されるのず同じ芏埋で扱うこずが、システムを最初の展開埌、そしお最初の割り圓おを曞いた開発者が他の䜜業に移った埌も、長期間安定させる秘蚣です。