没有合适的资源?快使用搜索试试~ 我知道了~
资源推荐
资源详情
资源评论
© CopyrightIBM Corporation2012 商標
Java コードから Java ヒープまで ページ 1 / 22
Java コードから Java ヒープまで
アプリケーションのメモリー使用量を把握し、最適化する
Chris Bailey
Java Service Architect
IBM
スキルレベル: 中級
日付: 2012年 4月 12日
この記事では Java コードのメモリー使用量を把握できるように、int 値を Integer
オブジェクトに格納する場合のメモリー・オーバーヘッドから、オブジェクト委
譲のコスト、各種コレクションの各タイプのメモリー効率まで、さまざまな話題
を取り上げます。さらに、アプリケーションの非効率的な部分を判別する方法、
そして適切なコレクションを選択してコードを改善する方法についても説明しま
す。
アプリケーション・コードのメモリー使用量の最適化は目新しい話題ではありませ
んが、一般によく理解されているとも言えません。この記事では、Java プロセスの
メモリー使用量について簡単に説明した後、皆さんが作成する Java コードのメモ
リー使用量を詳しく探ります。そして最後に、HashMap や ArrayList などの Java コレク
ションを使用する領域において、アプリケーション・コードのメモリー使用効率を
高める方法を紹介します。
基礎知識: Java プロセスのメモリー使用量
ネイティブ・メモリーに関する詳しい説明
Java アプリケーションにおけるプロセスのメモリー使用量について十分
に理解するには、Andrew Hall 氏による developerWorks の記事「Thanks
for the memory (メモリーよ、ありがとう)」を読んでください。同記事は
Windows および Linux 用と、AIX 用の 2 本に分かれており、それぞれの OS
におけるメモリー・レイアウトと使用可能なユーザー空間について、さら
には Java ヒープとネイティブ・ヒープとの間でのやりとりについて解説し
ています。
Java アプリケーションを実行するために、コマンドラインで「java」と入力する
か、Java ベースのミドルウェアを起動すると、Java ランタイムがオペレーティン
グ・システム・プロセスを作成します。それはまるで、C で書かれたプログラムを
実行しているかのようです。実際、ほとんどの JVM は主に C あるいは C++ で書か
れています。オペレーティング・システム・プロセスとしての Java ランタイムに
は、メモリーに関して他のあらゆるプロセスと同じ制約が課せられます。その制約
developerWorks® ibm.com/developerWorks/jp/
Java コードから Java ヒープまで ページ 2 / 22
とは、アドレス指定可能なメモリー空間はアーキテクチャーによって決まり、ユー
ザー空間はオペレーティング・システムによって決まるというものです。
アーキテクチャーによって決まるアドレス指定可能なメモリー空間は、何ビット・
プロセッサーを使用しているかによっても変わってきます (例えば、32 ビット・
プロセッサーであるか 64 ビット・プロセッサーであるか、あるいは 31 ビットの
プロセッサー (メインフレームの場合) であるかに依存します)。プロセッサーがア
ドレス指定可能なメモリーの範囲は、プロセスで扱えるビット数によって決まり
ます。32 ビットを扱えるとしたら、アドレス指定可能な範囲は 2^32 です。これ
は、4,294,967,296 ビット、つまり 4GB に相当します。64 ビット・プロセッサー
でアドレス指定可能な範囲はこれよりも大幅に増えて、2^64 となります。つま
り、18,446,744,073,709,551,616、つまり 16 エクサバイトです。
プロセッサー・アーキテクチャーによって決まるアドレス指定可能なメモリー空間
は、その一部が OS によって OS 自体のカーネルや C ランタイム (C または C++ で書
かれた JVM の場合) のために使用されます。OS と C ランタイムによって使用される
メモリーの量は、どの OS を使用しているかによって変わってきますが、通常は相当
な量が使用されます。例えば、Windows がデフォルトで使用する量は 2GB です。残
りのアドレス指定可能な空間 (ユーザー空間と呼ばれます) が、実際に実行中のプロ
セスで使用できるメモリーということになります。
Java アプリケーションで言うと、ユーザー空間は Java プロセスが使用するメモリー
であり、実質的には Java ヒープとネイティブ (非 Java) ヒープという 2 つのプールで
構成されます。Java ヒープのサイズは JVM の Java ヒープ設定によって制御されま
す。この Java ヒープ設定には、Java ヒープの最小サイズを設定する -Xms と Java ヒー
プの最大サイズを設定する -Xmx があります。ネイティブ・ヒープは、Java ヒープが
最大サイズの設定で割り当てられた後の、残りのユーザー空間です。図 1 に一例と
して、32 ビット Java プロセスの場合のメモリー・レイアウトを示します。
図 1. 32 ビット Java プロセスでのメモリー・レイアウトの例
図 1 では、アドレス指定可能な 4GB のメモリー空間のうち、約 1GB を OS と C ラン
タイムが使用し、約 2GB を Java ヒープが、そしてその残りをネイティブ・ヒープが
使用しています。JVM 自体もメモリーを使用すること (OS カーネルと C ランタイム
と同じように使用します)、そして JVM が使用するメモリーはネイティブ・ヒープの
一部であることに注意してください。
Java オブジェクトの分析
Java コードで new 演算子を使用して Java オブジェクトのインスタンスを作成する場
合、そのインスタンスに割り当てられるデータの量は、おそらく皆さんの想像を遥
かに超えているでしょう。驚かれるかもしれませんが、例えば int 値と Integer オブ
ibm.com/developerWorks/jp/ developerWorks®
Java コードから Java ヒープまで ページ 3 / 22
ジェクト (int 値を保持できる最小のオブジェクト) のサイズの比は、一般に 1:4 にも
なります。この増加分のオーバーヘッドは、JVM が Java オブジェクト (この例では
Integer) を表すために使用するメタデータです。
オブジェクトのメタデータのサイズは、JVM のバージョンやベンダーによって異な
りますが、通常は以下のメタデータを合計したサイズとなります。
• クラス: オブジェクトの型を表すクラス情報を指すポイン
ター。java.lang.Integer オブジェクトの場合、これは java.lang.Integer クラスを
指すポインターです。
• フラグ: オブジェクトの状態を表すフラグを集めたもの。このなかには、メタ
データとしてオブジェクトのハッシュ・コードが含まれる場合にそのことを示
すフラグや、オブジェクトの「形式」を示すフラグ (オブジェクトが配列の形
式をしているかどうかを示すフラグ) も含まれます。
• ロック: オブジェクトの同期情報。つまりオブジェクトが現在同期されている
かどうかを示します。
オブジェクトのメタデータの後には、オブジェクト・インスタンスに保管されている
フィールドからなるオブジェクト・データの本体が続きます。java.lang.Integer オブ
ジェクトの場合、オブジェクト・データは int が 1 つのみです。
したがって、32 ビット JVM を実行している場合に java.lang.Integer オブジェクトの
インスタンスを作成すると、オブジェクトのレイアウトは図 2 のようになります。
図 2. 32 ビット Java プロセスでの java.lang.Integer オブジェクトのレイアウト例
図 2 に示されているように、int 値の 32 ビットのデータを格納するために、128
ビットのデータが使用されます。128 ビットのうち、32 ビット以外はすべてオブ
ジェクトのメタデータが使用しています。
Java 配列オブジェクトの分析
int 値の配列をはじめとする配列オブジェクトは、標準的な Java オブジェクトと似た
ような形式と構造を持ちますが、最も違う点は、配列オブジェクトには配列のサイ
ズを示すメタデータが追加されることです。配列オブジェクトは以下のメタデータ
で構成されます。
• クラス: オブジェクトの型を表すクラス情報を指すポインター。int フィールド
の配列の場合、これは int[] クラスを指すポインターです。
• フラグ: オブジェクトの状態を表すフラグを集めたもの。このなかには、メタ
データとしてオブジェクトのハッシュ・コードが含まれる場合にそのことを示
すフラグや、オブジェクトの形式を示すフラグ (オブジェクトが配列の形式を
しているかどうかを示すフラグ) も含まれます。
developerWorks® ibm.com/developerWorks/jp/
Java コードから Java ヒープまで ページ 4 / 22
• ロック: オブジェクトの同期情報。つまりオブジェクトが現在同期されている
かどうかを示します。
• サイズ: 配列のサイズ。
図 3 に一例として、int 配列オブジェクトのレイアウトを示します。
図 3. 32 ビット Java プロセスでの int 配列オブジェクトのレイアウト例
図 3 では、int 値の 32 ビットのデータを格納するために、160 ビットのデータが
使用されています。160 ビットのうち、32 ビット以外はすべて配列のメタデータ
が使用しています。byte、int、long などのプリミティブ型の場合、エントリーが
1 つのみの配列は、これらに対応する単一フィールドのラッパー・オブジェクト
(Byte、Integer、または Long) よりも、メモリーの点でコストがかかります。
さらに複雑なデータ構造の分析
優れたオブジェクト指向の設計およびプログラミングでは、カプセル化 (データへの
アクセスを制御するインターフェース・クラスを提供すること) と委譲 (ヘルパー・
オブジェクトを使用してタスクを実行すること) を用いることを推奨しています。
カプセル化と委譲によって、ほとんどのデータ構造の表現には複数のオブジェク
トが関与することになります。その単純な一例は、java.lang.String オブジェクトで
す。java.lang.String オブジェクトには文字型の配列がデータとして格納され、この
文字型配列に対する読み書きと管理を行う java.lang.String オブジェクトによってカ
プセル化されます。java.lang.String オブジェクトのレイアウトは、32 ビット Java プ
ロセスでは図 4 のようになります。
図 4. 32 ビット Java プロセスでの java.lang.String オブジェクトのレイアウト例
図 4 に示されているように、java.lang.String オブジェクトにはオブジェクトの標準
的なメタデータに加え、文字列データを管理するためのフィールドが含まれていま
す。これらのフィールドは通常、ハッシュ値、文字列のサイズのカウント、文字列
データへのオフセット、文字の配列自体へのオブジェクト参照です。
つまり、8 文字の文字列 (char データによる 128 ビット) の場合、文字配列に 256
ビットのデータが使用され、その配列を管理する java.lang.String オブジェクト
ibm.com/developerWorks/jp/ developerWorks®
Java コードから Java ヒープまで ページ 5 / 22
に 224 ビットのデータが使用されるため、128 ビット (16 バイト) のデータを表現
するには合計 480 ビット (60 バイト) が必要になります。このオーバーヘッド比
は、3.75:1 です。
一般に、データ構造が複雑になるにつれ、オーバーヘッドは大きくなっていきま
す。このことに関しては、次のセクションでさらに詳しく検討します。
32 ビットおよび 64 ビットの Java オブジェクト
これまでの例で取り上げたオブジェクトでのサイズとオーバーヘッドは、32 ビッ
ト Java プロセスに適用されるものです。「基礎知識: Java プロセスのメモリー使用
量」セクションで説明したように、64 ビット・プロセッサーの場合、32 ビット・
プロセッサーよりもアドレス指定可能なメモリー空間は遥かに大きくなります。64
ビット・プロセスでは、Java オブジェクトに含まれる一部のデータ・フィールドの
サイズ (具体的には、オブジェクトのメタデータと、別のオブジェクトを参照する
フィールド) も 64 ビットまで増やす必要があります。それ以外のデータ・フィール
ドの型 (int、byte、long など) については、サイズは変わりません。図 5 に、64 ビッ
トの Integer オブジェクトおよび int 配列のレイアウトを示します。
図 5. 64 ビット Java プロセスでの java.lang.Integer オブジェクトおよび int 配列オブ
ジェクトのレイアウト例
図 5 に示されている 64 ビットの Integer オブジェクトの場合、int フィールド用の
32 ビットを格納するために使用されるデータは 224 ビットです (オーバーヘッド比
7:1)。64 ビットの単一要素からなる int 配列では、32 ビットの int エントリーを格
納するために 288 ビットのデータが使用されます (オーバーヘッド比 9:1)。この影響
は、実際のアプリケーションでは、今まで 32 ビット Java ランタイムで実行されてい
たアプリケーションを 64 ビット Java ランタイムに移した場合、アプリケーションの
Java ヒープ・メモリー使用量が劇的に増加するという形で現れます。増加率は一般
に、元のヒープ・サイズの約 70 パーセントです。例えば、32 ビット Java ランタイム
で 1GB の Java ヒープを使用する Java アプリケーションは、64 ビット Java ランタイ
ムでは 1.7GB の Java ヒープを使用することになります。
このメモリー使用量の増加は、Java ヒープに限られた話ではありません。ネイティ
ブ・ヒープ・メモリー領域の使用量も同じく増加します。その増加率は、場合に
よっては 90 パーセントにも及びます。
表 1 に、アプリケーションが 32 ビット・モードで実行される場合と、64 ビット・
モードで実行される場合の、それぞれのオブジェクトと配列のフィールド・サイズ
を記載します。
剩余21页未读,继续阅读
资源评论
mysteryboy2000
- 粉丝: 6
- 资源: 5
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功