🎃

ヒープ領域とメモリリーク

2025/02/06に公開
3

Daily Blogging47日目

今までメモリのことなんて気にせずに生きてきたけど、ついに気にしなければならない時が来た

ヒープ領域とは

そもそもメモリとは、コンピュータ全体の記憶領域(RAM)を指す。
そのメモリには2つの種類が存在する。

  • スタック領域
  • ヒープ領域

両者の違いは、何を格納するのかというところと動的であるかというところ

スタック領域

関数の戻り値やローカル変数が一時的に格納される記憶領域。
関数が終了したタイミングや、ローカル変数のスコープを抜けたタイミングで自動で解放される。

特徴

  • サイズは固定(コンパイル時に決まる)
  • 基本的にプリミティブな値(数値・真偽値など)が入る

stringの扱い

stringはプリミティブだけど、他のプリミティブと違って実際のサイズが可変長なのでヒープ領域に入るらしい
スタック領域に入れると、Stringの値を変えた時に元々それ用に確保してた領域に収まらなくなって隣に格納されていたデータに影響を与える。
あとは長い文字列が来た時に元々領域が狭いスタック領域に入れるとすぐ枯渇しちゃう

ヒープ領域

参照型のデータ、stringが格納される記憶領域。

動的に確保可能なメモリの領域。ヒープ (heap) とは、『山積み』という言葉の中の『山』をさす英単語である。

特徴

  • サイズが可変(実行時に確保・解放される)
  • スタックよりも管理が複雑で低速(ガベージコレクションが必要)
  • オブジェクト、配列、関数、可変長の string などが格納される

スタックに入りきらない大きなデータや、動的に確保されるデータはヒープに置かれる

ガベージコレクション

ヒープ領域のメモリはいつ解放されるのか。
誰が解放するのか。

ヒープ領域のメモリは、ガベージコレクション(GC)という存在が解放してくれる。
解放するメモリは、どこからも参照されなくなったデータ
たとえば、

  • スコープを抜けた変数
  • nullやundefinedが代入される

逆に解放されないデータはこういうやつ

  • グローバル変数
  • イベントリスナー

メモリリーク

解放されないデータはヒープ領域に残り続ける。
ヒープ領域にデータが溜まり続け、メモリが枯渇することをメモリリークと呼ぶ。

メモリリークが発生すると、システムのパフォーマンスが低下し、最悪クラッシュするよ。

こういうところに気をつけよう

メモリリークを防ぐためには、使い終わったデータは確実に解放し、不要なオブジェクトを残さないを徹底するのがポイント

不要な参照を残さない

悪い

let cache = {};  // ずっとメモリに残る

function storeData(key, value) {
  cache[key] = value;
}

良い

// キャッシュの上限を決められる
const LRU = require("lru-cache");
const cache = new LRU({ max: 100 }); // 最大100件まで保持

function storeData(key, value) {
  cache.set(key, value);
}

イベントリスナーやタイマーを適切にクリーンアップ

悪い

useEffect(() => {
  window.addEventListener("resize", () => console.log("resize"));
}, []);

良い

useEffect(() => {
  const handleResize = () => console.log("resize");
  window.addEventListener("resize", handleResize);
  // クリーンアップ
  return () => window.removeEventListener("resize", handleResize);
}, []);

悪い

useEffect(() => {
  setInterval(() => console.log("interval"), 1000);
}, []); // タイマーが増え続ける!

良い

useEffect(() => {
  const intervalId = setInterval(() => console.log("interval"), 1000);
  return () => clearInterval(intervalId);
}, []);

大きなデータを扱うときはメモリ効率を意識する

悪い

users = User.all.to_a  # 全データをメモリにロードし、圧迫

良い

User.find_each do |user|
  puts user.name  # メモリに溜め込まずに処理
end

Discussion

junerjuner

オブジェクトや配列のデータが格納される記憶領域。

javascript には 値型 / 参照型 についての実装は定義されていない為、 プリミティブでも string とかは ヒープに格納されていてもおかしくないのではないでしょうか?

ShoSho

ご指摘ありがとうございます!
色々調べてみましたが確かにstringは可変長なのでヒープ領域に格納されるみたいですね!
こちら修正いたしました!

junerjuner

究極 Pythonみたいな実装(=すべて参照型)も許容されているので注意が必要です。