5JVM如何处理异常
异常实例构造昂贵
生成堆栈信息,逐一访问当前线程的Java栈帧,记录各种调试信息(方法名,类名,文件名,第几行)
异常实例构造昂贵
生成堆栈信息,逐一访问当前线程的Java栈帧,记录各种调试信息(方法名,类名,文件名,第几行)
静态绑定:解析时能识别具体的目标方法(重载)
动态绑定:运行过程中根据调用者的动态类型来识别目标方法的情况。
执行时根据实际类型在其方法表中根据索引值获取目标方法。索引值是链接时的指代符号。(重写)
动态绑定与静态绑定相比,仅多出几个内存解引用操作:
调用指令,5种
即时编译优化手段:内联缓存、方法内联
方法内联:将方法体编译进来取代方法调用,减少了压栈帧,访问,弹出栈帧操作
一种加快动态绑定的优化技术
缓存虚方法调用者类型及其目标方法,以后直接调用该类型对应方法(没有方法内联,还是方法调用),没有缓存则退化为方法表动态绑定。
虚方法调用包括 invokevirtual 指令和 invokeinterface 指令。如果这两种指令所声明的目标方法被标记为 final,那么 Java 虚拟机会采用静态绑定。否则,Java 虚拟机将采用动态绑定,在运行过程中根据调用者的动态类型,来决定具体的目标方法。
Java 虚拟机的动态绑定是通过方法表这一数据结构来实现的。方法表中每一个重写方法的索引值,与父类方法表中被重写的方法的索引值一致。在解析虚方法调用时,Java 虚拟机会纪录下所声明的目标方法的索引值,并且在运行过程中根据这个索引值查找具体的目标方法。
Java 虚拟机中的即时编译器会使用内联缓存来加速动态绑定。Java 虚拟机所采用的单态内联缓存将纪录调用者的动态类型,以及它所对应的目标方法。
当碰到新的调用者时,如果其动态类型与缓存中的类型匹配,则直接调用缓存的目标方法。否则,Java 虚拟机将该内联缓存劣化为超多态内联缓存,在今后的执行过程中直接使用方法表进行动态绑定。
三大步骤:加载、链接、初始化
基本数据类型由Java虚拟机原先定义好,引用数据类型,泛型会被擦除,数组由虚拟机直接生成,类和接口对应字节流。
定义:查找字节流并创建类结构
数据源:jar文件、class文件、网络数据源等
数组没有字节流由Java虚拟机直接生成,其他类需借助类加载器完成查找字节流过程。
功能:加载类,提供命名空间
类的唯一性是由类加载器实例以及类的全名确定。
同一串字节流由不同的类加载器加载,会得到两个不同的类。可借此,来运行同一个类的不同版本。
类加载器接收到加载请求时,先将请求转发给父类加载器,附加在其没有找到该类时,它再尝试加载。避免重复加载。
三个阶段:验证、准备、解析
.class文件加载到JVM前,类不知道其他类及其他类的方法、字段对应的具体地址,若引用其他类成员时,JAVA编译器会生成一个符号引用指代目标方法类名、方法名、参数类型、返回值类型
解析目的就是将指代符号解析为实际引用,若引用的类未加载则触发加载此类(但未必链接及初始化它)
JVM规范要求在执行前完成引用符号的解析,没有要求连接过程中完成解析
为常量值的字段赋值,执行clinit方法
clinit方法:Java编译器会把所有final静态字段赋值和静态代码块中的代码置于clinit方法中
初始化触发时机
加载是指查找字节流,并且据此创建类的过程。加载需要借助类加载器,在Java虚拟机中,类加载器使用了双亲委派模型,即接收到加载请求时,会先将请求转发给父类加载器。
链接,是指将创建成的类合并至Java虚拟机中,使之能够执行的过程。链接还分验证、准备和解析三个阶段。其中,解析阶段为非必须的。
初始化,则是为标记为常量值的字段赋值,以及执行 < clinit > 方法的过程。类的初始化仅会被执行一次,这个特性被用来实现单例的延迟初始化。
1 | public static Object getInstance(boolean flag) { |
会加载,不会链接、初始化。flag=true时链接、初始化。
5部分
HotSpot为例,JVM需将方法去的字节码文件转换为机器码文件,两种方式
解释执行快不需等待,即时编译实际运行速度快。HotSpot默认采用混合模式,结合两者有点,先解释执行字节码,后将热点代码以方法为代码即时编译。
HotSpot JVM采用多技术提升启动性能及峰值性能,重要手段:即时编译。
20%代码占用80%计算资源,二八原则。80%不常用代码采用解释执行,20%热点代码即时编译。
HotSpot内置多个即时编译器:C1 、C2 、 Graal
在编译时间和生成代码的执行效率取舍
JAVA7开始HosPot默认采用分次编译:热点代码首先C1编译,之后热点中的热点C2编译
HosPot即时编译在额外的编译线程中进行,为了不干扰应用正常运行。根据CPU数目设置线程数,按1:2配置C1、C2
计算资源充足的情况下,解释执行和编译执行同时进行,下次调用该方法时替换解释执行。
即时编译器因为有程序的运行时信息,优化效果更好,代码效率更高,程序峰值性更好
主要内容
java并发编程核心原理
12并发工具类
9中并发设计模式
4大并发实战案例
学习目标
从单一的知识和技术中“跳出来”,高屋建瓴地看问题,并逐步建立自己的知识体系。
掌握java并发编程技术背后的逻辑关系以及应用场景。(建立并发问题的全景图,理解并发问题本质。)
推荐《操作系统原理》,并发编程最早的应用领域就是操作系统
synchronized、wait()、notify() 不过是操作系统领域里管程模型的一种实现,条件变量 Condition也是管程里的概念。单独理解管中窥豹,站在理论模型高度会发现知识原来如此简单。
什么是管程?
管程作为一种解决并发问题的模型,是继信号量模型之后的一项重大创新,它与信号量在逻辑上是等价的(可以用管程实现信号量,也可以用信号量实现管程),但是相比之下管程更易用。
并发编程三个核心问题:分工、同步、互斥。
分工指的是如何高效地拆解任务并分配给线程,而同步指的是线程之间如何协作,互斥则是保证同一时刻只允许一个线程访问共享资源。
Java SDK 并发包很大部分内容都是按照这三个维度组织的,例如 Fork/Join 框架就是一种分工模式,CountDownLatch 就是一种典型的同步方式,而可重入锁则是一种互斥手段。
选择,永远比努力更重要。选择拼搏于细分行业里的夕阳产业,是多么愚蠢。
找到自己的问题,才是最重要的。
这三年属于被“骂”的最多的三年,做的东西被同行“骂”,汇报被领导“骂”,被“骂”的多了,渐渐就意识到自己的问题了。
驱除虚妄,才能进步
所有的失败都可以归结为“错估了形势,低估了敌人,高估了自己”。
《驱除虚妄,才能进步》
一种轻量级的线程
协程是在用户态调度切换的成本更低,线程是在内核态中调度
协程比线程栈要小得多,协程栈的大小往往只有几K或者几十K,线程栈大小差不多有1M
线程同步意味着等待,线程等待是严重浪费。协程等待成本没那么高。
软件事务内存(Software Transactional Memory,简称STM)
数据库事务 MVCC Multi-Version Concurrency Control 多版本并发控制
读未提交,也就是允许读到未提交的数据,这种情况下查询是不会使用锁的,可能会产生脏读、不可重复读、幻读等情况。
读已提交就是只能读到已经提交的内容,可以避免脏读的产生,属于 RDBMS 中常见的默认隔离级别(比如说 Oracle 和 SQL Server),但如果想要避免不可重复读或者幻读,就需要我们在 SQL 查询的时候编写带加锁的 SQL 语句(我会在进阶篇里讲加锁)。
可重复读,保证一个事务在相同查询条件下两次查询得到的数据结果是一致的,可以避免不可重复读和脏读,但无法避免幻读。MySQL 默认的隔离级别就是可重复读。
可串行化,将事务进行串行化,也就是在一个队列中按照顺序执行,可串行化是最高级别的隔离等级,可以解决事务读取中所有可能出现的异常情况,但是它牺牲了系统的并发性。
Actor模型本质:以Actor作为基本计算单元的计算模型
在面向对象编程里面,一切都是对象;
在Actor模型里,一切都是Actor,并且Actor之间是完全隔离的,不会共享任何变量。
Actor异步模型缺点:
Java需要借助第三方类库使用Actor模型,如Akka、Vert.x
1 | //该Actor当收到消息message后, |
Actor模型异步消息机制:发送消息仅仅是发送出去,接收到后可能不会立即处理
内部可保存消息表,接收到消息放入消息表,如果消息表中有数据则新消息不会马上得到处理
单线程处理消息,无并发问题
Actor不仅适用于并发计算,且适用于分布式计算
发送消息和接收消息的Actor可以不在一个进程中,也可以不在同一台机器上,只需要知道对方的地址。
基础的计算单元,具体来讲包括三部分能力
1 | //累加器 |