関数実行までの JVM スレッド スタック
すべての JVM スレッドは、開始時にプライベート スレッド スタックを作成します。 jvm スレッド スタックは、スタック フレームを格納するために使用されます。jvm スレッド スタックは、C 言語のスタックと非常によく似ており、ローカル変数と一部の演算結果の管理を担当し、関数呼び出しと関数の戻りにも関与します。 JVM 仕様の実行スレッド スタックのサイズは、固定することも動的に割り当てることも、特定のルールに従って計算することもできます。スタックの実装は jvm ごとに異なります。開発者に jvm スレッド スタックの初期サイズを制御する方法を提供するものや、jvm の最大値と最小値の設定を提供するものもあります。
スレッドに必要な割り当てサイズの計算が固定値または設定された最大値を超えると、jvm は StackOverflowError をスローします。動的に割り当てられたスタックの場合、メモリが最小値または必要な値を満たすのに十分なスペースを提供できない場合、JVM は OutOfMemoryError
スタック フレームをスローします。これは、パラメーターとローカル変数を管理する関数実行環境として理解できます。 、戻り値など。
各スタックフレームにはローカル変数(ローカル変数)を管理する配列が含まれており、この配列のユニット数はバイトコードにコンパイルされるときに決定されます。 32 ビットの場合、1 つのユニットに boolean、byte、char、short、int、float、reference、returnAddress を格納でき、連続する 2 つのユニットを使用して long および double を格納できます。ローカル変数配列の添字は 0 から始まります。一般的に、0 の位置にこれが格納され、その後に関数のパラメータが格納され、その後に関数に現れるローカル変数が格納されます。
各スタック フレームには、(LIFO) 演算スタック データ構造 (オペランド スタック) も含まれます。そのサイズは、作成時に空のスタックになります。 int a+b の一般的な使用法を説明する簡単な例を挙げてみましょう。まず a をスタックにプッシュし、次に b をスタックにプッシュし、次に 2 つの値を同時にポップして iadd 命令を実行します。そして結果をスタックにプッシュして命令を完了します。
上記の 2 つの主要な構造に加えて、各スタック フレームには定数プール (実行時定数プール)、例外スロー管理、その他の構造もあります。ここでは詳しく説明しませんので、他の情報を参照してください。
簡単なデモを使用して、スタック フレームの動作を説明しましょう。まず、そのような関数を見てみましょう:
public int comp(float number1, float number2){ int result ; if(number1 < number2) result = 1; else result = 2; return result; }
関数内のロジックに対応するバイトコードは次のとおりです:
0: fload_1
1: fload_2
2: fcmpg
3: ifge 11
6: iconst_1
7: istore_3
8: goto 13
11: iconst_2
12: istore_3
13: iload_3
14: ireturn
これらのバイトコード命令 fload について簡単に説明します。 _x: x 番目の変数を取得しますローカル変数配列、load と入力し、プッシュしてスタックにプッシュします。 fcmpg: 2 つの単精度浮動小数点数を比較します。 2 つの数値が 1 より大きい場合、結果は 0 となり、等しい場合、結果は -1 になります。 iconst_x: スタックに定数 x をプッシュします。ローカル変数配列 x 番目; iload_x: x 番目のローカル変数配列を読み取り、スタックにプッシュします。 よく見ると、それがわかります。 i の先頭は int を指し、f の先頭はロードを指します。load はロードを表し、if はジャンプを表します。バイトコードのオペレーション コード定義にも特定の意味があります。詳細は、jvm バイトコード関連に変換できます。規格。特定の呼び出し comp(1.02,2.02) を例として、スタック フレーム構造上で jvm がどのように実行されるかを見てみましょう:Java のクラス
バイトコードに関して言えば、.class は不可欠です。クラスの内容を詳しく見るには、デモ クラスを受講するとよいでしょう。クラスは 2 つの関数のうちの 1 つが非常に単純で、もう 1 つは上記の cmp 関数です。
public class Hello { public void say(){ System.out.println("Hello world!"); } public int comp(float number1, float number2){ int result ; if(number1 < number2) result = 1; else result = 2; return result; } }
javac -g:none Hello.java を使用してこのクラスをコンパイルし、次に javap -c -v Hello.class を使用してコンパイルされたクラスを解析します。
Classfile /src/main/java/com/demo/Hello.class Last modified 2016-10-28; size 404 bytes MD5 checksum 9ac6c800c312d65b568dd2a0718bd2c5public class com.demo.Hello minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #6.#14 // java/lang/Object."<init>":()V #2 = Fieldref #15.#16 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #17 // Hello world! #4 = Methodref #18.#19 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #20 // com/demo/Hello #6 = Class #21 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 say #11 = Utf8 comp #12 = Utf8 (FF)I #13 = Utf8 StackMapTable #14 = NameAndType #7:#8 // "<init>":()V #15 = Class #22 // java/lang/System #16 = NameAndType #23:#24 // out:Ljava/io/PrintStream; #17 = Utf8 Hello world! #18 = Class #25 // java/io/PrintStream #19 = NameAndType #26:#27 // println:(Ljava/lang/String;)V #20 = Utf8 com/demo/Hello #21 = Utf8 java/lang/Object #22 = Utf8 java/lang/System #23 = Utf8 out #24 = Utf8 Ljava/io/PrintStream; #25 = Utf8 java/io/PrintStream #26 = Utf8 println #27 = Utf8 (Ljava/lang/String;)V{ public com.demo.Hello(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public void say(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello world! 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return public int comp(float, float); descriptor: (FF)I flags: ACC_PUBLIC Code: stack=2, locals=4, args_size=3 0: fload_1 1: fload_2 2: fcmpg 3: ifge 11 6: iconst_1 7: istore_3 8: goto 13 11: iconst_2 12: istore_3 13: iload_3 14: ireturn StackMapTable: number_of_entries = 2 frame_type = 11 /* same */ frame_type = 252 /* append */ offset_delta = 1 locals = [ int ] }
getstatic:获取镜头变量; invokevirtual:调用函数; return:void 函数结束返回;
ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }
minor_version 、major_version :指的是java class 文件的版本,一般说class文件的版本是 XX.xx 其中XX 就是major,xx是minor,比如上面demo中的版本是52.0 代表就是 minor 0,major 51.
constant_pool_count:就是常量池元素个数,cp_info constant_pool[constant_pool_count-1] 就是相关的详细信息了。
access_flags:指的是访问标识例如ACC_PUBLIC、ACC_FINAL、ACC_INTERFACE、ACC_SUPER 写过java的相信看名字应该知道啥意思,ACC是access的缩写。
其他具体的,就不一一介绍了详细可以直接参考官方文档。
动态生成java字节码
当然,你可以直接按照官方的class文件格式来直接写 byte[],然后自定义个 class load 载入编写的byte[]来实现动态生成class。不过,这个要求可能也有点高,必须的非常熟悉class文件格式才能做到。这里demo还是借助 ASM 这个类库来简单演示下,就编写下 上面的Hello 不过里面只实现say的方法。如下:
public class AsmDemo { public static final String CLASS_NAME = "Hello"; public static final AsmDemoLoad load = new AsmDemoLoad(); private static class AsmDemoLoad extends ClassLoader { public AsmDemoLoad() { super(AsmDemo.class.getClassLoader()); } public Class<?> defineClassForName(String name, byte[] data) { return this.defineClass(name, data, 0, data.length); } } public static byte[] generateSayHello() throws IOException { ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); classWriter.visit(V1_7, ACC_PUBLIC + ACC_SUPER, CLASS_NAME, null, getInternalName(Object.class), null); //默认初始化函数 Method constructorMethod = Method.getMethod("void <init> ()"); GeneratorAdapter constructor = new GeneratorAdapter(ACC_PUBLIC, constructorMethod, null, null, classWriter); constructor.loadThis(); //每个类都要基础Object constructor.invokeConstructor(Type.getType(Object.class), constructorMethod); constructor.returnValue(); constructor.endMethod(); Method mainMethod = Method.getMethod("void say ()"); GeneratorAdapter main = new GeneratorAdapter(ACC_PUBLIC, mainMethod, null, null, classWriter); main.getStatic(Type.getType(System.class), "out", Type.getType(PrintStream.class)); main.push("Hello world!"); main.invokeVirtual(Type.getType(PrintStream.class), Method.getMethod("void println (String)")); main.returnValue(); main.endMethod(); return classWriter.toByteArray(); } public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException, NoSuchMethodException, SecurityException, IOException { byte[] code = AsmDemo.generateSayHello(); //反射构建 hello 类,调用hello方法。 Class<?> hello = load.defineClassForName(CLASS_NAME, code); hello.getMethod("say", null).invoke(hello.newInstance(), null); } }
关于动态生成字节码用途,一定场景下是可以提升效率与性能,因为动态生成的类和普通的载入类并无太大区别。手工优化后的字节码执行可能比编译的要优,可以替代反射使用的许多场景 同时避免反射的性能消耗。很著名的一个例子,fastJSON 就是使用内嵌 ASM 框架动态生成字节码类,来进行序列和反序列化工作,是目前公认最快的json字符串解析。