字节码中的指令都是基于栈的操作,比如要完成1+1这样的计算,对应的指令如下:
iconst_1 // 将常量1压入栈 iconst_1 iadd // 把栈顶的两个值相加并出栈,然后把结果放回栈 istore_0 // 将栈顶的值放到局部变量表第0个Solt解释执行的好处是下载后启动速度快,但是确定也非常明显:运行速度慢。JIT正是用来解决这个问题的,能够将多次调用的方法、多次执行的循环体编译成本地代码。
优化是个很好玩的题目,记得在参加一次变成比赛的时候用gcc -O3编译之后的代码把printf()都没输出了。。在JIT中比较常见的优化手段有:
手段描述公共子表达式消除 如果一个表达式已经计算过了,那么后面不需要重复计算
数组范围检查消除 并不是必须一次不漏地检查
方法内联 把代码复制到调用方法中
逃逸分析 判断对象是否可能被方法外引用到
程序执行一定会涉及到内存操作,在Java中定义了八种操作来完成:
操作含义lock 把一个变量标识为线程独占状态
unlock 释放变量
read 将变量从主存读取到工作内存
load 将read到的变量值放入工作内存中的副本
use 将工作内存中的变量传递给执行引擎
assign 引擎返回的值传递给工作内存中的副本
store 将工作内存中的变量传递给主存
write 把从工作内存得到的变量写入主存对应的变量中
这里有必要讲一下volatile的作用,在使用到的时候能明白下面两条即可:
如果Java中所有的操作都需要程序员来控制的话,会有大量的重复代码,而且写起来很累,那么我们可以通过先行发生原则来判断并行的两个操作是否存在冲突:
程序次序规则:单线程内按照程序书写顺序。
管程锁定规则:unlock必须在lock之前。
volatile变量规则:写操作先行发生于读操作。
线程启动规则:Thread.start()先于线程的其他任意方法。
线程终止规则:线程中所有的操作都先于对此线程的终止检测。
线程中断规则:interrupt()先于中断检测。
对象终结规则:对象的初始化完成先于它的finalize()方法。
传递规则:如果A先于B、B先于C,那么A先于C。
Thread的底层实现还是比较麻烦的,但是最起码应该知道Thread的状态是如何进行转换:
最后,常见的同步方式是synchronized或者aqs的各种实现,这里就不讲了,因为每个都足够写一大篇。