LoginSignup
85
93

More than 3 years have passed since last update.

45個Javaパフォーマンス最適化の技(上篇)

Last updated at Posted at 2019-03-26

前書き:
日本語はnativeではないため、おかしい表現や誤った文法が結構あると思うので、指摘していただくとありがたいです。

JAVAプログラムでは、パフォーマンスの問題のほとんどはJAVA言語にあるのではなく、プログラム自体にあります。
プログラムのパフォーマンスを、大幅に向上させることができる優れたコーディング習慣を身に付けることが重要です。

1.正しい場所でSingletonを使うようにしましょう

シングルトンを使用すると、ロードの負荷が軽減され、ロード時間が短縮され、ロードの効率が向上しますが、すべての場所がシングルトンに適しているわけではありません。

簡単に言うと、シングルトンは主に以下の3つのシチュエーションに適用できます。

1.リソースの使用を制御し、スレッドの同期を通じてリソースの同時アクセスを制御します。
2.リソース保護の目的を達成するためにインスタンスの生成を制御します。
3.直接の関連付けを確立することなく、データ共有を制御し、複数の無関係なプロセス間またはスレッド間の通信を可能にしたい場合。

2.静的変数を自由に使用しないようにしましょう

オブジェクトが静的変数として定義されている場合、GCは通常、そのオブジェクトによって占有されているメモリを回収しません。

public class A {
  private static B b = new B();
}

このとき、静的変数bのライフサイクルはクラスAと同期しています。クラスAがアンロードされていない場合、プログラムが終了するまでbオブジェクトはメモリ内に常駐します。

3.あまりにも多くのJavaオブジェクトを作成しすぎないようにしましょう

システムではオブジェクトの作成に時間がかかるだけでなく、これらのオブジェクトのガベージコレクションおよび処理にも時間がかかるため、頻繁に呼び出されるループやメソッド内で新しいオブジェクトを生成することを回避するようにしてください。 オブジェクトを基本データ型または配列に置き換えるのが最善です。

4. final修飾子を使用してみてください

final修飾子を持つクラスは派生できません。 JavaコアAPIには、java.lang.Stringなどのfinal付きの例が多数あります。例えばStringクラスにfinalを指定すると、ユーザーはlength()メソッドをオーバーライドできなくなります。 さらに、クラスがfinalの場合、そのクラスのすべてのメソッドはfinalになります。 Javaコンパイラーは、すべてのfinalメソッドをインライン化する機会を探します(これは特定のコンパイラーの実装に関連します)。これにより、パフォーマンスが平均的に50%向上します。

5.できるだけローカル変数を使買いましょう

メソッドが呼び出されたときに、渡されたパラメータと呼び出しで作成された一時変数はスタックに保存される方が速いです。一方、静的変数やインスタンス変数などの他の変数はヒープに作成され、スタックと比べて遅いです。

6.ボクシング型とプリミティブ型の両方の使用を扱うようにしましょう

ボクシング型とプリミティブ型は使用中に相互に変換できます。しかし、両者によって生成されるメモリ領域はまったく異なります。プリミティブ型の生成と処理はスタック上で処理され、ボックス型はオブジェクトであるのでヒープにインスタンスを生成します。 コレクションなど、ボックス型を使って処理する必要がある場面以外ではプリミティブタイプの使用が推奨されています。

7.同期を最小限にする

周知のように、同期は大きなシステムオーバーヘッドであり、さらにデッドロックを引き起こす可能性さえあるので、不必要な同期制御を避けるようにしてください。 synchronizeメソッドが呼び出されると、現在のオブジェクトは直接ロックされ、他のスレッドはそのメソッドが終了するまで現在のオブジェクトの他のメソッドを呼び出すことができません。 したがって、同期方法は最小限に抑えられ、コードブロック同期の代わりに方法同期を使用する必要があります。

8. finalizeメソッドを使わないようにしましょう

GCのワークロードは非常に大きいため、特にYoungメモリがリサイクルされるとアプリケーションは一時停止するので、もしこの時finalizeメソッドを使ってリソースをクリーンアップすると、GCの負荷が大きくなり、プログラム操作が遅くなります。

9.オブジェクトの代わりに基本データ型を使うようにしましょう

String str = "hello";

上記のコードは、"hello"文字列が作成され、JVMの文字キャッシュプールもこの文字列をキャッシュします。

String str = new String("hello");

この時、文字列を作成することに加えて、strが参照するStringオブジェクトにはchar []配列も含まれています。この配列にはh、e、l、l、oが格納されています。

10.マルチスレッドは、スレッドセーフを考慮する必要がない時、可能な限りHashMapとArrayListを使用しましょう

HashTable、Vectorなどは同期メカニズムを使っているのでパフォーマンスを低下させます。

11.できるだけ合理的なHashMapを作成しましょう

大きなHashMapを作成したいときは、できるだけ下のコンストラクタで容量と負荷係数を指定してください。

public HashMap(int initialCapacity, float loadFactor);

HashMapの拡張を避けてください。拡張は非常にコストがかかるものです。

デフォルトでは、initialCapacityは16、loadFactorは0.75ですが、どれだけの容量が必要なのか、必要な最適サイズを正確に見積もることが重要です。
Hashtable、Vectorsも同じです。

12.変数の二重計算を減らしましょう

for(int i=0;i<list.size();i++)

の場合

for(int i=0,len=list.size();i<len;i++)

に変更する必要があります。

ループ内で複雑な式を使用しない。ループでは、ループ条件は繰り返し計算されます。
複雑な式を使用せずにループ条件の値を変更しないことによって、プログラムはより速く実行されます。

13. 不必要なインスタンス生成を避けるようにしましょう

例えば:

A a = new A();

if(i==1){

  list.add(a);

}

の場合、

if(i==1){

  A a = new A();

  list.add(a);

}

に変更するべきです。

14. finallyブロックでリソースを解放しよう

プログラムで使用されているリソースは、リソースリークを回避するために解放する必要があります。これはfinallyブロックで行うのが最善です。 プログラムの実行結果にかかわらず、finallyブロックは常にリソースが適切に閉じられるように実行されます。

15. 'a / b'の代わりにシフト演算を使ってみましょう

"/"は非常に高価な演算で、シフト演算を使用するとより速く効率的になります。
例えば、

int num = a / 4;

int num = a / 8;

の場合、

int num = a >> 2;

int num = a >> 3;

に変更するべきです。
ただし、シフト操作は直感的で理解しにくいため、シフトを使用するとコメントが追加する方がいいでしょう。

16. 'a * b'の代わりにシフト演算を使ってみましょう

同様に、 '*'演算では、シフト演算を使用する方が速く効率的です。

例えば、

int num = a * 4;

int num = a * 8;

の場合、

int num = a << 2;

int num = a << 3;

に変更するべきです。

17. StringBufferの容量を出来るだけ事前に決めるべきでしょう

StringBufferコンストラクタは、デフォルトサイズ(通常は16)の文字の配列を作成します。 使用中、このサイズを超えると、メモリが再割り当てされ、より大きな配列が作成され、元の配列がコピーされ、古い配列が破棄されます。

ほとんどの場合、StringBufferを作成するときにサイズを指定できます。これによって、容量がに十分でない時に起こる自動拡張を回避し、パフォーマンスを向上させることができます。

18.無駄なオブジェクトへの参照をできるだけ早く解放しましょう

ほとんどの場合、メソッドのローカル参照変数によって参照されるオブジェクトはメソッドの終了時にゴミになるため、ほとんどの場合、プログラムは参照変数を明示的にnullに設定する必要はありません。

例えば

Public void test(){

Object obj = new Object();

……

obj=null;

}

上記のコードでは、明示的に´obj = null´を実行する必要はありません。

メソッドtest()の実行が完了すると、objの参照は、解放されます。

しかし、プログラムが次のように変更されたなら:

public void test(){

Object obj = new Object();

// ……

Obj=null;

//時間のかかる、メモリ集約的な操作、または時間のかかる、メモリ集約的な方法が呼びされる

// ……

}

この時、objに明示的にnullを代入することで、早期に、Objectへの参照を解放できます。

19.できるだけ二次元配列を使わないようにしましょう

二次元データは一次元配列よりもはるかに多くのメモリ空間を占有します。約10倍です。

20.splitを使わないようにしましょう

splitは正規表現をサポートしているため、効率は比較的低く、多くのリソースを消費することになります。頻繁にキャッシュできるApacheのStringUtils.split(string、char)の使用を検討してください。

21. ArrayListとLinkedList

1つは線形テーブル、1つはリンクリスト、結論からすると、getを頻繁に呼び出す場合はArrayListはLinkedListより優れています。LinkedListの場合は一つずつ走査する必要があります。一方、addやremoveを頻繁に呼び出す場合は、LinkedListはArrayListより優れています。ArrayListはデータを移動させる必要があります。

これは理論的分析です。両者のデータ構造を理解し、シチュエーションに応じて適切な対処法を取りましょう。

22.配列をループしてコピーするではなく、System.arraycopy()を使用しましょう

System.arraycopy()は、ループして配列をコピーするよりもはるかに高速です。

23.よく使うオブジェクトをキャッシュしよう

頻繁に使用されるオブジェクトをキャッシュするために、配列、またはHashMapコンテナを使用してキャッシュすることができます。しかし、この方法ではシステムのキャッシュ使用量が多くなりすぎてパフォーマンスが低下します。EhCacheなどのサードパーティオープンソースツールの使用をお勧めします。 Oscacheはキャッシュされ、それらは基本的にFIFO / FLUのようなキャッシュアルゴリズムを実装しています。

85
93
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
85
93