Java 引用方式与方法调用

Java 的4种引用方式?

在 JDK 1.2 之后,Java 对引用的概念进行了扩充,将引用分为

  1. 强引用 Strong Reference
  2. 软引用 Soft Reference
  3. 弱引用 Weak Reference
  4. 虚引用 Phantom Reference

强引用

Object obj = new Object();

代码中普遍存在的,像上述的引用。只要强引用还在,垃圾收集器永远不会回收掉被引用的对象。

软引用

用来描述一些还有用,但并非必须的对象。软引用所关联的对象,有在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围,并进行第二次回收。如果这次回收还是没有足够的内存,才会抛出内存异常。提供了 SoftReference 类实现软引用。

弱引用

描述非必须的对象,强度比软引用更弱一些,被弱引用关联的对象,只能生存到下一次垃圾收集发生前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。提供了 WeakReference 类来实现弱引用。

虚引用

一个对象是否有虚引用,完全不会对其生存时间够成影响,也无法通过虚引用来取得一个对象实例。为一个对象关联虚引用的唯一目的,就是希望在这个对象被收集器回收时,收到一个系统通知。提供了 PhantomReference 类来实现虚引用。

Java 方法调用

什么是方法调用?

方法调用唯一的任务是确定被调用方法的版本(调用哪个方法),暂时还不涉及方法内部的具体运行过程。

Java的方法调用,有什么特殊之处?

Class文件的编译过程不包含传统编译的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址。这使得Java有强大的动态扩展能力,但使Java方法的调用过程变得相对复杂,需要在类加载期间甚至到运行时才能确定目标方法的直接引用。

Java虚拟机调用字节码指令有哪些?

  • invokestatic:调用静态方法
  • invokespecial:调用实例构造器方法、私有方法和父类方法
  • invokevirtual:调用所有的虚方法
  • invokeinterface:调用接口方法

虚拟机是如何执行方法里面的字节码指令的?

解释执行(通过解释器执行)
编译执行(通过即时编译器产生本地代码)

解释执行

当主流的虚拟机中都包含了即时编译器后,Class文件中的代码到底会被解释执行还是编译执行,只有虚拟机自己才能准确判断。

Javac编译器完成了程序代码经过词法分析、语法分析到抽象语法树,再遍历语法树生成线性的字节码指令流的过程。因为这一动作是在Java虚拟机之外进行的,而解释器在虚拟机的内部,所以Java程序的编译是半独立的实现。

基于栈的指令集和基于寄存器的指令集

什么是基于栈的指令集?

Java编译器输出的指令流,里面的指令大部分都是零地址指令,它们依赖操作数栈进行工作。

计算“1+1=2”,基于栈的指令集是这样的:

iconst_1
iconst_1
iadd
istore_0

两条iconst_1指令连续地把两个常量1压入栈中,iadd指令把栈顶的两个值出栈相加,把结果放回栈顶,最后istore_0把栈顶的值放到局部变量表的第0个Slot中。

什么是基于寄存器的指令集?

最典型的是x86的地址指令集,依赖寄存器工作。
计算“1+1=2”,基于寄存器的指令集是这样的:

mov eax, 1
add eax, 1

mov指令把EAX寄存器的值设为1,然后add指令再把这个值加1,结果就保存在EAX寄存器里。

基于栈的指令集的优缺点?

优点:

  • 可移植性好:用户程序不会直接用到这些寄存器,由虚拟机自行决定把一些访问最频繁的数据(程序计数器、栈顶缓存)放到寄存器以获取更好的性能。
  • 代码相对紧凑:字节码中每个字节就对应一条指令
  • 编译器实现简单:不需要考虑空间分配问题,所需空间都在栈上操作

缺点:

  • 执行速度稍慢
  • 完成相同功能所需的指令熟练多

频繁的访问栈,意味着频繁的访问内存,相对于处理器,内存才是执行速度的瓶颈。

并发与线程

并发与线程的关系?

并发不一定要依赖多线程,PHP中有多进程并发。但是Java里面的并发是多线程的。

什么是线程?

线程是比进程更轻量级的调度执行单位。线程可以把一个进程的资源分配和执行调度分开,各个线程既可以共享进程资源(内存地址、文件I/O),又可以独立调度(线程是CPU调度的最基本单位)。

实现线程有哪些方式?

  • 使用内核线程实现
  • 使用用户线程实现
  • 使用用户线程+轻量级进程混合实现

Java线程的实现

操作系统支持怎样的线程模型,在很大程度上就决定了Java虚拟机的线程是怎样映射的。

Java线程调度

什么是线程调度?

线程调度是系统为线程分配处理器使用权的过程。

线程调度有哪些方法?

  • 协同式线程调度:实现简单,没有线程同步的问题。但是线程执行时间不可控,容易系统崩溃。
  • 抢占式线程调度:每个线程由系统来分配执行时间,不会有线程导致整个进程阻塞的问题。

虽然Java线程调度是系统自动完成的,但是我们可以建议系统给某些线程多分配点时间——设置线程优先级。Java语言有10个级别的线程优先级,优先级越高的线程,越容易被系统选择执行。

但是并不能完全依靠线程优先级。因为Java的线程是被映射到系统的原生线程上,所以线程调度最终还是由操作系统说了算。如Windows中只有7种优先级,所以Java不得不出现几个优先级相同的情况。同时优先级可能会被系统自行改变。Windows系统中存在一个“优先级推进器”,当系统发现一个线程执行特别勤奋,可能会越过线程优先级为它分配执行时间。

线程安全的定义?

当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方法进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。

Java语言操作的共享数据,包括哪些?

  • 不可变
  • 绝对线程安全
  • 相对线程安全
  • 线程兼容
  • 线程对立

不可变

在Java语言里,不可变的对象一定是线程安全的,只要一个不可变的对象被正确构建出来,那其外部的可见状态永远也不会改变,永远也不会在多个线程中处于不一致的状态。

如何实现线程安全?

虚拟机提供了同步和锁机制。

  • 阻塞同步(互斥同步)
  • 非阻塞同步

阻塞同步(互斥同步)

互斥是实现同步的一种手段,临界区、互斥量和信号量都是主要的互斥实现方式。Java中最基本的同步手段就是synchronized关键字,其编译后会在同步块的前后分别形成monitorenter和monitorexit两个字节码指令。这两个字节码都需要一个Reference类型的参数指明要锁定和解锁的对象。如果Java程序中的synchronized明确指定了对象参数,那么这个对象就是Reference;如果没有明确指定,那就根据synchronized修饰的是实例方法还是类方法,去获取对应的对象实例或Class对象作为锁对象。

在执行monitorenter指令时,首先要尝试获取对象的锁。

展开阅读全文

本文系作者在时代Java发表,未经许可,不得转载。

如有侵权,请联系nowjava@qq.com删除。

编辑于

关注时代Java

关注时代Java