ホームページ  >  記事  >  Java  >  Javaのヒープ、スタック、スタックの違いは何ですか? Java のヒープ、スタック、スタックの違いの概要

Javaのヒープ、スタック、スタックの違いは何ですか? Java のヒープ、スタック、スタックの違いの概要

不言
不言転載
2018-11-20 15:54:252566ブラウズ

この記事では、Java におけるヒープ、スタック、スタックの違いについて説明します。 Java におけるヒープ、スタック、スタックの違いについての紹介は、参考になると思います。

正式な内容を始める前に、スタックはヒープとスタックの総称です。 ;

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

#2. スタックの利点は、CPU に直接配置されているレジスタに次いでアクセス速度がヒープよりも速いことです。ただし、スタックに格納されるデータのサイズと有効期間を決定する必要があり、柔軟性に欠けるという欠点があります。さらに、スタック データを共有することもできます。詳細についてはポイント 3 を参照してください。ヒープの利点は、メモリ サイズを動的に割り当てることができ、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 b = 3;

コンパイラは最初に int a = 3 を処理し、まずスタック上に変数 a への参照を作成し、次に検索します。リテラル値があるかどうか out 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() ステートメントを使用して、実行時に必要に応じて動的に作成されることをコンパイラーに明示的に伝えるため、より柔軟ですが、時間がかかるという欠点があります。

4. String是一个特殊的包装类数据。即可以用String str = new String("abc");的形式来创建,也可以用String str = "abc";的形式来创建(作为对比,在JDK 5.0之前,你从未见过Integer i = 3;的表达式,因为类与字面值是不能通用的,除了String。而在JDK 5.0中,这种表达式是可以的!因为编译器在后台进行Integer i = new Integer(3)的转换)。前者是规范的类的创建过程,即在Java中,一切都是对象,而对象是类的实例,全部通过new()的形式来创建。Java 中的有些类,如DateFormat类,可以通过该类的getInstance()方法来返回一个新创建的类,似乎违反了此原则。其实不然。该类运用了单 例模式来返回类的实例,只不过这个实例是在该类内部通过new()来创建的,而getInstance()向外部隐藏了此细节。那为什么在String str = "abc";中,并没有通过new()来创建实例,是不是违反了上述原则?其实没有。

5. 关于String str = "abc"的内部工作。Java内部将此语句转化为以下几个步骤:

(1)先定义一个名为str的对String类的对象引用变量:String str;

(2)在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的String类的对象o,并 将o的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。如果已经有了值为"abc"的地址,则查找对象o,并返回o的地址。

(3)将str指向对象o的地址。

值得注意的是,一般String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用!

为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。

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

 注意,我们这里并不用str1.equals(str2);的方式,因为这将比较两个字符串的值是否相等。==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真值。而我们在这里要看的是,str1与str2是否都指向了同一个对象。 
结果说明,JVM创建了两个引用str1和str2,但只创建了一个对象,而且两个引用都指向了这个对象。

我们再来更进一步,将以上代码改成:

 String str1 = "abc"; 
 String str2 = "abc"; 
 str1 = "bcd"; 
 System.out.println(str1 + "," + str2); //bcd, abc 
 System.out.println(str1==str2); //false

 这就是说,赋值的变化导致了类对象引用的变化,str1指向了另外一个新对象!而str2仍旧指向原来的对象。上例中,当我们将str1的值改为"bcd"时,JVM发现在栈中没有存放该值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。

事实上,String类被设计成为不可改变(immutable)的类。如果你要改变其值,可以,但JVM在运行时根据新值悄悄创建了一个新对象,然 后将这个对象的地址返回给原来类的引用。这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良 影响。

再修改原来代码:

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

str3这个对象的引用直接指向str1所指向的对象(注意,str3并没有创建新对象)。当str1改完其值后,再创建一个String的引用 str4,并指向因str1修改值而创建的新的对象。可以发现,这回str4也没有创建新的对象,从而再次实现栈中数据的共享。

我们再接着看以下的代码。

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

创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。

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

创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。 

以上两段代码说明,只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。

6. 数据类型包装类的值不可修改。不仅仅是String类的值不可修改,所有的数据类型包装类都不能更改其内部的值。

7. 结论与建议:

(1)我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,我们创建了String类的对象str。担心陷阱!对象可能并没有被创建!唯一可以肯定的是,指向 String类的引用被创建了。至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,除非你通过new()方法来显要地创建一个新的对象。因 此,更为准确的说法是,我们创建了一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc"的String类。清醒地认 识到这一点对排除程序中难以发现的bug是很有帮助的。

(2)使用String str = "abc";的方式,可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。这个思想应该是 享元模式的思想,但JDK的内部在这里实现是否应用了这个模式,不得而知。

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

(4)由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。

栈(stack):是一个先进后出的数据结构,通常用于保存方法(函数)中的参数,局部变量. 
在java中,所有基本类型和引用类型都在栈中存储.栈中数据的生存空间一般在当前scopes内(就是由{...}括起来的区域).

堆(heap):是一个可动态申请的内存空间(其记录空闲内存空间的链表由操作系统维护),C中的malloc语句所产生的内存空间就在堆中.

Java では、new xxx() を使用して構築されたすべてのオブジェクトはヒープに格納されます。そのため、ガベージ コレクターはオブジェクトが参照されていないことを検出すると、そのオブジェクトを自動的に破棄します。オブジェクトの生存空間に制限はありません。

## 参照型がそれを指している限り、どこでも使用できます。 #JAVA では、データを保存できる場所が 6 つあります:

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

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

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

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

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

6 に置くことを選択できます。 . 非 RAM ストレージ。データが完全にプログラムの外部で存続する場合、データはプログラムの制御なしで存在でき、プログラムが実行されていないときでも存在できます。
速度の点では、次の関係があります:
##登録<ヒープ<その他


##『上記の段落は「Java で考える」より抜粋』

ここでは、主にヒープとスタックの関係について説明します。

ヒープ: ヒープはヒープです。これは、いわゆる動的メモリであり、不要なときにメモリをリサイクルして、新しいメモリ要求に割り当てることができます。メモリ内のデータは順序付けされていません。つまり、最初に割り当てられたメモリ間には必要な位置関係がありません。その後に割り当てられるメモリには優先順位はありません。通常、Malloc はユーザーによって自由にヒープが割り当てられ、手動で解放する必要があります。

スタック:スタックです。実際には、入口と出口が 1 つだけのキュー、つまり先入れ後出し (First In Last Out) で、最初に割り当てられたメモリは後で解放されなければなりません。通常、関数パラメータ値やローカル変数などを格納するためにシステムによって自動的に割り当てられ、自動的にクリアされます。

また、ヒープはグローバルであり、スタックは各関数が入るときに小さな部分に分割され、関数が静的に戻るときに解放されます。グローバル変数 (new によって取得された変数) はヒープに配置され、ローカル変数はスタックに配置されるため、関数が戻ると、すべてのローカル変数が失われます。

実際、実際のアプリケーションでは、スタックは主にメソッド呼び出しを保存するために使用されます。ヒープはオブジェクトのストレージに使用されます。

JAVA の基本型には、実際には特別な処理が必要です。 JAVA では、new によって作成されたオブジェクトは「ヒープ」に格納されるため、基本型などの小さくて単純な変数を作成するために new を使用することは、多くの場合あまり効率的ではありません。したがって、JAVA では、これらの型に対して C および C と同じメソッドが使用されます。つまり、new を使用して作成する代わりに、「参照」ではない「自動」変数を作成します。この変数はその「値」を所有し、スタックに配置されるため、効率が向上します。

以上がJavaのヒープ、スタック、スタックの違いは何ですか? Java のヒープ、スタック、スタックの違いの概要の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcnblogs.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。