在 JDK 1.2 之后,Java 对引用的概念进行了扩充,将引用分为
Object obj = new Object();
代码中普遍存在的,像上述的引用。只要强引用还在,垃圾收集器永远不会回收掉被引用的对象。
用来描述一些还有用,但并非必须的对象。软引用所关联的对象,有在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围,并进行第二次回收。如果这次回收还是没有足够的内存,才会抛出内存异常。提供了 SoftReference 类实现软引用。
描述非必须的对象,强度比软引用更弱一些,被弱引用关联的对象,只能生存到下一次垃圾收集发生前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。提供了 WeakReference 类来实现弱引用。
一个对象是否有虚引用,完全不会对其生存时间够成影响,也无法通过虚引用来取得一个对象实例。为一个对象关联虚引用的唯一目的,就是希望在这个对象被收集器回收时,收到一个系统通知。提供了 PhantomReference 类来实现虚引用。
方法调用唯一的任务是确定被调用方法的版本(调用哪个方法),暂时还不涉及方法内部的具体运行过程。
Class文件的编译过程不包含传统编译的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址。这使得Java有强大的动态扩展能力,但使Java方法的调用过程变得相对复杂,需要在类加载期间甚至到运行时才能确定目标方法的直接引用。
解释执行(通过解释器执行)
编译执行(通过即时编译器产生本地代码)
当主流的虚拟机中都包含了即时编译器后,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语言有10个级别的线程优先级,优先级越高的线程,越容易被系统选择执行。
但是并不能完全依靠线程优先级。因为Java的线程是被映射到系统的原生线程上,所以线程调度最终还是由操作系统说了算。如Windows中只有7种优先级,所以Java不得不出现几个优先级相同的情况。同时优先级可能会被系统自行改变。Windows系统中存在一个“优先级推进器”,当系统发现一个线程执行特别勤奋,可能会越过线程优先级为它分配执行时间。
当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方法进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。
在Java语言里,不可变的对象一定是线程安全的,只要一个不可变的对象被正确构建出来,那其外部的可见状态永远也不会改变,永远也不会在多个线程中处于不一致的状态。
虚拟机提供了同步和锁机制。
互斥是实现同步的一种手段,临界区、互斥量和信号量都是主要的互斥实现方式。Java中最基本的同步手段就是synchronized关键字,其编译后会在同步块的前后分别形成monitorenter和monitorexit两个字节码指令。这两个字节码都需要一个Reference类型的参数指明要锁定和解锁的对象。如果Java程序中的synchronized明确指定了对象参数,那么这个对象就是Reference;如果没有明确指定,那就根据synchronized修饰的是实例方法还是类方法,去获取对应的对象实例或Class对象作为锁对象。
在执行monitorenter指令时,首先要尝试获取对象的锁。
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。