转《深入理解Java虚拟机》学习笔记之最后总结

编译器

Java是编译型语言,按照编译的时期不同,编译器可分为:

  1. 前端编译器:其实叫编译器的前端更合适些,它把*.java文件转变成*.class文件,如Sun的Javac、Eclipse JDT中的增量式编译器ECJ;
  2. JIT编译器:虚拟机的后端运行期编译器(Just In Time Compiler),它把字节码转变成机器码,如HotSpot VMd C1、C2编译器;
  3. AOT编译器:静态提前编译器(Ahead Of Time Compiler),它直接把*.java文件编译成本地机器码,如GUN Compiler for the Java(GCJ)、Excelsior JET;

前端编译器做了许多针对编码过程的优化措施来改善程序员的编码风格和提高编码效率,如相当多新生的Java语法特性,都是靠前端编译器的“语法糖”来实现 的;而虚拟机设计团队把性能的优化集中到了JIT编译器,这样可以让那些不是有Javac产生的Class文件也同样能享受到编译器优化所带来的好处。

Java程序最初是通过解释器(Interpreter)进行解释执行的,后来在部分的商用虚拟机(Sun HotSpot、IBM J9)中,当虚拟机发现某个方法或代码块的运行特别频繁,就会把这些代码认定为“热点代码(Hot Spot Code)”,为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器 称为即时编译器(Just In Time Compiler)。

尽管并不是所有的Java虚拟机都采用解释器和编译器并存的架构,但许多主流的商用虚 拟机如HotSpot、J9等都同时包含解释器和编译器:当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。当程序运 行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码之后,可以获取更高的执行效率。

HotSpot虚拟机中内置了两个即时编译器分别是Client Compiler和Server Compiler,或者分别称为C1和C2。但无论是采用的编译器是Client Compiler还是Server Compiler,解释器和编译器搭配使用的方式在虚拟机中都被称为“混合模式(Mixed Mode)”,不过可以通过参数-Xint强制虚拟机运行于“解释模式(Interpreted Mode)”,此时编译器完全不介入工作,全部代码都使用解释方式执行;另外也可以使用参数-Xcomp强制虚拟机运行于“编译模式(Compiled Mode)”,这时将优先采用编译方式执行,但解释器仍然要在编译无法进行的情况下介入执行。

编译优化

由于即时编译器编译本地代码需要占用程序运行时间,要编译出优化程度更高的代码,所花 费的时间就可能越长;而且想要编译出优化程度更高的代码,解释器可能还要替编译器收集性能监控信息,这对解释执行的速度也有影响。为了在程序启动响应速度 和运行效率之间达到最佳平衡,HotSpot虚拟机将会逐渐启用分层编译策略,分层编译的概念在JDK1.6时期出现,后来一直处于改进阶段,最终在 JDK1.7的Server模式虚拟机中作为默认编译策略开启。分层编译根据编译器编译、优化的规模与耗时,划分出不同的编译层次,其中包括:

  • 第0层:程序解释执行,解释器不开启性能监控功能(Profiling),可触发第1层编译;
  • 第1层:也称为C1编译,将字节码编译为本地代码,进行简单可靠的优化,如有必要将加入性能监控的逻辑;
  • 第2层(或2层以上):也称为C2编译,也是将字节码编译为本地代码,但是会启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化;

实施分层编译后,Client Compiler和Server Compiler将会同时工作,许多代码都可能会被多次编译,用Client Compiler获取更高的编译速度,用Server Compiler来获取更好的编译质量,在解释执行的时候也无需再承担收集性能监控信息的任务。

在运行过程中被即时编译器编译的“热点代码”有两类:被多次调用的方法和被多次执行的循环体,但不管是那种都是以整个方法作为编译对象的,因为这种编译方式发生在方法的执行过程中,因此be形象地称为栈上替换(On Stack Replacement,OSR)。

要知道一段代码是不是热点代码,是不是需要触发即时编译,这个行为称为热点探测,目前主要的热点探测判定方式有两种:

  • 基于采样的热点探测:虚拟机会周期性地检查各个线程的栈顶,如果发现某个(或某些) 方法经常出现在栈顶,那这个方法就是“热点方法”。基于采样的热点探测优点是实现简单高效;缺点是结果不精准。(比如某个线程阻塞了,栈顶一直是方法A, 虚拟机周期性采样都只是探测到这个方法A……)
  • 基于计数器的热点探测:采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的阈值就认定它是“热点方法”。

HotSpot虚拟机中使用的是第二种——基于计数器的热点探测方法,因此它为每个方法准备了两个计数器:方法调用计数器和回边计数器。在确定了虚拟机运行参数的前提下,这两个计数器都有一个确定的阈值,当计数器超过了阈值,就会触发JIT编译。

方法内联编译器优化重要优化手段,因为它不仅消除了方法调用成本,更重要的意义是它是其他优化手段的基础,因为只有把代码集中了才能更方便和有效地进行优化。如果想查看即时编译的情况可以使用参数:-XX:+PrintCompilation。

JVM内存模型

主内存和工作内存

  • 所有的变量都存储在主内存中
  • 每个线程都还有自己的工作内存,拥有主内存的对象的拷贝
  • 线程只能操作自己的工作内存,线程间的交互只能通过主内存通讯

内存间交互操作:java内存模型定义了8种原子操作,jvm要报保证每一个操作为原子操作:

  1. lock(锁定,作用于主内存的变量);
  2. unlock(解锁,作用于主内存的变量);
  3. read(读取,作用于主内存);
  4. load(载入,放入到工作内存中);
  5. use(作用于工作内存的变量);
  6. assign(赋值,作用于工作内存);
  7. store(存储,作用于工作内存的变量);
  8. write(写入,作用于主内存的变量);

 

如果要把一个变量从主内容复制到工作内存,那就要顺序地执行read和load操作,如果把变量从工作内存同步回主内存,就要顺序地执行store和write操作。注意java内存模型只要求上述两个操作必须按顺序执行,而没有保证是连续执行。
以上8中操作必须满足的规则:

  1. 不允许出现read和load,store和write操作之一单独出现
  2. 不允许一个线程丢弃他的最近的assign操作
  3. 不允许一个线程无原因的把数据从线程的工作内存同步到主内存中
  4. 一个新变量只能在主内存中”诞生”,不允许在工作内存中直接使用一个未被初始化的变量
  5. 一个变量在同一个时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会解锁;
  6. 如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量之前,需要重新执行load或assign操作初始化变量的值;
  7. 如果一个变量事先没有被lock操作锁定,那就不允许对他执行unlock操作
  8. 对一个变量执行unlock之前,必须先把变量同步会主内存(执行store,write);

volatile
volatile--java虚拟机提供的轻量级的同步机制。2个重要特性。1是保证此变量对所有线程可见,2是禁止指令重排序优化,

  • 每次
    使用之前都要先刷新,执行引擎看不到不一致的情况,保证可见性;但volatile变量在各个线程的工作内存中不存在一致性问题(也可以存在不一致的情
    况),但java里面的运算并非原子操作,导致volatile变量的运算在并发下一样是不安全的。(java内存模型规定,load和use动作连
    续,store和write动作连续)
  • 指令重排从硬件上来讲,指令重排序指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理。但并不是说指令任意重排,CPU需要能正确处理指令依赖情况以保障程序能得出正确的执行结果。

性能方面:volatile的读操作的性能消耗与普通变量几乎没有差别,但是写操作则可能会慢一些,因为他需要在本地代码中插入许多内存屏蔽指令来保证处理器不发生乱序执行。

以下场景仍需要同步:

  • 运算结果并不依赖变量的当前值,或者能够保证只有单一的线程修改变量的值;
  • 变量不需要与其他的状态变量共同参与不变约束;

原子性、可见性和有序性(总结):

  • 原子性:read,load,assign,use,write
  • 可见性:java内存模型是通过变量修改后将新值同步回主存,在变量读取前从主存刷新变量值这种依赖主存作为传递媒介的方式来实现可见性的。volatile,synchronized,final(this引用逃逸除外;)
  • 有序性:如果在本地线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的;

逃逸分析:当变量(或者对象)在方法中分配后,其指针有可能被返回或者被全局引用,这样就会被其他过程或者线程所引用,这种现象称作指针(或者引用)的逃逸(Escape)。(来自互联网)

现行发生原则

指的是java内存模型中定义的两项操作之间的偏序关系,如果说操作A先行发生于操作B,其实就是说在发生操作B之前,操作A产生的影响能被操作B观察到,“影响”包括修改了内存中共享变量的值、发送了消息、调用了方法等。

java内存模型下的“天然的”先行发生关系:

  1. 程序次序规则:一个线程,代码顺序(控制流顺序);
  2. 管程锁定规则:
  3. volatile变量规则:写操作先行发生于后面的读操作
  4. 线程启动规则:start()方法先行发生于此线程的每一个动作;
  5. 线程终止规则:join
  6. 线程中断规则:
  7. 对象终结规则;初始化先于finalize();
  8. 传递性;

线程状态

  • 新建new
  • 运行runable
  • 无期限等待waiting
  • 期限等待timed waiting
  • 阻塞blocked
  • 结束terminated

线程安全的实现方法

  • 互斥同步(阻塞同步):互斥是因,同步是果;互斥是方法,同步是目的
    java.util.concurrent.ReentranLock,比synchron增加了一些高级特性:等待可中断、可实现公平锁、以及锁可以绑定多个条件;
  • 非阻塞同步:(通俗的说就是不断地重试,知道成功为止),乐观的并发策略,需要硬件指令集的支持;

锁优化

    • 自旋锁与自适应自选(cas)
    • 锁消除(判定依据是逃逸分析),String类,字符串相加,JDK1.5之前转化为StringBuffer类(线程安全);JDK1.5及以后,之后会StringBuilder
    • 锁粗化,范围扩大
    • 轻量级锁,jDK1.6加入,
    • 偏向锁
时间: 2024-10-27 10:02:23

转《深入理解Java虚拟机》学习笔记之最后总结的相关文章

java虚拟机学习笔记

笔记 1.编译顺序:                 编译器                     虚拟机      虚拟机          java源文件*.java------->字节码*.class------>类装载器--->执行引擎 一个.class文件只能包含一个类或接口.因此.java文件中定义了多少类,编译时就会生成多少.class文件(内部类不算). 2.java程序可以选择两种方式访问底层系统,由程序员选择:(1).通过java程序调用javaapi调用本地方法,

java虚拟机学习笔记2

笔记 11.数组数组也是类的对象.具有相同类型和维数的数组属于同一个类(不管长度只看维数).数组的长度属于对象实例.多维数组也是一维数组.如二 维数组,即为一个一维数组,该一维数组的每个元素是一个数组的引用.数组和普通对象一样也存储在堆中.数组名为数组的引用,通过索引即数组标号来访问数组内容. 12.异常在java栈帧的帧数据区内保存有针对该方法的异常表的引用.异常表记载了该方法的字节码(*.class)受catch子句保护的范围(即try子句里的 字节码).当某个方法抛出异常时,虚拟机在对应的

java虚拟机学习笔记1

笔记 1.编译顺序:                 编译器                     虚拟机      虚拟机          java源文件*.java------->字节码*.class------>类装载器--->执行引擎 一个.class文件只能包含一个类或接口.因此.java文件中定义了多少类,编译时就会生成多少.class文件(内部类不算). 2.java程序可以选择两种方式访问底层系统,由程序员选择:(1).通过java程序调用javaapi调用本地方法,

《深入理解Java虚拟机》读书笔记

背景 并发处理的广泛应用是使得Amdahl定律代替摩尔定律成为计算机性能发展的源动力的根本原因,也是人类压榨计算机运算能力最有力的武器 Amdahl 定律通过系统中的并行化与串行化的比重来描述多处理器系统能获得的运算加速能力. 摩尔定律则用于描述处理器晶体管数量与运行效率之间的发展关系. 这两个定律的更替代表了近年来硬件发展从追求处理器频率到追求多核心并行处理的发展过程. 高效并发 物理机上的并发解决方案 在当前这个多核处理器时代,"让计算机并发执行若干个运算任务"和"更充分

周志明的《深入理解JAVA虚拟机》中基于栈的指令集和基于寄存器的指令集,要好好学习

这个知识点是我以前没有的,所以我以前发贴表示完全不理解JAR字节码的执行过程及以本地代码交互的过程. 现在有了这个知识点. 那对JVM的运行机制就了解了大概了. 周志明的<深入理解JAVA虚拟机>这书是本好书呀. 但由于我个人阅读习惯,三天之内要草草看一次的.所以没有按书上代码操作. 以后工作如何有应用时,再慢慢深入吧. ~~~~~~~~~~~~~~~ NET CLR 和 Java VM 都是堆疊式虛擬機器(Stack-Based VM),也就是說,它們的指令集(Instruction Set

深入理解java虚拟机的故障处理工具_java

前言 本文主要给大家介绍的是java虚拟机的故障处理工具,文中提到这些工具包括: 名称 主要作用 jps JVM process Status Tool, 显示指定系统内所有的HotSpot虚拟机进程.通常是本地主机 jstat JVM Statistics Monitoring Tool,用于收集HotSpot虚拟机各方面的运行数据 jinfo Configuration Info for java, 显示虚拟机配置信息 jmap Memory Map for Java, 生成虚拟机的内存存储

java-《深入理解Java虚拟机》有关methodHandle的代码问题?

问题描述 <深入理解Java虚拟机>有关methodHandle的代码问题? 以下代码书上说是输出"i am grandfather",但远行实际输出为"i am father" class Test { class GrandFather { void thinking() { System.out.println("i am grandfather"); } } class Father extends GrandFather {

深入理解Java虚拟机:JVM高级特性与最佳实践

目 录 [ - ] <深入理解Java虚拟机:JVM高级特性与最佳实践>前言 <深入理解Java虚拟机:JVM高级特性与最佳实践>内容特色 <深入理解Java虚拟机:JVM高级特性与最佳实践>目录 第1章 走近Java 1.1 概述 1.2 Java技术体系 1.3 Java发展史 1.4 展望Java技术的未来 1.4.1 模块化 1.4.2 混合语言 1.4.3 多核并行 1.4.4 进一步丰富语法 1.4.5 64位虚拟机 1.5 实战:自己编译JDK 1.5.1

深入理解JAVA虚拟机--Idea远程执行本地Java代码

工程配置 上传文件配置 运行结果 源代码 联系作者 今天在看深入理解JAVA虚拟机的9.3节,作者实现了一个远程执行功能.这个功能可以在远程服务器中临时执行一段程序代码,而去不依赖jdk版本,不改变原有服务端程序的部署,不依赖任何第三方库,不入侵原有的程序,不会对原有程序运行带来任何影响.程序的原理可以去看书,本文主要结合IDEA把使用过程记录一下 工程配置 新建一个工程,把书中的5个类倒入,然后写一个测试类(test),这个类的代码就是要让远程服务器自动执行的.test.jsp是用来触发远程服

《深入Java虚拟机》笔记:指令集 (转)

<深入Java虚拟机>笔记:指令集   指令 含义 iconst_m1 把int型常量-1压入栈中 iconst_0 把int型常量压入栈中 fconst_1 把float型常量1压入栈中 lconst_2 把long型常量2压入栈中 dconst_3 把double型常量3压入栈中 bipush byte1 把byte1转换成int型压入栈中 sipush byte1,byte2 把byte1,byte2组成的short转换成int压入栈中 aconst_null 把空对象压入栈中 ldc