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

简介: 编译器 Java是编译型语言,按照编译的时期不同,编译器可分为: 前端编译器:其实叫编译器的前端更合适些,它把*.java文件转变成*.class文件,如Sun的Javac、Eclipse JDT中的增量式编译器ECJ; JIT编译器:虚拟机的后端运行期编译器(Just In Time C...

编译器

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加入,
    • 偏向锁
相关文章
|
3月前
|
Java API 微服务
2025 年 Java 从入门到精通学习笔记全新版
《Java学习笔记:从入门到精通(2025更新版)》是一本全面覆盖Java开发核心技能的指南,适合零基础到高级开发者。内容包括Java基础(如开发环境配置、核心语法增强)、面向对象编程(密封类、接口增强)、进阶技术(虚拟线程、结构化并发、向量API)、实用类库与框架(HTTP客户端、Spring Boot)、微服务与云原生(容器化、Kubernetes)、响应式编程(Reactor、WebFlux)、函数式编程(Stream API)、测试技术(JUnit 5、Mockito)、数据持久化(JPA、R2DBC)以及实战项目(Todo应用)。
213 5
|
7天前
|
小程序 Java 知识图谱
Java 学习笔记 —— BMI & BMR 计算器
这是一个使用 Java 编写的 BMI 与 BMR 计算器小程序,可输入年龄、性别、身高和体重,计算身体质量指数(BMI)和基础代谢率(BMR),并输出健康评估结果。通过该项目,掌握了 Java 的输入处理、数据验证、条件判断、数学运算及格式化输出等基础知识,是 Java 初学者的理想练习项目。
|
6天前
|
Java
Java 数组学习笔记
本文整理Java数组常用操作:遍历、求和、查找、最值及二维数组行求和等典型练习,涵盖静态初始化、元素翻倍、去极值求平均等实例,帮助掌握数组基础与应用。
|
6月前
|
存储 Java
# 【Java全栈学习笔记-U1-day02】变量+数据类型+运算符
本篇笔记主要围绕Java全栈学习的第二天内容展开,涵盖了变量、数据类型、运算符以及Scanner类的应用。首先介绍了变量的概念与命名规范,以及如何定义和使用变量;接着详细讲解了Java中的基本数据类型,包括整型、浮点型、字符型、布尔型等,并通过实例演示了数据类型的运用。随后,深入探讨了各类运算符(赋值、算术、关系、逻辑)及其优先级,帮助理解表达式的构成。最后,介绍了如何利用Scanner类实现用户输入功能,并通过多个综合示例(如计算圆面积、购物打折、变量交换及银行利息计算)巩固所学知识。完成相关作业将进一步加深对这些基础概念的理解与实践能力。
96 13
|
存储 Java
Java学习笔记 List集合的定义、集合的遍历、迭代器的使用
Java学习笔记 List集合的定义、集合的遍历、迭代器的使用
269 4
|
6月前
|
开发框架 Java 开发工具
【Java全栈学习笔记-U1-day01】Java介绍
本笔记整理了Java学习的基础内容,涵盖程序理解、Java语言特性、JDK安装与配置、Java程序开发工具及编写步骤。重点介绍了Java程序的基本结构、编译和运行过程,以及输出语句的使用。通过实例演示了IDEA创建Java程序的方法,并强调了编码规范和注意事项。适合初学者复习和交流学习。 主要内容: 1. 理解程序:计算机组成、程序定义。 2. 简介:Java语言特点、技术平台、JDK作用。 3. 编写Java程序:编写、编译、运行步骤,基本结构。 4. 输出语句 5. DEA使用:新建工程、保存位置、文件介绍、新建类。 6. 扩展:注释、代码规范、大小写敏感、缩进等。
|
9月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
11月前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
存储 安全 Java
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(基础篇)
从Java环境的搭建到实际代码的编写,从基本用法的讲解到底层原理的剖析,深度解析Java基础知识。本文是《Java学习路线》专栏的起始文章,旨在提供一套完整的Java学习路线,覆盖Java基础知识、数据库、SSM/SpringBoot等框架、Redis/MQ等中间件、设计模式、架构设计、性能调优、源码解读、核心面试题等全面的知识点,并在未来不断更新和完善,帮助Java从业者在更短的时间内成长为高级开发。
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(基础篇)
|
存储 安全 Java
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(进阶篇)
本文是Java基础的进阶篇,对异常、集合、泛型、Java8新特性、I/O流等知识进行深入浅出的介绍,并附有对应的代码示例,重要的地方带有对性能、底层原理、源码的剖析。适合Java初学者。
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(进阶篇)