【Go言語】bytes.Bufferを使っていてメモリ解放できない原因を解説する

bytes.Bufferはバッファリングされたデータをメモリ上に保持するGo言語の標準ライブラリです。

このバッファを使用するとプログラム実行中にメモリ使用量が増えるため、ちゃんと管理してあげることが必要です。

今回はこのbytes.Bufferによって占有されたメモリを解放できない原因を3つ解説します。

バッファの参照がまだ存在している

もっとも原因になるのがこれ。

バッファが使用されている間やバッファの参照がまだ存在している場合、メモリは解放されません。バッファを使用するすべてのコードを確認して、バッファの参照を削除するように修正する必要があります。

例えばバッファを関数の戻り値として返すようなコードだと、呼び出し元でバッファの参照がまだ存在しているためメモリ解放できません。バッファの参照を削除することが必要です。

以下、バッファの参照がまだ存在している場合にメモリが解放されないサンプルコードです。


package main

import (
	"bytes"
	"fmt"
)

func createBuffer() *bytes.Buffer {
	var buf bytes.Buffer
	buf.WriteString("Hello, World!")
	return &buf
}

func main() {
	buf := createBuffer()
	fmt.Println(buf.String())
}

このコードでは、createBuffer()関数でbytes.Bufferを作成して文字列をメモリに書き込んでいます。そして&bufを返しています。このようにして返されたバッファはメモリが解放されないまま、main()関数で参照され続けます。

この場合にメモリを解放するにはバッファの参照を削除する必要があります。具体的にはcreateBuffer()関数内でバッファを作成し、そのバッファのポインタを返すのではなく、bytes.Bufferのポインタを直接返すように修正することで解決できます。

修正後のコードは以下のようになります。


package main

import (
	"bytes"
	"fmt"
)

func createBuffer() *bytes.Buffer {
	buf := bytes.Buffer{} //ここ
	buf.WriteString("Hello, World!")
	return &buf
}

func main() {
	buf := createBuffer()
	fmt.Println(buf.String())
}

 

GC(ガーベージコレクション)が実行されていない

Go言語のGC(ガーベージコレクション)は不要になったメモリを解放するために実行されます。

GCが実行されるまで一度確保されたメモリは解放されません。普通、GCは頻繁に実行されるためこの問題はほとんど発生しません。ただし、バッチなどの重めの処理で長時間かかるプログラムが実行された場合だとGCが実行されるまでメモリが解放されないことがあります。

こういう場合に明示的にGCを実行することができます。runtimeパッケージのGC()関数を使用してGCを手動でトリガーします。

package main

import (
	"bytes"
	"fmt"
	"runtime"
)

func main() {
	var buf bytes.Buffer

	// 文字列をバッファに書き込む
	buf.WriteString("Hello, World!")

	// バッファの内容を表示する
	fmt.Println(buf.String())

	// バッファを解放する
	buf.Reset()

	// GCを手動で実行する
	runtime.GC()
}

このサンプルコードではbuf.Reset()を使用してバッファをクリアしてます。またruntime.GC()を使用して、不要なメモリを解放するように指示しています。

まとめ

問題になることは少ないと思います。逆に問題になったときは原因を特定しにくい問題だと思います。