Javaの文字列のメモリ使用量(Java8ぐらい編)

Javaの文字列は、メモリを消費します。 仕事をしていると、良く見積もり方法を誤っている人が多いため、ここに、簡単な見積もり方法を残します。 もう、10年前のノウハウなので、細かい値は間違えていたらご容赦を。

前提条件:Java8

Java9より、Compact Strings (JEP 254)が実装されているため、ここでの見積もり方法は、Java8まで有効です。 Java9以降の見積もり方法は、気が向いたら記述します。が、基本的な考え方は同じで、文字列がAscii文字のみで構成されている場合、1文字あたり1byteで計算されるところが変わります。

また、Java8では、使用する変数が減っているため、Java7もまた、別のロジックで計算する必要があります。 ただ、Java7は、Java8に比べてint変数が2個多いだけなので、基本的な考え方はこちらも同じです。

java.lang.Stringの構造

変数名 変数型 サイズ[byte]
value char[] 8
hash int 4

メモリサイズの計算方法

Stringクラスのオブジェクトヘッダに8バイト、char変数に8バイト、int変数に4バイト、合計20バイトがベースラインになります。通常は8バイトでアライメントされるため、4バイトのパディングが入り、24バイトがStringオブジェクトのベースラインになります。

続いて、char[]オブジェクトは、オブジェクトヘッダ8バイトに、配列長4バイトがベースラインで、あとは1文字追加される度に2バイト増えていきます。

そのため、24+12+2N(ただし、12+2Nはアライメントの関係で8の倍数へ切り上げる必要あり)が文字列のメモリサイズとなります。

計算例

文字列長 メモリサイズ 計算式
0 40 24+(12+2*0+4)
8 56 24+(12+2*8+4)
16 72 24+(12+2*16+4)
256 552 24+(12+2*256+4)

実用例

DBからCSVファイルをダウンロードするとき、メモリ使用量を意識しないと、直ぐにOutOfMemoryが発生します。 例えば、カラム数100、平均カラム長16、レコード数10万のCSVを作成しようとするとき、何も考えずに愚直にアプリケーションを作ってしまうと、 100 * 72byte * 100,000 = 720Mbyte という結果になります。 もし、これを単純に、 100 * 16 * 100,000 = 160Mbyte と計算したときに比べて、4倍以上の差が付く形になります。

さらに、DBから取得するときに、ResultSetの結果の保持の方法次第では同量の720Mbyte以上のサイズがヒープ上に存在することになるし、CSV加工のために"(ダブルクオート)を前後に付与する処理を一括実施したあとにファイルに書き込んだりしようとすると、さらに同量の文字列がヒープに積み込まれることになります。 このように、処理方式によって、メモリ使用量が大きく変わる可能性があります。

とはいえ、経験的に、でかいデータをどうこうするときに安全にざっくり見積もりたいときは、だいたい全文字数の10倍を見ておけば大丈夫です。

まとめ

小さい文字列を大量にヒープ上に置くときは、メモリサイジングをしっかりやりましょう。