第一周 预习资料
1.环境准备
JDK、JRE、JVM关系
JDK = JRE(运行环境) + 开发工具
JRE = JVM + 类库
开发Java程序交互关系:
用JDK开发JAVA程序,编译成字节码,打包给装有JRE的程序运行。
JRE启动JVM实例,加载、验证、执行Java字节码及依赖库,运行Java程序。
Linux配置Java环境变量
$ cat ~/.bash_profile
# JAVA ENV
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_162.jdk/Contents/Home
export PATH=$PATH:$JAVA_HOME/bin
让环境配置立即生效:
$ source ~/.bash_profile
查看环境变量:
echo $PATH
echo $JAVA_HOME
查找JDK安装在哪个目录?
jps ‐v
whereis javac
ls ‐l /usr/bin/javac
find / ‐name javac
2.量化性能指标
关注硬件:CPU+内存+IO(磁盘+网络)
衡量系统性能3个维度:
延迟:请求响应时间
吞吐量:交易类每秒事务数(TPS),查询类每秒请求数(QPS)
系统容量:硬件配置
性能调优总结:
第一步:收集数据,制定指标
第二步:分析解决瓶颈问题
3.JVM基础知识
1)编程语言分类
机器语言:二进制编码
汇编语言:缩写英文标符号
高级语言:多种编程语言总称
4.字节码技术
4.1.字节码简介
Java字节码由单字节指令组成,最多支持256个操作码,由前缀+操作名称组成。
指令分为:
1、栈操作指令,包括与局部变量操作指令
2、程序流程控制指令
3、对象操作指令,包括方法调用指令
4、算术运算以及类型转换指令
4.2.获取字节码清单
# javac编译
javac demo/jvm0104/HelloByteCode.java
# javap反编译
javap -c demo/java0104/HelloByteCode.class
4.3.解读字节码清单
4.4.查看class文件中的常量池信息
javap -c -verbose demo.jvm0104.HelloByteCode
ACC_PUBLIC: public类
ACC_SUPER: 调用super类
#1 常量编号
= 分隔符
Mathodref这个常量指向的是一个方法
4.5.查看方法信息
方法描述
小括号内 入参/形参信息
左方括号 表示数组
L 表示对象
V 这个返回值是void
栈深度stack=2;
局部变量表保留多少个槽位locals=2;
方法的参数个数args_size=1
无参构造函数的个数不是0
对于非静态方法,this将被分配到局部变量表的第0号槽位中
4.6.线程栈与字节码执行模型
每个线程有自己独有的线程栈,用于存储栈帧。
每执行一个方法,JVM都会创建栈帧。
栈帧由操作数栈,局部变量数组以及class引用。
class引用指向常量池中class
局部变量数组:方法参数,局部变量
操作数栈是一个LIFO结构的栈, 用于压 入和弹出值。
4.7.方法体中的字节码解读
方法体中字节码解读
方法体中字节码前数字是数组索引号
4.8.对象初始化指令:new,init,clinit
new创建对象,但没调构造函数
invokespecial调特殊方法,这里构造函数
dup复制栈顶值
astore {N}或astore_{N}赋值给局部变量
putfield将值赋值给实例字段
putstatic将值赋值给静态字段
静态初始化方法<clinit>
4.9.栈内存操作指令
dup复制栈顶值
pop删除栈顶值
swap交换栈顶值
dup_x1复制粘贴栈顶值
dup_x2复制粘贴栈顶两个值
4.10.局部变量表
javac -g xx.java
javap -c xx.class
astore_1 将引用地址值存储到编号1的局部变量中
iconst_1 将常量值1加载到栈里面
dstore 4 将double值保存到本地变量4号槽位
静态方法,槽位0没有this引用位置。
4.11.流程控制指令
if_icmpge: if,integer,compare,great equal
一个值是否大于等于另一个值
iinc 4,1: 4号槽位值加1
goto 18: 跳到循环开始地方
4.12.算术运算指令与类型转换指令
i2d: int to double
inic 不需要将数值load到操作数栈,直接对LocalVariableTable值进行运算
4.13.方法调用指令和参数传递
invokestatic 调用静态方法
invokespecial 调用构造方法,private方法,超类方法
invokevirtual 调用公共,受保护和打包私有方法
invokeinterface 调用接口
4.14.InvokeDynamic
为了实现动态类型语言
把实际翻译策略隐藏在JDK库实现
5.Java类加载机制
5.1.类的生命周期和加载过程
一个类在JVM里的生命周期有7个阶段,
分别是加载(Loading)、验证 (Verification)、准备(Preparation)、解析(Resolution)、初始化 (Initialization)、使用(Using)、卸载(Unloading)。
前五个部分(加载,验证,准备,解析,初始化)统称为类加载
1)加载
找class文件,找不到报NoClassDefFound
2)校验
检查 classfile 语义,常量池中的符号,并执行类型检查
加载所有超类和接口,检查类层次结构
3)准备
创建静态字段,初始化
4)解析
解析常量池,主要有以下四种:类或接口的解析、字段解析、类方法解析、接 口方法解析。
5)初始化
初始化的过程包括执行:
类构造器方法
static静态变量赋值语句
static静态代码块
5.2 类加载时机
5.3 类加载器机制
通过一个类的全限定名a.b.c.XXClass来获取描述此类的Class 对象
系统自带的类加载器分为三种:
启动类加载器(BootstrapClassLoader) 加载 Java 的核心类
扩展类加载器(ExtClassLoader) 加载JRE的扩展目录
应用类加载器(AppClassLoader)加载来自Java命令的classpath或者cp选项、java.class.path系统属性指定的jar包和类路径
类加载机制有三个特点:
- 双亲委托:当一个自定义类加载器需要加载一个类,比如java.lang.String,它很
懒,不会一上来就直接试图加载它,而是先委托自己的父加载器去加载,父加载
器如果发现自己还有父加载器,会一直往前找,这样只要上级加载器,比如启动
类加载器已经加载了某个类比如java.lang.String,所有的子加载器都不需要自己
加载了。如果几个类加载器都没有加载到指定名称的类,那么会抛出
ClassNotFountException异常。 - 负责依赖:如果一个加载器在加载某个类的时候,发现这个类依赖于另外几个类
或接口,也会去尝试加载这些依赖项。 - 缓存加载:为了提升加载效率,消除重复加载,一旦某个类被一个类加载器加
载,那么它会缓存这个加载结果,不会重复加载。
怎么看到加载了哪些类,以及加载顺序?
在类的启动命令行参数加上 ‐XX:+TraceClassLoading 或者 ‐verbose 即 可
6.JMM模型
6.1 JVM内存结构
JVM的内存区域分为: 堆内存 和 栈内存 ;
栈保存了调用链上正在执行的方法的局部变量。
每个线程都有一份自己的局部变量副本。
方法中使用的原生数据类型和对象引用地址在栈上存储;对象、对象成员 与类定义、静态变量在堆上。
虽然各个线程自己使用的局部变量都在自己的栈上,但是大家可以共享堆 上的对象,特别地各个不同线程访问同一个对象实例的基础类型的成员变量,会给每 个线程一个变量的副本。
6.2 栈内存的结构
6.3 堆内存的结构
堆内存是所有线程共用的内存空间
6.4 CPU指令与乱序执行
CPU的实现都是采用流水线的方式
通过内部调度把这些指令打乱了执行,充分利用流水线资源
6.5 JMM背景
JMM规范明确定义了不同的线程之间,通过哪些方式,在什么时候可以看见其他线程 保存到共享变量中的值;以及在必要时,如何对共享变量的访问进行同步。
6.6 JMM简介
- JVM的内存区域分为: 堆内存 和 栈内存 ;
- 堆内存的实现可分为两部分: 堆(Heap) 和 非堆(Non‐Heap) ;
- 堆主要由GC负责管理,按分代的方式一般分为: 老年代+年轻代;年轻代=新生代
+存活区; - CPU有一个性能提升的利器: 指令重排序 ;
- JMM规范对应的是 JSR133, 现在由Java语言规范和JVM规范来维护;
- 内存屏障的分类与作用。
6.7 内存屏障简介
JMM引入了内存屏障机制。
内存屏障可分为 读屏障 和 写屏障 ,用于控制可见性。 常见的 内存屏障 包括:
#LoadLoad
#StoreStore
#LoadStore
#StoreLoad
#LoadLoad , 那么屏障前面的Load指令就一定要先执行完,才能执行 屏障后面的Load指令。 比如我要先把a值写到A字段中,然后再将b值写到B字段对应的内存地址。如果 要严格保障这个顺序,那么就可以在这两个Store指令之间加入一个
#StoreStore 屏障。 遇到
#LoadStore 屏障时, CPU自废武功,短暂屏蔽掉指令重排序功能。
#StoreLoad 屏障, 能确保屏障之前执行的所有store操作,都对其他处理器可 见; 在屏障后面执行的load指令, 都能取得到最新的值。换句话说, 有效阻止屏障 之前的store指令,与屏障之后的load指令乱序 、即使是多核心处理器,在执行这 些操作时的顺序也是一致的。
参考资料
极客时间-Java进阶训练营