JVM-JVM是如何执行方法调用的
重载、重写
- void invoke(Object obj, Object... args) { ... }
- void invoke(String s, Object obj, Object... args) { ... }
- invoke(null, 1); // 调用第二个invoke方法
- invoke(null, 1, 2); // 调用第二个invoke方法
- invoke(null, new Object[]{1}); // 只有手动绕开可变长参数的语法糖,
- // 才能调用第一个invoke方法
重载的方法在编译过程中即可完成识别。具体到每一个方法调用,Java 编译器会根据所传入参数的声明类型(注意与实际类型区分)来选取重载方法。选取的过程共分为三个阶段:在不考虑对基本类型自动装拆箱(auto-boxing,auto-unboxing),以及可变长参数的情况下选取重载方法;如果在第 1 个阶段中没有找到适配的方法,那么在允许自动装拆箱,但不允许可变长参数的情况下选取重载方法;如果在第 2 个阶段中没有找到适配的方法,那么在允许自动装拆箱以及可变长参数的情况下选取重载方法。如果 Java 编译器在同一个阶段中找到了多个适配的方法,那么它会在其中选择一个最为贴切的,而决定贴切程度的一个关键就是形式参数类型的继承关系。
在开头的例子中,当传入 null 时,它既可以匹配第一个方法中声明为 Object 的形式参数,也可以匹配第二个方法中声明为 String 的形式参数。由于 String 是 Object 的子类,因此 Java 编译器会认为第二个方法更为贴切。
如果子类定义了与父类中非私有方法同名的方法,而且这两个方法的参数类型相同,那么这两个方法之间又是什么关系呢?如果这两个方法都是静态的,那么子类中的方法隐藏了父类中的方法。如果这两个方法都不是静态的,且都不是私有的,那么子类的方法重写了父类中的方法。
在 Java 中,方法存在重载以及重写的概念,重载指的是方法名相同而参数类型不相同的方法之间的关系,重写指的是方法名相同并且参数类型也相同的方法之间的关系。
Java 虚拟机识别方法的方式略有不同,除了方法名和参数类型之外,它还会考虑返回类型。在 Java 虚拟机中,静态绑定指的是在解析时便能够直接识别目标方法的情况,而动态绑定则指的是需要在运行过程中根据调用者的动态类型来识别目标方法的情况。由于 Java 编译器已经区分了重载的方法,因此可以认为 Java 虚拟机中不存在重载。
例:
- 1 package javap.method;
- 2
- 3 public class Animal {
- 4
- 5 protected static void method1(String str) {
- 6 System.out.println("Animal_static_method1" + str);
- 7 }
- 8
- 9 protected void method2(String str) {
- 10 System.out.println("Animal_not_static_method2" + str);
- 11 }
- 12
- 13 // 1、Java语言:只要方法名、参数类型一致(不考虑返回值),就认为重复定义
- 14 // 如下,java编译器会报:'method2(String)' is already defined in 'javap.method.Animal'
- 15 //int method2(String str) {
- 16 //}
- 17
- 18 // 2、Java 虚拟机:识别方法的关键在于类名、方法名,方法描述符(method descriptor--参数类型、返回类型),
- 19 // 所以如果手动添加上述方法到字节码,JVM是可以准确识别和区分的
- 20
- 21 protected int method3() {
- 22 System.out.println("Animal_not_static_method3");
- 23 return 99;
- 24 }
- 25
- 26 }
- 27
- 28 class Fish extends Animal {
- 29
- 30 // 静态方法不可以写@Override,报错:Method does not override method from its superclass
- 31 // @Override
- 32 protected static void method1(String str) {
- 33 System.out.println("Fish_static_method1" + str);
- 34 }
- 35
- 36 @Override
- 37 protected void method2(String str) {
- 38 System.out.println("Fish_not_static_method2" + str);
- 39 }
- 40
- 41 // 如果改为 void 返回值:报错 attempting to use incompatible return type
- 42 protected int method3() {
- 43 System.out.println("Fish_not_static_method3");
- 44 return 100;
- 45 }
- 46
- 47
- 48
- 49 public static void main(String[] args) {
- 50 Animal animal = new Fish();
- 51 animal.method2("MainMethod2"); // 发生动态绑定
- 52 System.out.println("************");
- 53 // 此方法则是直接调用方法区中静态方法,无需经过方法表
- 54 animal.method1("MainMethod1"); // 静态绑定,因为声明的时候animal是Animal类型
- 55
- 56 System.out.println("########");
- 57 Fish fish = new Fish();
- 58 fish.method2("IsFish");
- 59 }
- 60 }
javap结果:
- 163 public static void main(java.lang.String[]);
- 164 descriptor: ([Ljava/lang/String;)V
- 165 flags: ACC_PUBLIC, ACC_STATIC
- 166 Code:
- 167 stack=2, locals=3, args_size=1
- 168 0: new #11 // class javap/method/Fish
- 169 3: dup
- 170 4: invokespecial #12 // Method "<init>":()V
- 171 7: astore_1
- 172 8: aload_1
- 173 9: ldc #13 // String MainMethod2
- 174 11: invokevirtual #14 // Method javap/method/Animal.method2:(Ljava/lang/String;)V -- 动态绑定
- 175 14: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
- 176 17: ldc #15 // String ************
- 177 19: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 178 22: aload_1
- 179 23: pop
- 180 24: ldc #16 // String MainMethod1
- 181 26: invokestatic #17 // Method javap/method/Animal.method1:(Ljava/lang/String;)V -- 静态绑定,因为声明的时候是Animal类型
- 182 29: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
- 183 32: ldc #18 // String ########
- 184 34: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 185 37: new #11 // class javap/method/Fish
- 186 40: dup
- 187 41: invokespecial #12 // Method "<init>":()V
- 188 44: astore_2
- 189 45: aload_2
- 190 46: ldc #19 // String IsFish
- 191 48: invokevirtual #20 // Method method2:(Ljava/lang/String;)V
- 192 51: return
- 193 LineNumberTable:
- 194 line 49: 0
- 195 line 50: 8
- 196 line 51: 14
- 197 line 52: 22
- 198 line 54: 29
- 199 line 55: 37
- 200 line 56: 45
- 201 line 57: 51
- 202 LocalVariableTable:
- 203 Start Length Slot Name Signature
- 204 0 52 0 args [Ljava/lang/String;
- 205 8 44 1 animal Ljavap/method/Animal;
- 206 45 7 2 fish Ljavap/method/Fish;
- 207 }
- 208 SourceFile: "Animal.java"
桥接
在文中我曾提到,Java 的重写与 Java 虚拟机中的重写并不一致,但是编译器会通过生成桥接方法来弥补。
例1:重写方法的返回类型不一致:
- 1 package javap.method;
- 2
- 3 public interface Customer {
- 4 boolean isVIP();
- 5 }
- 6 class Merchant {
- 7 public Number actionPrice(double price, Customer customer) {
- 8 return price * 0.8;
- 9 }
- 10 }
- 11
- 12 class NaiveMerchant extends Merchant {
- 13 @Override
- 14 public Double actionPrice(double price, Customer customer) {
- 15 if (customer.isVIP()) {
- 16 return price * 0.6;
- 17 } else {
- 18 return (Double) super.actionPrice(price, customer);
- 19 }
- 20 }
- 21 }
- 1 $ java -jar ../asmtools.jar jdis NaiveMerchant.class
- 2 package javap/method;
- 3
- 4 super class NaiveMerchant
- 5 extends Merchant
- 6 version 52:0
- 7 {
- 8
- 9
- 10 Method "<init>":"()V"
- 11 stack 1 locals 1
- 12 {
- 13 aload_0;
- 14 invokespecial Method Merchant."<init>":"()V";
- 15 return;
- 16
- 17 }
- 18
- 19 public Method actionPrice:"(DLjavap/method/Customer;)Ljava/lang/Double;"
- 20 stack 4 locals 4
- 21 {
- 22 aload_3;
- 23 invokeinterface InterfaceMethod Customer.isVIP:"()Z", 1;
- 24 ifeq L18;
- 25 dload_1;
- 26 ldc2_w double 0.6d;
- 27 dmul;
- 28 invokestatic Method java/lang/Double.valueOf:"(D)Ljava/lang/Double;";
- 29 areturn;
- 30 L18: stack_frame_type same;
- 31 aload_0;
- 32 dload_1;
- 33 aload_3;
- 34 invokespecial Method Merchant.actionPrice:"(DLjavap/method/Customer;)Ljava/lang/Number;";
- 35 checkcast class java/lang/Double;
- 36 areturn;
- 37
- 38 }
- 39
- 40 public bridge synthetic Method actionPrice:"(DLjavap/method/Customer;)Ljava/lang/Number;"
- 41 stack 4 locals 4
- 42 {
- 43 aload_0;
- 44 dload_1;
- 45 aload_3;
- 46 invokevirtual Method actionPrice:"(DLjavap/method/Customer;)Ljava/lang/Double;";
- 47 areturn;
- 48
- 49 }
- 50
- 51 } // end Class NaiveMerchant
例2:范型参数类型造成的方法参数类型不一致:
- 1 package javap.method;
- 2
- 3 public interface CustomerNew {
- 4 boolean isVIP();
- 5 }
- 6
- 7 class VIP implements CustomerNew {
- 8
- 9 public boolean isVIP() {
- 10 return true;
- 11 }
- 12 }
- 13
- 14 class MerchantNew<T extends CustomerNew> {
- 15 public double actionPrice(double price, T customer) {
- 16 return price * 0.8;
- 17 }
- 18 }
- 19
- 20 class VIPOnlyMerchant extends MerchantNew<VIP> {
- 21 @Override
- 22 public double actionPrice(double price, VIP customer) {
- 23 return price * 0.9; // 杀熟
- 24 }
- 25 }
- 1 $ java -jar ../asmtools.jar jdis VIPOnlyMerchant.class
- 2 package javap/method;
- 3
- 4 super class VIPOnlyMerchant
- 5 extends MerchantNew
- 6 version 52:0
- 7 {
- 8
- 9
- 10 Method "<init>":"()V"
- 11 stack 1 locals 1
- 12 {
- 13 aload_0;
- 14 invokespecial Method MerchantNew."<init>":"()V";
- 15 return;
- 16
- 17 }
- 18
- 19 public Method actionPrice:"(DLjavap/method/VIP;)D"
- 20 stack 4 locals 4
- 21 {
- 22 dload_1;
- 23 ldc2_w double 0.9d;
- 24 dmul;
- 25 dreturn;
- 26
- 27 }
- 28
- 29 public bridge synthetic Method actionPrice:"(DLjavap/method/CustomerNew;)D"
- 30 stack 4 locals 4
- 31 {
- 32 aload_0;
- 33 dload_1;
- 34 aload_3;
- 35 checkcast class VIP;
- 36 invokevirtual Method actionPrice:"(DLjavap/method/VIP;)D";
- 37 dreturn;
- 38
- 39 }
- 40
- 41 } // end Class VIPOnlyMerchant
符号引用转为实际引用
在编译过程中,我们并不知道目标方法的具体内存地址。因此,Java 编译器会暂时用符号引用来表示该目标方法。这一符号引用包括目标方法所在的类或接口的名字,以及目标方法的方法名和方法描述符。
符号引用存储在 class 文件的常量池之中。根据目标方法是否为接口方法,这些引用可分为接口符号引用和非接口符号引用。我在文章中贴了一个例子,利用“javap -v”打印某个类的常量池,如果你感兴趣的话可以到文章中查看。
- 1 // 在奸商.class的常量池中,#16为接口符号引用,指向接口方法"客户.isVIP()"。而#22为非接口符号引用,指向静态方法"奸商.价格歧视()"。
- 2 $ javap -v 奸商.class ...
- 3 Constant pool:
- 4 ...
- 5 #16 = InterfaceMethodref #27.#29 // 客户.isVIP:()Z
- 6 ...
- 7 #22 = Methodref #1.#33 // 奸商.价格歧视:()D
- 8 ...
上一篇中我曾提到过,在执行使用了符号引用的字节码前,Java 虚拟机需要解析这些符号引用,并替换为实际引用。
对于非接口符号引用,假定该符号引用所指向的类为 C,则 Java 虚拟机会按照如下步骤进行查找。
- 在 C 中查找符合名字及描述符的方法。
- 如果没有找到,在 C 的父类中继续搜索,直至 Object 类。
- 如果没有找到,在 C 所直接实现或间接实现的接口中搜索,这一步搜索得到的目标方法必须是非私有、非静态的。并且,如果目标方法在间接实现的接口中,则需满足 C 与该接口之间没有其他符合条件的目标方法。如果有多个符合条件的目标方法,则任意返回其中一个。
从这个解析算法可以看出,静态方法也可以通过子类来调用。此外,子类的静态方法会隐藏(注意与重写区分)父类中的同名、同描述符的静态方法。
对于接口符号引用,假定该符号引用所指向的接口为 I,则 Java 虚拟机会按照如下步骤进行查找。
- 在 I 中查找符合名字及描述符的方法。
- 如果没有找到,在 Object 类中的公有实例方法中搜索。
- 如果没有找到,则在 I 的超接口中搜索。这一步的搜索结果的要求与非接口符号引用步骤 3 的要求一致。
经过上述的解析步骤之后,符号引用会被解析成实际引用。对于可以静态绑定的方法调用而言,实际引用是一个指向方法的指针。对于需要动态绑定的方法调用而言,实际引用则是一个方法表的索引。具体什么是方法表,我会在下一篇中做出解答。
虚方法调用
- 1 package javap.method;
- 2
- 3 import java.util.Random;
- 4
- 5 public interface Customer {
- 6 boolean isVIP();
- 7 }
- 8 class Merchant {
- 9 public double actionPrice(double price, Customer customer) {
- 10 return price * 0.8;
- 11 }
- 12 }
- 13
- 14 class NaiveMerchant extends Merchant {
- 15 @Override
- 16 public double actionPrice(double price, Customer customer) {
- 17 if (customer.isVIP()) { // (4)invokeinterface 调用接口方法
- 18 return price * priceDiscri(); // (1)invokestatic 调用静态方法
- 19 } else {
- 20 /**
- 21 * (2)invokespecial:用于调用私有实例方法、构造器,以及使用 super 关键字调用父类的实例方法或构造器,
- 22 * 和所实现接口的默认方法
- 23 */
- 24 return super.actionPrice(price, customer); // invokespecial (调用super方法)
- 25 }
- 26 }
- 27
- 28 // 价格歧视
- 29 public static double priceDiscri() { //咱们的杀熟算法太粗暴了,应该将客户城市作为随机数生成器的种子。
- 30 return new Random() // invokespecial (调用构造方法)
- 31 .nextDouble() // (3)invokevirtual (调用虚方法-非私有实例方法)
- 32 + 0.8d;
- 33 }
- 34
- 35 // (5) invokedynamic:用于调用动态方法,比较复杂,此处先不介绍
- 36 }
例子:在实际运行过程中,Java 虚拟机是如何高效地确定每个 Passenger 实例应该去哪条通道的呢?我们一起来看一下。
- abstract class Passenger {
- abstract void passThroughImmigration();
- @Override
- public String toString() { ... }
- }
- class ForeignerPassenger extends Passenger {
- @Override
- void passThroughImmigration() { /* 进外国人通道 */ }
- }
- class ChinesePassenger extends Passenger {
- @Override
- void passThroughImmigration() { /* 进中国人通道 */ }
- void visitDutyFreeShops() { /* 逛免税店 */ }
- }
- Passenger passenger = ...
- passenger.passThroughImmigration();
- 静态绑定(重载-不完全准确)
- 调用静态方法的 invokestatic 指令
- 调用构造器、私有实例方法以及超类非私有实例方法的 invokespecial 指令
- 动态绑定(重写-不完全准确):如下这些都称为虚函数在绝大多数情况下,Java虚拟机需要在运行时根据调用者的动态类型,来确定虚方法调用的目标方法
- 所有非私有实例方法调用都会被编译成 invokevirtual 指令
- 接口方法调用都会被编译成 invokeinterface 指令
Java 虚拟机中采取了一种用空间换取时间的策略来实现动态绑定。它为每个类生成一张方法表,用以快速定位目标方法。那么方法表具体是怎样实现的呢?
方法表
- 何时生成方法表?类加载的(链接下的准备)准备阶段,它除了为静态字段分配内存之外,还会构造与该类相关联的方法表。
- 方法表本质上是一个数组:每个数组元素指向一个当前类及其祖先类中非私有的实例方法。这些方法可能是具体的、可执行的方法,也可能是没有相应字节码的抽象方法。
- 方法表满足两个特质:
- 其一,子类方法表中包含父类方法表中的所有方法;
- 其二,子类方法在方法表中的索引值,与它所重写的父类方法的索引值相同。
我们知道,方法调用指令中的符号引用会在执行之前解析成实际引用。对于静态绑定的方法调用而言,实际引用将指向具体的目标方法。对于动态绑定的方法调用而言,实际引用则是方法表的索引值(实际上并不仅是索引值)。在执行过程中,Java 虚拟机将获取调用者的实际类型,并在该实际类型的虚方法表中,根据索引值获得目标方法。这个过程便是动态绑定。
toString 方法和 passThroughImmigration 方法分别对应 0 号和 1 号,是因为 toString 方法的索引值需要与 Object 类中同名方法的索引值一致。为了保持简洁,这里我就不考虑 Object 类中的其他方法。
实际上,使用了方法表的动态绑定与静态绑定相比,仅仅多出几个内存解引用操作:访问栈上的调用者,读取调用者的动态类型,读取该类型的方法表,读取方法表中某个索引值所对应的目标方法。相对于创建并初始化 Java 栈帧来说,这几个内存解引用操作的开销简直可以忽略不计。那么我们是否可以认为虚方法调用对性能没有太大影响呢?其实是不能的,上述优化的效果看上去十分美好,但实际上仅存在于解释执行中,或者即时编译代码的最坏情况中。这是因为即时编译还拥有另外两种性能更好的优化手段:内联缓存(inlining cache)和方法内联(method inlining)- 后续章节再介绍。下面我便来介绍第一种内联缓存
内联缓存
内联缓存是一种加快动态绑定的优化技术。它能够缓存虚方法调用中调用者的动态类型,以及该类型所对应的目标方法。在之后的执行过程中,如果碰到已缓存的类型,内联缓存便会直接调用该类型所对应的目标方法。如果没有碰到已缓存的类型,内联缓存则会退化至使用基于方法表的动态绑定。
如果没有碰到已缓存的类型,一般的做法可能替换单态内联缓存中的纪录。这种做法就好比 CPU 中的数据缓存,它对数据的局部性有要求,即在替换内联缓存之后的一段时间内,方法调用的调用者的动态类型应当保持一致,从而能够有效地利用内联缓存;但是最坏的情况是,每次相邻两个都是未缓存的类型,每次都要更新缓存;上述JVM的做法,与替换内联缓存纪录的做法相比,它牺牲了优化的机会,但是节省了写缓存的额外开销。
例子:
- 1 package javap.method;
- 2
- 3
- 4 // Run with: java -XX:CompileCommand='dontinline,*.passThroughImmigration' Passenger
- 5 public abstract class Passenger {
- 6 abstract void passThroughImmigration();
- 7
- 8 public static void main(String[] args) {
- 9 Passenger a = new ChinesePassenger();
- 10 Passenger b = new ForeignerPassenger();
- 11 long current = System.currentTimeMillis();
- 12 for (int i = 1; i <= 2000000000; i++) { // 20亿
- 13 if (i % 100000000 == 0) { // 1亿
- 14 long temp = System.currentTimeMillis();
- 15 System.out.println(temp - current);
- 16 current = temp;
- 17 }
- 18 Passenger c = (i < 1000000000) ? a : b; // 10亿
- 19 c.passThroughImmigration();
- 20 }
- 21 }
- 22 }
- 23 class ChinesePassenger extends Passenger {
- 24 @Override void passThroughImmigration() {}
- 25 }
- 26 class ForeignerPassenger extends Passenger {
- 27 @Override void passThroughImmigration() {}
- 28 }
Java 虚拟机中的即时编译器会使用内联缓存来加速动态绑定。Java 虚拟机所采用的单态内联缓存将纪录调用者的动态类型,以及它所对应的目标方法。当碰到新的调用者时,如果其动态类型与缓存中的类型匹配,则直接调用缓存的目标方法。否则,Java 虚拟机将该内联缓存劣化为超多态内联缓存,在今后的执行过程中直接使用方法表进行动态绑定。
在今天的实践环节,我们来观测一下单态内联缓存和超多态内联缓存的性能差距。为了消除方法内联的影响,请使用如下的命令。
java -XX:CompileCommand='dontinline,*.passThroughImmigration' javap.method.Passenger,执行4次,每次结果为1列
- 1 263 272 256 260 // a开始 -- 单态内联缓存
- 2 258 277 269 263
- 3 247 257 248 294
- 4 247 242 249 312
- 5 250 241 249 404
- 6 242 263 248 468
- 7 258 255 242 364
- 8 262 243 242 263
- 9 252 271 252 279
- 10 246 258 246 249
- 11 328 316 296 320 // b开始 -- 劣化为“超多态内联缓存”,在今后的执行过程中直接使用方法表进行动态绑定
- 12 315 339 293 369
- 13 345 323 286 761
- 14 287 314 279 398
- 15 294 311 290 349
- 16 303 341 289 367
- 17 289 345 283 346
- 18 282 315 278 363
- 19 288 401 277 416
- 20 293 388 284 331
java javap.method.Passenger,执行4次,每次结果为1列 (可能会带来方法内联)
- 1 116 83 82 115 // a开始
- 2 143 134 128 149
- 3 122 141 117 141
- 4 131 173 124 128
- 5 124 149 131 116
- 6 120 153 117 117
- 7 115 141 114 114
- 8 115 125 116 114
- 9 114 116 114 135
- 10 113 112 119 132
- 11 158 143 144 168 // b开始
- 12 140 141 142 156
- 13 148 146 145 142
- 14 156 153 144 144
- 15 142 140 141 144
- 16 145 198 307 147
- 17 191 216 236 156
- 18 157 206 163 144
- 19 150 199 152 145
- 20 147 191 162 144
JVM-JVM是如何执行方法调用的的更多相关文章
- 04 JVM是如何执行方法调用的(上)
重载和重写 重载:同一个类中定义名字相同的方法,但是参数类型或者参数个数必须不同. 重载的方法在编译过程中就可完成识别.具体到每一个方法的调用,Java 编译器会根据所传入参数的生命类型来选取重载方法 ...
- 04 JVM是如何执行方法调用的(下)
虚方法调用 Java 里所有非私有实例方法调用都会被编译成 invokevirtual 指令,而接口方法调用会被编译成 invokeinterface 指令.这两种指令,均属于 Java 虚拟机中的虚 ...
- 多态:JVM是如何进行方法调用的
在我们平时的工作学习中写java代码时,如果我们在同一个类中定义了两个方法名和参数类型都相同的方法时,编译器会直接报错给我们.还有在代码运行的时候,如果子类定义了一个与父类完全相同的方法的时候,父类的 ...
- 深入解析多态和方法调用在JVM中的实现
深入解析多态和方法调用在JVM中的实现 1. 什么是多态 多态(polymorphism)是面向对象编程的三大特性之一,它建立在继承的基础之上.在<Java核心技术卷>中这样定义: 一个对 ...
- JVM系列(四):java方法的查找过程实现
经过前面几章的简单介绍,我们已经大致了解了jvm的启动框架和执行流程了.不过,这些都是些无关痛痒的问题,几行文字描述一下即可. 所以,今天我们从另一个角度来讲解jvm的一些东西,以便可以更多一点认知. ...
- 第31篇-方法调用指令之invokevirtual
invokevirtual字节码指令的模板定义如下: def(Bytecodes::_invokevirtual , ubcp|disp|clvm|____, vtos, vtos, invokevi ...
- 图解JVM执行引擎之方法调用
一.方法调用 方法调用不同于方法执行,方法调用阶段的唯一任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程.Class文件的编译过程中不包括传统编译器中的连接步骤,一 ...
- jvm 字节码执行 (一)方法调用
“虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器.硬件.指令集和操作系统层面上,而虚拟机的执行引擎是 由自己实现的,因此可以自行制定指令集 ...
- JVM方法调用过程
JVM方法调用过程 重载和重写 同一个类中,如果出现多个名称相同,并且参数类型相同的方法,将无法通过编译.因此,想要在同一个类中定义名字相同的方法,那么它们的参数类型必须不同.这种方法上的联系就是重载 ...
- JVM(十二):方法调用
JVM(十二):方法调用 在 JVM(七):JVM内存结构 中,我们说到了方法执行在何种内存结构上执行:Java 方法活动在虚拟机栈中的栈帧上,栈帧的具体结构在内存结构中已经详细讲解过了,下面就让我们 ...
随机推荐
- 利用InnoStep在VS编译时自动构建安装包
摘要 很多同学在C/S开发领域或多或少都可能会遇到需要制作安装包的场景,打包的工具也是五花八门,例如有NSIS.InstallShield.Wix Toolset.ClickOnce等等,这里以Inn ...
- 【渗透测试】Cobalt Strike制作钓鱼邮件渗透Windows
目标 在kali中使用Cobalt Strike制作钓鱼邮件,对Windows进行渗透 机器环境 kali(服务端):192.168.175.129 win11(攻击机):192.168.175.12 ...
- Qt+GDAL开发笔记(一):在windows系统mingw32编译GDAL库、搭建开发环境和基础Demo
前言 麒麟系统上做全球北斗定位终端开发,调试工具要做一个windows版本方便校对,北斗GPS发过来的是大地坐标,应用需要的是经纬度坐标,所以需要转换,可以使用公式转换,但是之前涉及到了另一个sh ...
- IDEA: 菜单栏消失的解决办法
解决方案 步骤一 双击shift输入View,点击第一个 步骤二如图所示 至此问题解决
- Django错误:ERRORS: ?: (staticfiles.E001) The STATICFILES_DIRS setting is not a tuple or list. HINT: Perhaps you forgot a trailing comma?
报错的原因是因为我们的STATICFILES_DIRS赋值时,形式不对,其应该赋数组对象,具体如下: 找到settings.py文件, 把 STATICFILES_DIRS=(os.path.join ...
- AcWing 4797. 移动棋子题解
算出数值为 \(1\) 的点离 \((3, 3)\) 的距离即可. #include <iostream> #include <cstring> #include <al ...
- centos7升级内核到最新稳定版
前言 centos7默认的内核版本才3.10,诸如VXLAN.eBPF等特性无法体验,因此需要升级.目前(2022.02)Linux的内核版本已更新到5.16. 步骤 更新仓库 yum update ...
- XSS--labs通关记录
XSS--labs通关记录 level 1(无过滤) 查看网页源代码 <!DOCTYPE html><!--STATUS OK--><html> <head& ...
- 群晖DS218+部署PostgreSQL(docker)
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 起因是懒 最近在开发中要用到PostgreSQL数据库 ...
- 音视频FAQ(一):视频直播卡顿
一.摘要 本文介绍了视频直播卡顿的四个主要原因,用户网络问题.用户设备性能问题.技术路线的选择和实现问题.因本文主要阐述视频直播的卡顿,故技术路线的实现指的是:CDN供应商的实现问题,包含CDN性能不 ...