JVMはどのようにして配列の長さを取得しますか
このメモは主に、int arr [] = {1,2,3};など、javaで記述した配列を記録します。次に、int len = arr.length;基になるjvmが
配列の長さを取得する方法; 配列はjvmの最下層は動的に生成されます。つまり、静的ではありません。オブジェクトのハッシュコードに似ています。hashcodeメソッドをオーバーライドしていない場合、オブジェクトのハッシュコードはデフォルトでオブジェクトのメモリアドレスであるため、ハッシュコードも動的に生成されます。 、配列は同じです。配列を定義した後、実行時に配列を動的に生成、追加、削除できるため、配列の長さも動的に生成されます。ここでは、プログラムとHSDBを使用して配列を証明します。 jvmの下部で長さを取得する方法は、最初に次のプログラムを見てください。
public class T0819 {
public static void main(String[] args) {
int arr [] ={
1,2,3};
int length = arr.length;
System.out.println(length);
}
}
javap -verboseT0819.classを使用してバイトコード情報を確認しましょう
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=3, args_size=1
0: iconst_3
1: newarray int
3: dup
4: iconst_0
5: iconst_1
6: iastore
7: dup
8: iconst_1
9: iconst_2
10: iastore
11: dup
12: iconst_2
13: iconst_3
14: iastore
15: astore_1
16: aload_1
17: arraylength
18: istore_2
19: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
22: iload_2
23: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
26: return
LineNumberTable:
line 7: 0
line 8: 16
line 9: 19
line 10: 26
LocalVariableTable:
Start Length Slot Name Signature
0 27 0 args [Ljava/lang/String;
16 11 1 arr [I
19 8 2 length I
}
プログラムカウンター17、arraylengthを見てください。率直に言って、jvmで配列長が動的に取得される方法を理解するには、jvmがバイトコード命令arraylengthを処理する方法によって異なります。ここで、openjdkが配列長を取得する方法のコードブロックを見つけました。 :
CASE(_arraylength):
{
arrayOop ary = (arrayOop) STACK_OBJECT(-1);
CHECK_NULL(ary);
SET_STACK_INT(ary->length(), -1);
UPDATE_PC_AND_CONTINUE(1);
}
int length() const {
return *(int*)(((intptr_t)this) + length_offset_in_bytes());
}
CASEコードは、配列の長さを取得するためのものです。この場合の3行目のコードは、長さを取得してスタックにプッシュすることです。
主に、thisポインターを使用して配列の最初の位置を取得してから追加するlength()メソッドを分析します。配列のオフセットは、配列の長さを取得するために追加されます。次の図を見てください。
((intptr_t)this)=配列の最初の位置を取得します
length_offset_in_bytes()=メモリ内の配列のオフセットを取得して
から追加します。 、アドレスを取得し、配列の長さを取得します
オフセットを取得するために関数length_offset_in_bytes()を見てみましょう
//如果不压缩,则在arrayOopDesc中声明的非静态字段之后分配
//如果压缩,它将占用oopDesc中_klass字段的后半部分
static int length_offset_in_bytes() {
return UseCompressedClassPointers ? klass_gap_offset_in_bytes() :
sizeof(arrayOopDesc);
}
上記のコードを見てください。UseCompressedClassPointersというパラメーターがあります。このパラメーターもjvmチューニングの一部ですが、このパラメーターはどういう意味ですか?上記の注記から、オブジェクトメモリレイアウトにはオブジェクトヘッダー、インスタンスデータ、および配置パディングが
あり、オブジェクトヘッダーはマークワード、タイプポインター、および配列の長さに分割され、タイプポインターの圧縮および非圧縮バイトはそうではないことがわかります。同じ
ですが、UseCompressedClassPointersとUseCompressedoopsの違いは何ですか?
UseCompressedoopsはオブジェクトポインタの長さを圧縮し、UseCompressedClassPointersはKlassオブジェクトポインタの長さを圧縮します。UseCompressedoopsを有効にすると、UseCompressedClassPointersはデフォルトでオンになります
。つまり、UseCompressedoopsにはUseCompressedClassPointersが含まれます。
まず、配列を分析しましょう。
対応するKlassは次のとおりです。TypeArrayKlassインスタンス
対応するoopは次のとおりです。TypeArrayOopインスタンス
そして、jvmのタイプポインタのコードブロックは次のとおりです。
union _metadata {
Klass* _klass; 8B
narrowKlass _compressed_klass; 4B
} _metadata;
それはコンソーシアムであり、コンソーシアム全体が8Bのスペースを占めます。圧縮すると、4Bが使用され、圧縮しない場合は8Bが使用されます。次に、圧縮すると、スペースの無駄になりますか? length_offset_in_bytes()メソッドのコメントが書かれているので、以下で問題全体の分析を開始します。
オブジェクトメモリレイアウト
オブジェクトヘッダー
マークワード
クラスポインタ
配列の長さ
インスタンスデータ
塗りつぶし
我们如果开启了指针压缩的情况下:
Mark Word 8B
Klass pointer 4B
数组长度 4B
那么指针长度 + 数组长度就是=4B+4B=8B
如果我们不压缩就是=8B+4B=12B
所以我们理解下代码注释中的这句话“如果压缩,它将占用oopDesc中_klass字段的后半部分”
如果压缩,类型指针4B,因为我们的
union _metadata {
Klass* _klass; 8B
narrowKlass _compressed_klass; 4B
} _metadata;
是8B,压缩了使用的就是联合体中的_compressed_klass,而我们的数组长度是4B,所以说就是 用的_klass中后半段4B,就是这个意思,也就是说我们的数组长度用的是联合体中的后半段,如果开启了指针压缩。
我们来通过HSDB来查看下我们的数组长度:
我们是开启了指针压缩的,所以是——metadata._compressed_klass,我们再来查看下它的内存视图:
我们关闭指针压缩:-XX:-UseCompressedClassPointers
一看上图就是知道是关闭了指针压缩的