web-dev-qa-db-ja.com

Java intメモリ使用量

さまざまな型のメモリ使用量を考えていたとき、メソッドに渡されるときにJavaが整数にメモリを使用する方法について少し混乱するようになりました。

たとえば、次のコードがありました。

public static void main (String[] args){
     int i = 4;
     addUp(i);
}

public static int addUp(int i){
     if(i == 0) return 0;
     else return addUp(i - 1);         
}

この次の例では、次のロジックが正しいかどうか疑問に思っています。

  • 最初に整数i = 4のメモリを作成しました。次に、それをメソッドに渡します。ただし、JavaではプリミティブがaddUp(i == 4)で指定されていないため、別の整数i = 4を作成します。その後、別のaddUp(i == 3)、addUp(i == 2)、 addUp(i == 1)、addUp(i == 0)では、毎回、値がポイントされていないため、新しいi値がメモリに割り当てられます。
  • 次に、単一の「int i」値に対して、6個の整数値メモリを使用しました。

ただし、常に配列を介して渡す場合:

public static void main (String[] args){
     int[] i = {4};
     // int tempI = i[0];
     addUp(i);
}

public static int addUp(int[] i){
     if(i[0] == 0) return 0;
     else return addUp(i[0] = i[0] - 1);         
}

-サイズ1の整数配列を作成し、それをaddUpに渡すと、addUp(i [0] == 3)、addUp(i [0] == 2)、addUp(i [0] == 1)、addUp(i [0] == 0)、1つの整数配列のメモリ空間のみを使用すればよいため、はるかにコスト効率が高くなります。さらに、i [0]の初期値を格納するために事前にint値を作成する場合、「元の」値がまだあります。

次に、なぜ人々はJavaメソッドでintのようなプリミティブを渡すのですか?これらのプリミティブの配列値を渡す方がはるかにメモリ効率的ではありませんか?例はどういうわけかまだO(1) memory?

そして、この質問に加えて、特にサイズが1の場合にint []とintを使用した場合のメモリの違いを疑問に思います。事前にありがとうございます。 Javaでよりメモリ効率が良くなると思っていたのですが、これが頭に浮かびました。

すべての答えをありがとう!私は各コードの大容量メモリを「分析」するのかどうかすぐに疑問に思っています。両方ともO(1)と見なされるのでしょうか、それとも仮定するのは間違っていますか?

33
AccCreate

ここで不足しているもの:例のint値は、ヒープではなく、stackに移動します。

そして、ヒープ上のオブジェクトと比較して、スタックに存在する固定サイズのプリミティブ値を処理するオーバーヘッドははるかに少なくなります!

つまり、「ポインター」を使用すると、ヒープ上に新しいオブジェクトを作成する必要があります。 Allオブジェクトはヒープ上に存在します。配列にはnoスタックがあります!また、オブジェクトは、使用を停止した直後にガベージコレクションの対象になります。一方、メソッドを呼び出すとスタックが行き来します!

それを超えて:プログラミング言語が提供するabstractionsは、読みやすく、理解しやすく、保守しやすいコードを書くのに役立つように作成されていることに留意してください。あなたのアプローチは基本的に、より複雑なコードにつながるある種の微調整を行うことです。そして、それはJavaがそのような問題を解決する方法ではありません。

意味:Javaでは、実行時にジャストインタイムコンパイラーが起動すると、実際の「パフォーマンスマジック」が発生します。ご存知のように、JITは、メソッドが「十分に」呼び出されると、小さなメソッドの呼び出しをinlineできます。そして、データを「近く」に保つことが重要になりますmore。たとえば、データがヒープ上に存在する場合、値を取得するにはmemoryにアクセスする必要があります。一方、スタック上にあるアイテムは-「プロセッサキャッシュ内」のように「近い」場合があります。したがって、メモリ使用量を最適化するための小さなアイデアは、実際にプログラムの実行を大幅に遅くする可能性があります。なぜなら今日でも、プロセッサキャッシュへのアクセスとメインメモリの読み取りには数桁の差があるためです。

簡単に言えば、パフォーマンスまたはメモリ使用量のいずれかについて、このような「微調整」を避けてください。JVMは、「通常の典型的な」ユースケース向けに最適化されています。したがって、巧妙な回避策を導入しようとすると、簡単に「あまり良くない」結果になります。

だから-あなたがパフォーマンスを心配するとき:他のみんながやっていることをやってください。で、もし 君は onereally注意-次に、JVMがどのように機能するかを学ぶ。コメントから、JITはスタック上のオブジェクトをinlineできることを暗示しているように、私の知識さえもわずかに時代遅れであることがわかりました。その意味では、問題を直接解決するクリーンでエレガントなコードを書くことに集中してください!

最後に、これはある時点で変更される可能性があります。 Javaにtrue値値オブジェクトを導入するアイデアがあります。基本的にはスタックではなく、ヒープ上に存在します。しかし、Java10の前にそれが起こることを期待しないでください。または11.または...(ここでは this が関係すると思います)。

56
GhostCat

いくつかのこと:

最初にヘアを分割しますが、Javaでintを渡すと、スタックに4バイトを割り当てています。配列を(参照であるため)渡すと、実際に割り当てます。スタックへの8バイト(x64アーキテクチャを想定)と、intをヒープに格納する追加の4バイト。

さらに重要なことは、配列に存在するデータがヒープに割り当てられるのに対し、配列自体への参照はスタックに割り当てられます。整数を渡すとき、ヒープの割り当ては不要で、プリミティブはスタックにのみ割り当てられます。時間の経過とともにヒープ割り当てを減らすと、ガベージコレクターのクリーンアップ対象が少なくなります。一方、スタックフレームのクリーンアップは簡単であり、追加の処理を必要としません。

ただし、実際には変数とオブジェクトの複雑なコレクションがある場合、それらをクラスにグループ化する可能性が高いため、これはすべて意味がありません(imho)。一般に、JVMのパフォーマンスの最後の低下をすべて圧縮しようとするのではなく、読みやすさと保守性を促進するために書くべきです。 JVMはそのままで非常に高速であり、バックストップとして ムーアの法則 が常に存在します。

それぞれのBig-Oを分析することは困難です。なぜなら、本当の状況を把握するには、ガベージコレクターの動作を考慮する必要があり、その動作はJVM自体とランタイム(JIT)の両方に大きく依存するからです。 JVMがコードに対して行った最適化。

ドナルドクヌース の「早すぎる最適化がすべての悪の根源である」という賢明な言葉を覚えておいてください

微調整を回避するコードを書くと、読みやすさと保守性を促進するコードは、長期的にはより良くなります。

18
pfranza

関数に渡される引数が必ずメモリを消費するという前提がある場合(ちなみにfalse)、配列を渡す2番目の例では、配列への参照のコピーが作成されます。その参照は、実際にはintよりも大きい場合がありますが、小さい可能性はほとんどありません。

10
harold

これらのメソッドがO(1)をとるか、O(N)をとるかは、コンパイラによって異なります。ここでNはiの値ですi[0]、依存。)コンパイラが末尾再帰最適化を使用する場合、パラメータ、ローカル変数、およびリターンアドレスのスタックスペースを再利用でき、実装はO(1)テール再帰最適化がない場合、スペースの複雑さは時間の複雑さO(N)と同じです。

基本的に末尾再帰の最適化の量(この場合)は、コンパイラーがコードを次のように書き換えます。

public static int addUp(int i){
     while(i != 0) i = i-1 ;
     return 0;        
}

または

public static int addUp(int[] i){
     while(i[0] != 0) i[0] = i[0] - 1 ;
     return 0 ;
}

優れたオプティマイザーは、ループをさらに最適化する可能性があります。

私の知る限り、現時点ではno Javaコンパイラは末尾再帰最適化を実装していませんが、多くの場合実行できないという技術的な理由はありません。

7

実際には、arrayをメソッドのパラメーターとして渡すと、この配列への参照が内部で渡されます。配列自体はヒープに格納されます。また、参照は4または8バイトサイズにすることができます(CPUアーキテクチャ、JVM実装などに依存します。さらに、JLSは、参照はメモリ内にあります)。

一方、プリミティブなint値は常に4バイトのみを消費し、スタックに常駐します。

5

配列を渡すと、配列を受け取るメソッドによって配列の内容が変更される場合があります。 intプリミティブを渡すとき、それらのプリミティブは、それらを受け取るメソッドによって変更されない場合があります。そのため、プリミティブを使用する場合と配列を使用する場合があります。

また、一般的に、Javaプログラミングでは、読みやすさを優先し、この種のメモリ最適化をJITコンパイラーで実行できるようにする傾向があります。

4
Andres

Int配列参照は、実際にはintプリミティブよりもスタックフレームで多くのスペースを使用します(8バイト対4)。実際には、より多くのスペースを使用しています。

しかし、人々が最初の方法を好む主な理由は、それがより明確で読みやすいからだと思います。

実際には、より多くのintが関係する場合、2番目に近いものを実行します。

4
Juan C Nuno