深入理解JVM虚拟机笔记五

最后一篇,仅做第一次粗略阅读著作的笔记收尾:


编译技术的优化:

    1.公共子表达式消除:

int res=b*c+a+12*c*b

    编译期拿到这句代码,发现b*c和c*b是一回事,并且在整个计算过程中b,c值不变,于是就处理成了:

int res=E+a+12*E

    节省了一次压栈帧的操作。甚至还会帮你合并同类项进行代数简化。具体的内容,在以后的龙书阅读笔记里会有更详细的记录。


    2.数组边界检查消除

    其实本质上就是java做了安全检查,这是一笔不小的开销,当编译期可以确定在这段代码中数组一定不会越界,就会把安全检查消除,避免过多的开销。(类似的还有自动装箱消除、安全点消除、反射消除)


    3.方法内联

    在以前阅读core java的时候,提到过一次使用final修饰方法可以达到内联优化的效果,当时不是很理解。现在就感觉理解的比较透彻了。

    java中除了invokestatic,invokespecial和final方法之外都被称为虚方法,对于final方法,虽然依旧是invokevirtual触发,但是不用去虚方法表中查询了。java是支持程序员使用动态分派的,但是动态分派将无法在编译期确定所调用的方法版本,因此不能对这类方法做任何的优化(仅仅凭借静态类型不能确定方法,需要运行时根据实际类型分配方法)。但如果对实例方法添加了final,这样的话在编译期我们就已经为final方法做了静态分派,从而可以降低对方法调用的成本!

    不过,并不推荐大量的使用final,一则是不便于维护,二则是应从算法优化、业务逻辑优化的层面上对程序改进,三则是jvm版本不同,所支持的语法规则也不同,不能一概而论,可能今天的性能提升到了明天就成了瓶颈。

    java为了解决虚方法的内敛,提出了CHA的类型继承关系分析技术。当内联开始时,发现方法是一个非虚方法,可以直接内联;发现方法是虚方法,则查询此方法是否只有一个版本,若是,则可以内联,不过这样是有风险的,因此需要一个守护内联,在程序执行的过程中,如果出现了这个类继承关系的改变,就要抛弃代码,重新编译。如果查到多个版本,则会将内联滞空,等到第一次调用这个方法时,记录下调用版本,日后如果版本一致就可以一直使用内联,否则取消内联。

    

    4.逃逸分析

    什么叫逃逸?比如我们在一个方法中定义了一个变量,然后把它传递给了其他的方法,就叫方法逃逸。又比如我们创建了一个对象,这个对象被其他线程使用了,这就称为线程逃逸。

    当虚拟机可以确定不出现逃逸的情况下,会做一些优化:

    1)栈上分配:局部对象如果不会逃逸,分配在栈上(拆分)让它随着栈帧的压出而销毁可能比让它占着内存等待GC回收好很多。

    2)同步消除:不出现线程逃逸的对象,可以消除它的同步开销。

    3)标量替换:不可再分拆的数据类型叫做标量:基本类型都是标量,而对象一般都是聚合量。当虚拟机可以肯定一个对象不会被外部访问,并且可以被拆分,就有可能不创建这个对象,而是直接创建局部变量代替它的拆分。


Java内存模型:

    1.java规定所有变量都必须在主内存中。每一个线程有自己的工作内存(高速缓存),每次从主内存中拿到数据的复制,操作完再复制回主内存,不允许直接读写主内存中的数据。

    2.volatile关键字保证了可见性:也即当某个线程对变量进行了修改,这个修改会被其他线程立刻发现。虽然保证了这个变量在所有线程里的一致性,但是不能保证运算的一致性,比如++操作在java中就是两条操作(可能会更多,看字节码指令),这整个运算的过程的原子性不能保证。

        volatile关键字还禁止了指令重排序优化,避免出现颠倒次序的语句使得线程间的交互出现意想不到的问题。(*)

        冷知识:long和double的字撕裂,可以通过volatile保证原子性(不过基本不用,64位虚拟机可以保证原子性)

    3.内存模型的三性:原子性(内存读写复制操作是保证原子性的(double,long有非原子性协定)、sychronized关键字保证原子性)、可见性(volatile关键字、sychronized关键字、final关键字(不允许发生this逃逸)都保证了可见性)、有序性(指令重排,volatile,sychronized都可以保证有序性)

    4.考虑java并发的时候一定不能按照代码顺序去考虑,要按照时间先行发生的顺序去考虑。先行规则有很多,不在此列举。

  

线程安全:

    1.不可变一定是线程安全的

    2.绝对线程安全:用sychronized包裹保证整个代码块的原子性

    3.相对线程安全:保证一步操作的原子性,不保证多步操作的原子性,如vector等线程安全的容器

    4.线程兼容:对象并不安全,但是可以通过一些手段保证安全

    5.线程对立:无论做什么都无法保证线程的安全,如Thread类下的suspend()和resume()


    1.互斥同步:sychronized和reentrylock

    2.非阻塞同步:不必阻塞,继续操作,出现问题,改面换新。需要硬件支持,CAS技术保证了替换的可能性。

    3.不需要同步的代码:不涉及到资源的共享


    1.自旋锁:锁导致的线程挂起是一笔很大的开销,因此提出了自旋锁的概念,在需要阻塞的地方,让线程进入忙循环,等待其他的线程处理完毕,这需要一部分的内存开销,不适合等待时间特别长的情况。当然也有自适应自旋锁,会根据历史自旋情况决定下次自旋最大次数。

    2.锁消除:不需要同步的地方,自动消除锁

    3.锁粗化:一系列操作都对同一个对象加锁,反复加锁带来的开销很大,不如粗话到最大边界只加一次锁。

    4.轻量级锁(CAS技术)、偏向锁……


猜你喜欢

转载自blog.csdn.net/zkANewer/article/details/79818503