深入理解java方法调用与参数传递,解决以下问题:
  1. Java方法调用是如何传递参数的?
  2. 被调用方法对调用方法内的变量有什么影响?
  3. java能使用返回值void的中间方法对变量进行加工吗?
  4. 为什么静态成员变量的改变影响是全局的?
  5. 同一个方法同时被多个线程调用线程安全吗?
1,每个线程都有一个方法链,即虚拟机栈。虚拟机栈是线程私有的,是方法执行的内存模型,每个栈帧都对应一个执行方法。开始执行,栈帧入栈,执行完毕,栈帧出栈。

2,局部变量表slot中的值:
基本类型为字面值,String等引用reference类型为指向堆内句柄的指针或直接指向堆内对象的指针,总之是地址值。(由于reference类型在Java虚拟机规范中只规定了一个指向对象的引用,并没有定义这个引用应该通过何种方式去定位、访问堆中的对象的具体位置,所以对象访问方式也是取决于虚拟机实现而定的。目前主流的访问方式有使用句柄和直接指针两种)
3,方法中变量的使用基于局部变量表的slot索引值:
每个方法执行都对应一个栈帧,都有独立的局部变量表,方法执行过程中 方法内部通过变量表的slot的索引值来定位和使用变量。所以一个方法调用另一个方法时,不可能将“某个栈的某个栈帧的某个索引值”这种定位描述传递过去,所以 方法调用时的参数传递采用值传递——基本类型的字面值、reference的地址值。
4,方法调用通过操作数栈实现参数传递:
概念模型下,栈帧上下独立,调用方法对应栈帧中局部变量表和操作数栈都均不受影响;虚拟机优化下,下面栈帧的操作数栈可以与上面栈帧的局部变量表重叠,无需额外的参数复制,调用方法对应栈帧中操作数栈内的值改变,局部变量表不受影响。

5,被调用方法对调用方法的影响:
首先,由上可知,每个栈帧的局部变量表都是独立的,不受方法调用影响。
值操作不会影响到调用方法变量,无论是基本类型的字面值还是引用类型的地址值;
a=10; //int a
a=“aaa”; //String a
a=new User(); //User a
实例操作改变了引用类型地址值指向的堆内实例信息,堆是共享的,所以调用方法内所看到的实例信息也是被改变的;
     a.setName("aaa"); //User a
6,既然我们已经说过,每个栈帧的局部变量表都是独立的,参数传递又是值传递,为什么同一实例的方法执行会影响到成员变量,甚至是线程安全?reference类型可能是改变了实例信息,那么基本类型呢?
由于,存储在局部变量表0位Slot中的是指向自身实例的reference类型“this”。对成员变量的操作,即是对this实例信息的操作。同时单例多线程下,会造成线程安全。 线程安全解决方式:
  1. 原子操作(基本类型的读取和赋值):volatile关键字;
  2. 非原子操作:synchronized,Lock
  3. 非原子操作+指令重排序结果受影响:synchronized、 lock+volatile
  4. ThreadLocal<T>;
7,那么你说实例成员变量是因为0位Slot的引用类型“this”,那么静态成员变量呢?他为什么会在多例情况下,受到方法执行的影响,导致多例多线程的线程安全呢?
由于在类加载过程中的准备阶段,静态变量的内存被分配到方法区,赋初值(另外,初始化阶段,通过<Clinit>类构造器被初始化,且只执行一次)。而方法区和堆一样是线程共享的内存区域,即对所有线程的所有类型的所有实例的所有方法都是共享的,,,,,就是说谁都能使用它并更改它,且对整个虚拟机产生影响。
  • 静态方法:静态方法中是没有this的引用,所以静态方法中不能直接使用实例变量只能使用静态变量(类变量),直接操作方法区中共享的变量内存;
  • 实例方法:多例情况下,每个this指向一个实例,可以理解为通过操作每个this,间接操作着它们共享的静态变量(实际上,仍然是直接操作方法区中共享的变量内存);
注意:
  • 静态变量在类加载中被立即解析成指向方法区内存的直接引用(指针),所以执行过程是直接操作方法区共享的变量内存。
  • 实例变量被 立即解析 惰性解析 为相对偏移量,即相对对象头(实例所在内存地址的起始位置)的偏移值,以此来定位不同实例中的变量。
简单理解为:
  • 方法调用的参数传递是值传递:基本类型字面值、引用类型地址值;
  • 方法执行对应栈帧的局部变量表是独立的,不受方法调用影响;
  • 值操作不会影响调用方法,但实例操作可以改变堆内实例信息,堆内存是共享的。
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐