java 程序运行的基础知识
JVM 线程栈 到 函数运行
每一个JVM线程来说启动的时候都会创建一个私有的线程栈。一个jvm线程栈用来存储栈帧,jvm线程栈和C语言中的栈很类似,它负责管理局部变量、部分运算结果,同时也参与到函数调用和函数返回的工作中。JVM规范中运行线程栈的大小可以是固定的或者是动态分配的,也可以是根据一定规则计算的。不同jvm对栈的实现会不同,一些可能提供给开发人员自己控制jvm线程栈初始大小的方式;对于动态分配来说也可能提供对jvm最大和最小值的设置。
当计算一个线程需要的分配的大小超出了固定值、或者设置的最大值,jvm会抛出StackOverflowError。而对于动态分配栈来说,如果内存不能够提供足够的空间来满足最小值、或者需要的值JVM会抛出 OutOfMemoryError
栈帧,可以理解成一个函数执行的环境,它管理参数、局部变量、返回值等等。
每个栈帧都包括一个管理局部变量的数组( local variables),这个数组的单元数量在编译成字节码的时候就能确定了。对于32-bit 一个单位能够存放 boolean, byte, char, short, int, float, reference,returnAddress;连续两个单位就能够用来存储long 、double。局部变量数组的下标是从0开始,一般而言0位置存储的是this,后面接着是函数的参数,再是函数中出现的局部变量。
每个栈帧也都包括一个(LIFO)操作栈的数据结构(operand stack),它的大小同样也可以在编译的时候确定,创建的时候会是个空栈。举个简单的例子,来描述它公用,对于int a+b来说,先把push a 进入栈中,再朴实 b 进入入栈中,然后 同时pop 两个值执行iadd 指令,再将其加后的结果push入栈中完成指令。
除开以上两个关键的结构,每个栈帧还有常量池( run-time constant pool)、异常抛出管理等结构。在此就不一一详细说来了,可以参考其他资料。
再来通过一个简单的 Demo 来说明,一个栈帧的工作。首先,我们来看这样的一个函数:
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个,类型fload,push 入栈;
fcmpg:比较两个单精度浮点数。如果两数大于结果为1,相等则结果为0,小于的话结果为-1;
ifge:跳转指令;
iconst_x:push 常量x入栈;
istore_x:pop栈存入局部变量数组第x个;
iload_x:读取局部变量数组第x个,入栈;
ireturn:函数结束返回int型;
细心点观察可以发现i开头指代int,f开头指代fload,load代表载入,if代表跳转等等,其中字节码的操作码定义也是有一定意义的,详情可以翻译jvm字节码相关标准。再来看看,jvm如何在栈帧结构上执行情况,以具体调用comp(1.02,2.02)为例:
Java 的 Class
说字节码,一定少不了.class。不妨,以一个demo类 来具体看class 的内容,类非常简单,两个函数一个say,另外一个就是上面的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 来解析编译的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 函数结束返回;
在 public int comp(float, float) code 这段代码里面就能看到上面提到的字节码运行的例子。有了个感性认识,其实大体看到class文件里面,除了字节码指令外,还包括了常量pool,访问标志(public 等),类的相关信息(属性、函数、常量等)。因为前面用的是 -g:node进行编译的,其他模式下还可以有其他扩展、调试信息也包括在class里面。官方给出的class文件格式,详细如下:
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]; }
magic: 就是非常有名的 0xCAFEBABE ,一个标识class文件;
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字符串解析。

Outils d'IA chauds

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool
Images de déshabillage gratuites

Clothoff.io
Dissolvant de vêtements AI

Video Face Swap
Échangez les visages dans n'importe quelle vidéo sans effort grâce à notre outil d'échange de visage AI entièrement gratuit !

Article chaud

Outils chauds

Bloc-notes++7.3.1
Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Dreamweaver CS6
Outils de développement Web visuel

SublimeText3 version Mac
Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Guide du nombre parfait en Java. Nous discutons ici de la définition, comment vérifier le nombre parfait en Java ?, des exemples d'implémentation de code.

Guide de Weka en Java. Nous discutons ici de l'introduction, de la façon d'utiliser Weka Java, du type de plate-forme et des avantages avec des exemples.

Guide du nombre de Smith en Java. Nous discutons ici de la définition, comment vérifier le numéro Smith en Java ? exemple avec implémentation de code.

Dans cet article, nous avons conservé les questions d'entretien Java Spring les plus posées avec leurs réponses détaillées. Pour que vous puissiez réussir l'interview.

Java 8 présente l'API Stream, fournissant un moyen puissant et expressif de traiter les collections de données. Cependant, une question courante lors de l'utilisation du flux est: comment se casser ou revenir d'une opération FOREAK? Les boucles traditionnelles permettent une interruption ou un retour précoce, mais la méthode Foreach de Stream ne prend pas directement en charge cette méthode. Cet article expliquera les raisons et explorera des méthodes alternatives pour la mise en œuvre de terminaison prématurée dans les systèmes de traitement de flux. Lire plus approfondie: Améliorations de l'API Java Stream Comprendre le flux Forach La méthode foreach est une opération terminale qui effectue une opération sur chaque élément du flux. Son intention de conception est

Guide de TimeStamp to Date en Java. Ici, nous discutons également de l'introduction et de la façon de convertir l'horodatage en date en Java avec des exemples.

Les capsules sont des figures géométriques tridimensionnelles, composées d'un cylindre et d'un hémisphère aux deux extrémités. Le volume de la capsule peut être calculé en ajoutant le volume du cylindre et le volume de l'hémisphère aux deux extrémités. Ce tutoriel discutera de la façon de calculer le volume d'une capsule donnée en Java en utilisant différentes méthodes. Formule de volume de capsule La formule du volume de la capsule est la suivante: Volume de capsule = volume cylindrique volume de deux hémisphères volume dans, R: Le rayon de l'hémisphère. H: La hauteur du cylindre (à l'exclusion de l'hémisphère). Exemple 1 entrer Rayon = 5 unités Hauteur = 10 unités Sortir Volume = 1570,8 unités cubes expliquer Calculer le volume à l'aide de la formule: Volume = π × r2 × h (4

Java est un langage de programmation populaire qui peut être appris aussi bien par les développeurs débutants que par les développeurs expérimentés. Ce didacticiel commence par les concepts de base et progresse vers des sujets avancés. Après avoir installé le kit de développement Java, vous pouvez vous entraîner à la programmation en créant un simple programme « Hello, World ! ». Une fois que vous avez compris le code, utilisez l'invite de commande pour compiler et exécuter le programme, et « Hello, World ! » s'affichera sur la console. L'apprentissage de Java commence votre parcours de programmation et, à mesure que votre maîtrise s'approfondit, vous pouvez créer des applications plus complexes.
