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
关注硬件:CPU+内存+IO(磁盘+网络)
衡量系统性能3个维度:
延迟:请求响应时间
吞吐量:交易类每秒事务数(TPS),查询类每秒请求数(QPS)
系统容量:硬件配置
性能调优总结:
第一步:收集数据,制定指标
第二步:分析解决瓶颈问题
1)编程语言分类
机器语言:二进制编码
汇编语言:缩写英文标符号
高级语言:多种编程语言总称
Java字节码由单字节指令组成,最多支持256个操作码,由前缀+操作名称组成。
指令分为:
1、栈操作指令,包括与局部变量操作指令
2、程序流程控制指令
3、对象操作指令,包括方法调用指令
4、算术运算以及类型转换指令
# javac编译 javac demo/jvm0104/HelloByteCode.java # javap反编译 javap -c demo/java0104/HelloByteCode.class
javap -c -verbose demo.jvm0104.HelloByteCode ACC_PUBLIC: public类 ACC_SUPER: 调用super类 #1 常量编号 = 分隔符 Mathodref这个常量指向的是一个方法
小括号内 入参/形参信息
左方括号 表示数组
L 表示对象
V 这个返回值是void
栈深度stack=2;
局部变量表保留多少个槽位locals=2;
方法的参数个数args_size=1
无参构造函数的个数不是0
对于非静态方法,this将被分配到局部变量表的第0号槽位中
每个线程有自己独有的线程栈,用于存储栈帧。
每执行一个方法,JVM都会创建栈帧。
栈帧由操作数栈,局部变量数组以及class引用。
class引用指向常量池中class
局部变量数组:方法参数,局部变量
操作数栈是一个LIFO结构的栈, 用于压 入和弹出值。
方法体中字节码解读
方法体中字节码前数字是数组索引号
new创建对象,但没调构造函数
invokespecial调特殊方法,这里构造函数
dup复制栈顶值
astore {N}或astore_{N}赋值给局部变量
putfield将值赋值给实例字段
putstatic将值赋值给静态字段
静态初始化方法
dup复制栈顶值
pop删除栈顶值
swap交换栈顶值
dup_x1复制粘贴栈顶值
dup_x2复制粘贴栈顶两个值
javac -g xx.java
javap -c xx.class
astore_1 将引用地址值存储到编号1的局部变量中
iconst_1 将常量值1加载到栈里面
dstore 4 将double值保存到本地变量4号槽位
静态方法,槽位0没有this引用位置。
if_icmpge: if,integer,compare,great equal
一个值是否大于等于另一个值
iinc 4,1: 4号槽位值加1
goto 18: 跳到循环开始地方
i2d: int to double
inic 不需要将数值load到操作数栈,直接对LocalVariableTable值进行运算
invokestatic 调用静态方法
invokespecial 调用构造方法,private方法,超类方法
invokevirtual 调用公共,受保护和打包私有方法
invokeinterface 调用接口
为了实现动态类型语言
把实际翻译策略隐藏在JDK库实现
一个类在JVM里的生命周期有7个阶段,
分别是加载(Loading)、验证 (Verification)、准备(Preparation)、解析(Resolution)、初始化 (Initialization)、使用(Using)、卸载(Unloading)。
前五个部分(加载,验证,准备,解析,初始化)统称为类加载
找class文件,找不到报NoClassDefFound
检查 classfile 语义,常量池中的符号,并执行类型检查
加载所有超类和接口,检查类层次结构
创建静态字段,初始化
解析常量池,主要有以下四种:类或接口的解析、字段解析、类方法解析、接 口方法解析。
初始化的过程包括执行:
类构造器方法
static静态变量赋值语句
static静态代码块
通过一个类的全限定名a.b.c.XXClass来获取描述此类的Class 对象
系统自带的类加载器分为三种:
启动类加载器(BootstrapClassLoader) 加载 Java 的核心类
扩展类加载器(ExtClassLoader) 加载JRE的扩展目录
应用类加载器(AppClassLoader)加载来自Java命令的classpath或者cp选项、java.class.path系统属性指定的jar包和类路径
类加载机制有三个特点:
怎么看到加载了哪些类,以及加载顺序?
在类的启动命令行参数加上 ‐XX:+TraceClassLoading 或者 ‐verbose 即 可
JVM的内存区域分为: 堆内存 和 栈内存 ;
栈保存了调用链上正在执行的方法的局部变量。
每个线程都有一份自己的局部变量副本。
方法中使用的原生数据类型和对象引用地址在栈上存储;对象、对象成员 与类定义、静态变量在堆上。
虽然各个线程自己使用的局部变量都在自己的栈上,但是大家可以共享堆 上的对象,特别地各个不同线程访问同一个对象实例的基础类型的成员变量,会给每 个线程一个变量的副本。
堆内存是所有线程共用的内存空间
CPU的实现都是采用流水线的方式
通过内部调度把这些指令打乱了执行,充分利用流水线资源
JMM规范明确定义了不同的线程之间,通过哪些方式,在什么时候可以看见其他线程 保存到共享变量中的值;以及在必要时,如何对共享变量的访问进行同步。
JMM引入了内存屏障机制。
内存屏障可分为 读屏障 和 写屏障 ,用于控制可见性。 常见的 内存屏障 包括:
#LoadLoad #StoreStore #LoadStore #StoreLoad
#LoadLoad , 那么屏障前面的Load指令就一定要先执行完,才能执行 屏障后面的Load指令。 比如我要先把a值写到A字段中,然后再将b值写到B字段对应的内存地址。如果 要严格保障这个顺序,那么就可以在这两个Store指令之间加入一个
#StoreStore 屏障。 遇到
#LoadStore 屏障时, CPU自废武功,短暂屏蔽掉指令重排序功能。
#StoreLoad 屏障, 能确保屏障之前执行的所有store操作,都对其他处理器可 见; 在屏障后面执行的load指令, 都能取得到最新的值。换句话说, 有效阻止屏障 之前的store指令,与屏障之后的load指令乱序 、即使是多核心处理器,在执行这 些操作时的顺序也是一致的。
极客时间-Java进阶训练营