ホームページ  >  記事  >  Java  >  Java ヒープ メモリとスタック メモリの詳細な紹介

Java ヒープ メモリとスタック メモリの詳細な紹介

高洛峰
高洛峰オリジナル
2017-01-24 14:44:531710ブラウズ

Java のヒープとスタック Java はメモリを 2 つのタイプに分割します。1 つはスタック メモリ、もう 1 つはヒープ メモリです。

関数内で定義された一部の基本型変数とオブジェクト参照変数は、関数のスタックメモリに確保されます。変数がコードのブロック内で定義されている場合、Java はスタック上の変数にメモリ領域を割り当てます。変数のスコープを超えると、Java は変数に割り当てられたメモリ領域を自動的に解放し、メモリ領域をすぐに解放できます。他の目的に使用します。

ヒープメモリは、newによって作成されたオブジェクトと配列を保存するために使用されます。ヒープに割り当てられたメモリは、Java 仮想マシンの自動ガベージ コレクタによって管理されます。配列またはオブジェクトがヒープ内に生成された後、スタック内に特殊変数を定義することもできます。この変数の値は、ヒープ メモリ内の配列またはオブジェクトの最初のアドレスと等しくなります。配列またはオブジェクトの参照変数を取得した後、スタック メモリ内の参照変数を使用して、プログラム内のヒープ内の配列またはオブジェクトにアクセスできます。参照変数は、配列またはオブジェクトのエイリアスまたはコード名に相当します。 。

参照変数は、定義されるとスタック上にメモリが割り当てられ、プログラムの実行時にスコープ外に解放されます。配列とオブジェクト自体はヒープ内に割り当てられます。配列とオブジェクトを生成するために new を使用するステートメントが配置されているコード ブロックの外でプログラムが実行された場合でも、配列とオブジェクト自体が占有しているヒープ メモリは解放されません。配列とオブジェクトは参照変数によって指されませんが、使用するとガベージとなり使用できなくなりますが、依然としてメモリを占有しているため、後で不特定の時点でガベージ コレクターによって解放されます。これは、Java がより多くのメモリを消費する主な理由でもあります。実際、スタック内の変数は、Java のポインタであるヒープ メモリ内の変数を指します。

Java のヒープとスタック

Java では、メモリが 2 つのタイプに分けられます。 : 1 つはスタック メモリ、もう 1 つはヒープ メモリです。

1. スタックとヒープは、Java が Ram にデータを保存するために使用する場所です。 C++ とは異なり、Java はスタックとヒープを自動的に管理するため、プログラマはスタックやヒープを直接設定できません。

2. スタックの利点は、CPU に直接配置されているレジスタに次いでアクセス速度がヒープよりも速いことです。ただし、スタックに保存されるデータのサイズと有効期間を決定する必要があり、柔軟性に欠けるという欠点があります。さらに、スタックデータを共有することもできます。ヒープの利点は、メモリ サイズを動的に割り当てることができ、Java のガベージ コレクターが使用されなくなったデータを自動的に収集するため、有効期間を事前にコンパイラーに伝える必要がないことです。ただし、実行時に動的にメモリを割り当てる必要があるため、アクセス速度が遅いという欠点があります。

3. Java には 2 種類のデータ型があります。

1つはプリミティブ型で、int、short、long、byte、float、double、boolean、charの合計8種類があります(
には文字列の基本型がないことに注意してください)。このような定義は int a = 3; long b = 255L; ​​のような形式で定義されており、自動変数と呼ばれます。自動変数はクラスのインスタンスではなくリテラル値を格納することに注意してください。つまり、ここにはクラスがありません。たとえば、 int a = 3; ここで、 a は int 型を指す参照、

はリテラル値 3 を指します。これらのリテラルデータのサイズと有効期間は既知であるため(これらのリテラル値は特定のプログラムブロック内で固定的に定義され、フィールド値はプログラムブロックが終了すると消える)、速度向上のために

が存在します。スタック内にあります。

さらに、スタックには非常に重要な特殊機能があります。それは、スタックに保存されたデータを共有できることです。同時に次のように定義するとします。

int a = 3;

コンパイラはまず int a = 3 を処理し、次にスタック上に変数 a への参照を作成します。単語があるかどうかを調べる 額面値 3 のアドレスが見つからない場合は、リテラル値 3 を格納するアドレスを開き、a を 3 のアドレスにポイントします。次に、 b の参照変数を作成した後、 int b = 3; を処理します。スタック上にはすでにリテラル値 3 があるため、b は 3 のアドレスを直接指します。このように、a と b が同時に 3 を指す状況が発生します。


特別な注意は、このリテラル値の参照はクラスオブジェクトの参照とは異なることです。 2 つのクラス オブジェクトの参照が同時に同じオブジェクトを指しているとします。一方のオブジェクト参照変数がオブジェクトの内部状態を変更すると、もう一方のオブジェクト参照変数はその変更を即座に反映します。対照的に、リテラル参照の値を変更しても、そのリテラルへの別の参照の値も変更されることはありません。上の例のように、a と b の値を定義した後、a=4 に設定すると、b は 4 にはなりませんが、3 に等しくなります。コンパイラ内で、a=4; が検出されると、スタックにリテラル値 4 があるかどうかが再検索され、存在しない場合は、値 4 がすでに存在する場合はアドレスを再度開きます。を指定すると、このアドレスを直接指すことになります。したがって、a の値が変化しても b の値には影響しません。


もう1つは、Integer、String、Double、および対応する基本データ型をラップするその他のクラスなどのクラスデータをパッケージ化することです。これらのタイプのデータはすべてヒープ内に存在します。Java は new() ステートメントを使用して、実行時に必要に応じて動的に作成されることをコンパイラーに明示的に伝えるため、より柔軟ですが、時間がかかるという欠点があります。

JAVA では、データを保存できる場所が 6 つあります。

1. 登録します。これは、プロセッサ内の他のメモリ領域とは異なる場所に配置されているため、最も高速なメモリ領域です。ただし、レジスタの数は非常に限られているため、必要に応じてコンパイラによってレジスタが割り当てられます。直接制御することはできませんし、プログラム内でレジスタの存在のヒントを感じることもできません。

2. スタック。汎用 RAM にありますが、「スタック ポインタ」を介してプロセッサによってサポートされます。スタック ポインタが下に移動すると、新しいメモリが割り当てられ、上に移動すると、そのメモリが解放されます。これは、レジスタに次いで高速かつ効率的にストレージを割り当てる方法です。プログラムを作成するとき、JAVA コンパイラは、スタック ポインタを上下に移動するための適切なコードを生成する必要があるため、スタックに格納されているすべてのデータの正確なサイズと有効期間を把握している必要があります。この制約によりプログラムの柔軟性が制限されるため、一部の Java データ (具体的にはオブジェクト参照) はスタックに格納されますが、Java オブジェクトはスタックに格納されません。

3. ヒープ。すべての JAVA オブジェクトを保存するために使用される一般的なメモリ プール (RAM にも存在します)。ヒープがスタックとは異なる利点は、コンパイラーがヒープから割り当てる記憶域の量を知る必要がなく、また、保存されたデータがヒープ内でどのくらい存続するかを知る必要もないことです。したがって、ヒープ上にストレージを割り当てる際の柔軟性が非常に高くなります。オブジェクトを作成する必要がある場合は、新しいコードを 1 行記述するだけで、このコード行が実行されると、ストレージがヒープに自動的に割り当てられます。もちろん、この柔軟性は対応するコードによって支払われる必要があります。ヒープを使用したスト​​レージの割り当ては、スタックを使用したスト​​レージよりも時間がかかります。

4. 静的ストレージ。ここでの「静的」とは「固定された位置にある」という意味です。静的ストレージには、プログラムの実行中に存在するデータが保存されます。キーワード static を使用すると、オブジェクトの特定の要素を静的として識別できますが、JAVA オブジェクト自体が静的記憶領域に格納されることはありません。

5. 一定のストレージ。定数値は通常、プログラム コード内に直接保存され、決して変更できないため安全です。組み込みシステムでは定数自体が他の部分から分離される場合があるため、この場合は ROM

6 に保存することを選択できます。データが完全にプログラムの外部で存続する場合、データはプログラムの制御から解放され、プログラムが実行されていないときでも存在できます。
速度に関しては、次の関係があります:

レジスタ<ヒープ<その他

『上記の文章は「Javaで考える」からの抜粋です』

質問1:

質問 2:

String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true

質問 3:

String str1 =new String ("abc");
String str2 =new String ("abc");
System.out.println(str1==str2); // false

関数内で定義されたいくつかの基本型変数とオブジェクト参照変数は、関数のスタック メモリに割り当てられます。

変数がコードのブロック内で定義されると、Java はスタック上の変数にメモリ領域を割り当てます。変数のスコープを超えると、Java は変数に割り当てられたメモリ領域を自動的に解放します。すぐに別の用途に使われました。

ヒープ メモリは、new によって作成されたオブジェクトと配列を保存するために使用されます。

ヒープに割り当てられたメモリは、Java 仮想マシンの自動ガベージ コレクターによって管理されます。


ヒープ内に配列またはオブジェクトを生成した後、スタック内の特別な変数を定義して、スタック内のこの変数の値がヒープ メモリ内の配列またはオブジェクトの最初のアドレスと等しくなるようにすることもできます。この変数はスタック内にあり、配列やオブジェクトの参照変数となります。


参照変数は、配列またはオブジェクトに名前を付けることと同じであり、将来的には、スタック内の参照変数を使用して、プログラム内のヒープ内の配列またはオブジェクトにアクセスできるようになります。

具体的には: スタックとヒープは、Java が Ram にデータを保存するために使用する場所です。 C++ とは異なり、Java はスタックとヒープを自動的に管理するため、プログラマはスタックやヒープを直接設定できません。


Java のヒープは、クラス オブジェクトが領域を割り当てるランタイム データ領域です。これらのオブジェクトは、new、newaray、anewarray、multianewarray などの命令によって作成され、プログラム コードを明示的に解放する必要はありません。ヒープはガベージ コレクションされます。ヒープの利点は、メモリ サイズを動的に割り当てることができることです。また、実行時にメモリが動的に割り当てられ、Java のガベージ コレクタがこれらの使用されなくなったデータを自動的に収集するため、有効期間を事前にコンパイラに伝える必要がありません。ただし、実行時に動的にメモリが割り当てられるため、アクセス速度が遅いという欠点があります。

      栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量(,int, short, long, byte, float, double, boolean, char)和对象句柄。  

      栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:  

int a = 3;  
int b = 3;  

编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。这时,如果再令a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。  

String是一个特殊的包装类数据。可以用:  

String str = new String("abc");
String str = "abc";

   

两种的形式来创建,第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。  
而第二种是先在栈中创建一个对String类的对象引用变量str,然后查找栈中有没有存放"abc",如果没有,则将"abc"存放进栈,并令str指向”abc”,如果已经有”abc” 则直接令str指向“abc”。 

          比较类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==,下面用例子说明上面的理论。  

String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true

   

可以看出str1和str2是指向同一个对象的。 

String str1 =new String ("abc");
String str2 =new String ("abc");
System.out.println(str1==str2); // false

   

用new的方式是生成不同的对象。每一次生成一个。  

   因此用第二种方式创建多个”abc”字符串,在内存中其实只存在一个对象而已. 这种写法有利与节省内存空间. 同时它可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。  

   另一方面, 要注意: 我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,创建了String类的对象str。(不一定,因为如果事先没有,那么就会创建,这就是创建对象了,如果原来就有,那就指向那个原来的对象就可以了)!对象可能并没有被创建!而可能只是指向一个先前已经创建的对象。只有通过new()方法才能保证每次都创建一个新的对象。由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。 

更多Java 堆内存与栈内存详细介绍相关文章请关注PHP中文网!

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。