您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

java 程序运行的基础知识

2025/3/15 5:25:22发布13次查看
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.:()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 #8 = utf8 ()v #9 = utf8 code #10 = utf8 say #11 = utf8 comp #12 = utf8 (ff)i #13 = utf8 stackmaptable #14 = nameandtype #7:#8 // :()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.:()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 ()); 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字符串解析。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product