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

Part1 走近Java

chapter 1 走近Java

Java技术体系:
java技术体系

如何理解"一次编译,到处运行":
java机制
Java源码首先被译成字节码,再由不同平台的JVM解析,Java语言在不同平台运行时不需要重新编译,Java虚拟机在执行字节码时,把字节码转换成具体平台中的机器码——Java虚拟机实现了跨平台特性;

Part2 自动内存管理

chapter 2 Java内存区域与内存溢出异常

虚拟机运行时数据区
程序计数器:每个线程都需要一个独立的程序计数器;
Java虚拟机栈:(通常“栈”所指)
Java虚拟机栈
主要指局部变量表——存放编译期就已知的基本数据类型、对象引用等;
本地方法栈(和虚拟机栈类似,为本地方法服务);

Java堆
Java堆是虚拟机管理的内存中最大的一块,被所有线程共享,在虚拟机启动时创建,存放实例对象(注:栈上分配,标量替换会进行优化);Java堆可以划分多个线程私有的分配缓冲区(更好地分配、回收内存);当Java堆内存没有完成实例分配且无法扩展时,就会抛出OutOfMemoryError异常.

方法区(Non-Heap):
线程共享;存储被虚拟机加载的类型信息、常量、静态变量等;运行时常量池是方法区的一部分;

对象的创建分配内存:指针碰撞(bump the pointer),空闲列表(free list);
本地线程分配缓冲(TLAB);
对象的访问定位:句柄访问,指针访问;

chapter 3 垃圾收集器与内存分配策略

判断对象是否存活:

  • 引用计数算法
  • 可达性分析:通过GC Roots的根对象作为起始节点集合;某个对象从GC Roots到这个对象不可达时,证明此对象不可使用;
    可作GCRoots(1)
    可作GCRoots(2)
    引用
  • 强引用:普遍存在的引用赋值,类似Object obj=new Object(),只要强引用关系还存在,垃圾收集器就不会回收掉被引用的对象;
  • 软引用:描述一些还有用,但非必须的对象。只被软引用关联的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围进行第二次回收;
  • 弱引用:被弱引用关联的对象只能生存到下一次垃圾回收集发生为止;
  • 虚引用:不能用来获取一个实例,仅仅是为了对象在被回收时获得一个系统通知;

垃圾收集算法:

  • 引用计数式收集(Reference Counting GC)
  • 追踪式垃圾收集(Tracing GC)

分代收集理论——将GC堆分成新生代和老年代提高回收效率;
一些概念的区分:
GC概念
Minor GC触发条件
对象在新生代Eden区中分配,当Eden区内存空间不足将发起一次Minor GC;
Full GC触发条件

  • 调用System.gc();
  • 老年代空间不足;
  • 方法区(永久代、元空间)空间不足;
  • 通过Minor GC触发内存担保机制;

新生代分区

标记-清除算法
标记-清除算法
最基础的算法;
缺点:执行效率不稳定,会随着对象的增多执行效率降低;产生大量不连续的内存碎片;

标记-复制算法
标记-复制算法
常用于回收新生代;
内存分配担保(“Appel式回收”):在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间以判断Minor GC是否安全,进一步判断是否需要Full GC;
缺点:在存活率较高(如老年代对象)时进行较多的复制操作,效率降低;

标记-整理算法
标记-整理算法
针对老年代——一种移动式算法(利弊皆有);

Hotspot 算法实现细节
经典垃圾收集器:
垃圾收集器

chapter 4 虚拟机性能监控、故障处理工具

Visual VM:多合一故障处理工具;

chapter 5 调优案例分析与实战

Part 3 虚拟机执行子系统

chapter 6 类文件结构

class文件

在Java中,要重载(overload)一个方法,除了要与原方法名称相同外,还必须要拥有一个与原方法不同的特征签名——(方法中各个参数在常量池中的字段符号的引用的集合),因为返回值不在特征签名中,所以Java不能通过返回值类型进行重载;

Java程序的方法体经过javac编译处理之后,变成字节码指令存储在Code属性中,Code属性出现在方法表的属性集合中;

chapter 7 虚拟机类加载机制

类的生命周期
加载:加载来自外部的二进制字节流,存储在方法区.随后会在Java堆中实例化一个java.lang.Class类的对象,作为程序访问方法区中的类型数据的外部接口.
连接:包括验证(检验二进制流是否符合规范),准备(为类变量分配内存,设置初始值),解析(将常量池中的符号引用替换为直接引用).
初始化:执行类构造器<clinit>()方法

  • clinit方法是由编译器自动收集类变量的赋值动作和静态语句块,静态语句块只能访问到定义在它之前的变量
  • clinit方法不需要显示调用父类的clinit,JVM保证子类clinit执行之前父类clinit方法已经执行完毕
  • JVM保证一个类的clinit方法在多线程环境中被同步加锁

有且只有6种情况对类初始化
初始化的几种情形
类加载器双亲委派模型

类加载器

  • 启动类加载器(Bootstrap Class Loader),加载<JAVA_HOME>\lib目录
  • 扩展类加载器(Extension Class Loader),加载<JAVA_HOME>\lib\ext目录
  • 应用程序类加载器(Application Class Loader),加载用户路径(ClassPath)上的类

双亲委派工作模型
当一个类加载器收到一个类的加载请求,不会自己尝试加载这个类,而是把这个请求委派给父类加载器完成,所有加载器最终传送到顶层的启动类加载器,只有当父加载器没有找到所需的类时候,子加载器才会尝试自己完成加载;
优点:避免了类的重复加载;保证Java核心API不被篡改;

//双亲委派机制的实现
protected syschronized Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException{
	//首先检查类是否已被加载过
}

Java模块化系统

chapter 8 虚拟机字节码执行引擎

分派
静态分派:javac在编译阶段根据参数的静态类型决定方法执行版本(重载)
动态分派:运行期间根据实际类型确定方法执行版本(重写)
动态类型语言
动态类型语言的关键特征是其类型检查的主体过程是在运行期而非编译期;
运行时异常:只要代码不执行到这一行就不会出现问题;
连接时异常:(例如NoClassDefFoundError)导致连接时异常的代码放在一条根本不会被执行的路径上,类加载过程(连接发生)中抛出异常;

chapter 9 类加载及执行子系统实战

在部署Web应用时,单独一个ClassPath不能满足需求,因此各种Web服务都提供了好几个有不同含义的ClassPath路径供用户存放第三方类库(一般以"lib"或"classes"命名);被放置到不同路径中的类库,具备不同的访问范围和服务对象,通常每一个目录都会有一个响应的自定义类加载器去加载放置在里面的Java类库;(Tomcat目录结构为例)
Tomcat服务器的类加载架构

Part 4 程序编译与代码优化

chapter 10 前端编译与优化

语法糖
在计算机语言中添加某种语法,对编译结果和功能无影响,但能减少代码量,增加程序可读性;
Java类型擦除式泛型的缺陷:
java泛型

Signature:
Signature属性的作用是存储一个方法在字节码层面的特征签名——Java的泛型属于类型擦除式泛型,擦除法仅仅是对方法的Code属性中的字节码进行擦除;实际上元数据保留了泛型信息——这也是我们在编码时能够通过反射获得参数化类型的根本依据;
自动装箱/拆箱
装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型;

前端编译器:完成从程序到抽象语法树或中间字节码的生成;

chapter 11 后端编译与优化

即时编译器:为了提高“热点代码”的执行效率,在运行时,虚拟机会把这些代码编译成本地机器码,进行代码优化,完成这个任务的后端编译器即为即时编译器;
解释器与编译器
当出现“罕见陷阱”,通过“逆优化”回到解释状态继续执行;
分层编译:编译器根据优化、编译的规模和耗时,划分不同编译层次;
分层编译
分层编译的交互关系:
分层编译交互

“热点探测”(判断是否为热点代码):

  • 基于采样的热点探测,周期性的检查各个线程的栈顶,判断方法热度;
  • 基于计数器的热点探测,为每个方法建立并维护计数器;

C1(Client Compiler)编译器架构:
Client Complier架构

提前编译器,主要两个分支:

  • 程序运行之前做静态翻译;
  • 给即时编译器做缓存加速;
即时编译提前编译
性能分析制导优化;激进预测性能优化;链接时优化没有执行时间和资源限制的压力

编译器优化技术
方法内联(把目标方法代码原封不动地“复制”到调用的方法中)
阻止继承:final类和方法:
final方法
虚方法和内联会产生矛盾,Java虚拟机引入CHA(类型继承关系分析)技术以及使用内联缓存(Inline Cache)减小方法调用开销;

逃逸分析

为什么要逃逸分析:
分析对象动态作用域,为其他优化措施提供依据;从不逃逸、方法逃逸(一个对象在方法中被定义后,可能被外部方法所引用)到线程逃逸(比如赋值给可以在其他线程中访问到的实例变量),称为对象由低到高的不同逃逸程度,通过分析逃逸程度从而采取不同程度的优化.

栈上分配:
栈上分配
标量替换
假如逃逸分析能够证明能够证明一个对象不会被外部方法访问,并且这个对象可以被拆散,可能不会创建这个对象而改为直接创建若干个被方法调用的成员变量(聚合量替换为标量).
同步消除
逃逸分析确定变量不会逃逸线程,则可以安全地消除对这个变量的同步措施;

公共子表达式消除
如果一个表达式E被计算过了,且从之前到现在E中所有变量值没有变化,则E就为公共表达式;

数组边界检查消除
数组边界检查消除
Graal编译器

Part 5 高效并发

chapter 12 Java 内存模型与线程

每条线程的工作内存保存了该线程使用的变量的主内存副本,线程对变量的所有操作都必须在工作内存中进行,不同线程之间无法访问对方工作内存中的变量。交互关系如图:
交互关系
主内存与工作内存的之间的具体的交互协议必须保证read,write,lock,unlock等操作都是原子的,不可再分的。
原子性:read,load,assign,use,store等操作保证原子性,不可中断;
可见性:当一个线程修改了共享变量的值时,其他线程能够立即得知这个修改;常用方法:volatile的特殊规则保证新值能够立即同步到主存,以及synchronized方法等;
有序性:volatile和synchronized保证线程的有序性;

先行发生原则:用来判断数据是否存在竞争,线程是否安全(包括程序次序规则、管程锁定规则、volatile变量规则等等都是Java内存中模型中"天然的"先行发生关系)

线程实现的三种方式:

  • 内核线程实现(1:1实现):每个轻量级线程都成为一个独立的调度单元,即使其中某一个轻量级线程在系统调用中被阻塞了,也不会影响整个进程继续工作;内核线程实现
  • 用户线程实现(1:N实现):用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助;
    用户线程
  • 混合线程实现(N:M实现)

Java线程调度:协同式抢占式
线程状态的转换关系:
线程状态转换

chapter 13 线程安全与锁优化

线程安全的实现方法
互斥同步(悲观策略——共享数据无论是否出现竞争都会加锁):
① sychronized持有锁(重量级的操作)
② Lock接口,以非块结构实现互斥同步.重入锁(ReentrantLock)相比sychronized增加了可等待中断、可实现公平锁、锁绑定多个条件等特性.
非阻塞同步(乐观策略):
依赖硬件指令集的发展(需要确保操作和冲突检测这两个步骤具有原子性)
CAS操作:需要三个操作数:V(内存位置),A(旧的预期值),B(准备设置的新值)当且仅当V符合A时才会用B更新A的值,否则不执行更新,上述处理是一个原子操作不会被其他线程中断.(ABA问题解决办法:通过控制变量值的版本保证CAS的正确性)
无同步方案:
可重入代码:具有不依赖全局变量、存储在堆上的数据和公用的系统资源,用到的变量由参数传入,不调用非可重入的方法
线程本地存储:把共享数据的可见范围限制在同一个线程之内,例如LocalThread.对于多线程访问的数据可用volatile关键字实现可见性.

锁优化
自旋锁:如果物理机器上有一个以上的处理器核心,能够让两个或以上的线程并行执行,可以让后面“请求锁”的线程执行一个忙循环(自旋),但不放弃处理器的执行时间,避免了线程切换的开销,但占用了处理器的时间;
锁消除:虚拟机即时编译器在运行时,对一些代码要求同步,但是对被检测到不可能存在共享数据竞争的锁进行消除;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值