ホームページ  >  記事  >  Java  >  Java定数プールの詳細なグラフィックとテキストの説明

Java定数プールの詳細なグラフィックとテキストの説明

尚
転載
2019-11-30 16:36:541982ブラウズ

Java定数プールの詳細なグラフィックとテキストの説明

Java 定数プールは永遠のトピックであり、面接官のお気に入りです。さまざまな質問があります。Xiaocai は定数プールについてすでに聞いているので、今回はそれをうまくまとめます。

推奨: java ビデオ チュートリアル

jvm 仮想メモリの配布:

Java定数プールの詳細なグラフィックとテキストの説明

プログラム カウンターは、 jvm でプログラムを実行する パイプラインにはいくつかのジャンプ命令が格納されていますが、これは高度すぎて Xiao Cai には理解できません。

ローカル メソッド スタックは、jvm がオペレーティング システムのメソッドを呼び出すために使用するスタックです。

仮想マシン スタックは、Java コードを実行するために jvm によって使用されるスタックです。

メソッド領域には、いくつかの定数、静的変数、クラス情報などが格納されます。これは、メモリ内のクラス ファイルの格納場所として理解できます。

仮想マシン ヒープは、Java コードを実行するために jvm によって使用されるヒープです。

Java の定数プールは、実際には、静的定数プールと実行時定数プールの 2 つの形式に分かれています。

いわゆる静的定数プールは、*.class ファイル内の定数プールです。クラス ファイル内の定数プールには、文字列 (数値) リテラルだけでなく、クラスとメソッドの情報も含まれており、絶対に占有されます。クラスファイルの一部ではなく、スペースの大部分。

ランタイム定数プールとは、jvm 仮想マシンがクラスのロード操作を完了した後、クラス ファイル内の定数プールをメモリにロードし、メソッド領域に保存することを意味します。これを定数プールと呼ぶことがよくあります。 . は、メソッド領域のランタイム定数プールを指します。

次に、インターネット上でよく知られている定数プールの例をいくつか引用して説明します。

String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
          
System.out.println(s1 == s2);  // true
System.out.println(s1 == s3);  // true
System.out.println(s1 == s4);  // false
System.out.println(s1 == s9);  // false
System.out.println(s4 == s5);  // false
System.out.println(s1 == s6);  // true

まず、Java で == 演算子を直接使用する場合、比較されるのは 2 つの文字列の内容ではなく、参照アドレスであることを説明します。内容を比較します。

s1 == s2 は非常に分かりやすいです。s1 と s2 に値を代入するとき、どちらも文字列リテラルを使用します。平たく言えば、文字列を直接書き出すことを意味します。コンパイル時に、この種のリテラルは、再利用を実現するためにクラス ファイルの定数プールに直接配置されます。ランタイム定数プールをロードした後、s1 と s2 は同じメモリ アドレスを指すため、それらは等しくなります。

s1 == s3 には落とし穴があります。s3 は動的に結合された文字列ですが、結合に関係するすべての部分は既知のリテラルです。コンパイル中に、この結合は最適化されます。コンパイラはそれを直接綴ります。つまり、クラスファイルでは String s3 = "Hel" "lo"; が String s3 = "Hello"; に最適化され、s1 == s3 が成立します。

s1 == s4 は確かに等しくありません。s4 も結合されていますが、新しい String("lo") 部分は既知のリテラルではなく、予測不可能な部分です。コンパイラはそれを最適化しません。結果文字列不変定理と組み合わせると、s4 がどこに割り当てられるかは誰にも分からないため、アドレスは異なるはずです。アイデアを明確にするために簡単な図を示します:

Java定数プールの詳細なグラフィックとテキストの説明s1 == s9 は等しくありません、そして理由は同様です。s7 と s8 は値を割り当てるときに文字列リテラルを使用しますが、それらが結合されるときはs9 に, 2 つの変数として, s7 と s8 は予測できません. 結局のところ, コンパイラはコンパイラであり, インタプリタとして使用できないため, 最適化は行われません. 実行されると, 新しい文字列は s7 と s8 によって結合されますヒープ内のアドレスになりますが、メソッド領域の定数プール内の s1 アドレスと同じにすることはできません。

Java定数プールの詳細なグラフィックとテキストの説明

s4 == s5 は説明の必要はありません。絶対に等しくありません。両方ともヒープ内にありますが、アドレスが異なります。

s1 == s6 これら 2 つの等価性は、完全にインターン メソッドによるものです。s5 はヒープ内にあり、コンテンツは Hello です。インターン メソッドは、Hello 文字列を定数プールに追加して、それを返そうとします。定数プールにはすでに Hello 文字列が存在するため、インターン メソッドはアドレスを直接返し、s1 はコンパイル中にすでに定数プールを指しているため、s1 と s6 は同じアドレスを指しており、等しくなります。

この時点で、非常に重要な 3 つの結論を導き出すことができます。

定数プールをよりよく理解するには、コンパイル時の動作に注意を払う必要があります。

ランタイム定数プールの定数は、基本的に各クラス ファイルの定数プールから取得されます。

プログラムの実行中、定数が定数プールに手動で追加されない限り (インターン メソッドの呼び出しなど)、jvm は定数を定数プールに自動的に追加しません。

上記には文字列定数プールのみが関係します。実際には、整数定数プール、浮動小数点定数プールなどもありますが、それらはすべて似ています。ただし、数値定数プールに手動で定数を追加することはできません。タイプ定数プールです。定数プールの定数はプログラムの起動時に決定されます。たとえば、整数定数プールの定数範囲は -128 ~ 127 です。定数プールではこの範囲の数値のみを使用できます。

実践

理論についてはここまで述べたので、実際の定数プールについて触れてみましょう。

前に述べたように、クラス ファイルには静的定数プールがあります。この定数プールはコンパイラによって生成され、Java ソース ファイルにリテラルを格納するために使用されます (この記事ではリテラルのみに焦点を当てます)。次の Java コードがあります:

 String s = "hi";

为了方便起见,就这么简单,没错!将代码编译成class文件后,用winhex打开二进制格式的class文件。如图:

Java定数プールの詳細なグラフィックとテキストの説明

简单讲解一下class文件的结构,开头的4个字节是class文件魔数,用来标识这是一个class文件,说白话点就是文件头,既:CA FE BA BE。

紧接着4个字节是java的版本号,这里的版本号是34,因为笔者是用jdk8编译的,版本号的高低和jdk版本的高低相对应,高版本可以兼容低版本,但低版本无法执行高版本。所以,如果哪天读者想知道别人的class文件是用什么jdk版本编译的,就可以看这4个字节。

接下来就是常量池入口,入口处用2个字节标识常量池常量数量,本例中数值为00 1A,翻译成十进制是26,也就是有25个常量,其中第0个常量是特殊值,所以只有25个常量。

常量池中存放了各种类型的常量,他们都有自己的类型,并且都有自己的存储规范,本文只关注字符串常量,字符串常量以01开头(1个字节),接着用2个字节记录字符串长度,然后就是字符串实际内容。本例中为:01 00 02 68 69。

接下来再说说运行时常量池,由于运行时常量池在方法区中,我们可以通过jvm参数:-XX:PermSize、-XX:MaxPermSize来设置方法区大小,从而间接限制常量池大小。

假设jvm启动参数为:-XX:PermSize=2M -XX:MaxPermSize=2M,然后运行如下代码:

//保持引用,防止自动垃圾回收
List<String> list = new ArrayList<String>();
        
int i = 0;
        
while(true){
    //通过intern方法向常量池中手动添加常量
    list.add(String.valueOf(i++).intern());
}

程序立刻会抛出:Exception in thread "main" java.lang.outOfMemoryError: PermGen space异常。PermGen space正是方法区,足以说明常量池在方法区中。

在jdk8中,移除了方法区,转而用Metaspace区域替代,所以我们需要使用新的jvm参数:-XX:MaxMetaspaceSize=2M,依然运行如上代码,抛出:java.lang.OutOfMemoryError: Metaspace异常。同理说明运行时常量池是划分在Metaspace区域中。具体关于Metaspace区域的知识,请读者自行搜索。

更多java知识请关注java基础教程栏目。

以上がJava定数プールの詳細なグラフィックとテキストの説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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