字节码
- java所有指令200个左右,一个字节(8位)可以保存256种不同的指令信息
-
加载或存储指令
- 将局部变量加载到操作栈中
- 从操作栈顶存储到局部变量表
- 将常量加载到操作栈顶(极为高频)
-
运算指令
-
类型转换指令
-
对象创建与访问指令
-
操作栈管理指令
-
方法调用与返回指令
-
同步指令
jvm使用方法结构中的acc_synchronized标志同步方法,Synchronize语义通过monitorenter和monitorexit实现
-
.java文件不是可执行文件,需要编译成为字节码甚至是机器码文件
java源文件->词法解析->语法解析->语义分析->生成字节码->字节码
-
字节码需要通过类加载过程加载到jvm环境中才可执行,执行模式三种:
- 解释执行
- jit编译执行
- jit编译与解释混合执行(主流jvm默认模式)
-
分批方式进行发布的原因是:机器在热状态下的负载大于冷机状态,冷机状态的服务可能因无法承载流量而假死,每个发布批次之多占到整个集群集群数量的1/8。
类加载过程
双亲委派模型
-
步骤
- Load阶段,读取类文件产生二进制流,转化为特定的数据结构,初步校验,创建对应类的java.lang.Class实例
- Link阶段,验证、准备、解析三个步骤
- Init阶段,执行类构造器方法
-
意义
类加载是将.class字节码文件实例化成Class对象并进行相关初始化的过程
new与newInstance区别
- new是强类型校验,可以调用任何构造方法,在使用new时,这个类可以没有被加载过
- Class类下的newInstance是弱类型,只能调用无参构造方法,如果没有无参构造方法就会抛出InstantiationException异常;如果此构造方法没有权限访问,则抛出IllegalAccessException异常。
类加载器
- java通过类加载器把类的实现与类的定义进行解耦,是实现面向接口编程、依赖倒置的必然选择
- 三层结构:
- 最高层Bootstrap,于jvm启动时创建,是最根基的类加载器,装载最核心的java类(Object、System、String等)
- 第二层在JDK9是Platform ClassLoader平台类加载器,加载一些扩展的系统类;JDK9之前是Extension ClassLoader
- 第三层是Application ClassLoader应用类加载器,加载用户定义的CLASSPATH路径下的类
- 自定义类加载器步骤
- 继承ClassLoader
- 重写findClass方法
- 调用defineClass方法
内存布局
-
Heap堆区
OOM故障的主要发源地,储存几乎所有的实例对象,堆区由各子线程共享使用,占用的空间最大,可以通过-Xms、-Xmx设置,线上一般设置成一样大小,避免在GC后调整堆大小时有额外的压力,
堆分成两大块:新生代和老年代,新生代=1个Eden区+2个Survivor区。
Eden区填满后会触发YGC(YoungGC),存活的对象放入Survivor区,
Survivor分为S0和S1两块空间。
每个对象都有一个计数器,每次YGC都会加一,-XX:MaxTunuringThreshold设置达到某个阈值直接从Eden移到老年代,默认是15。
-
Metaspace元空间
JDK11版本JVM为Hotspot,JDK8中元空间的前身Perm区已经被淘汰,-XX:MaxPermSize=1280m指定参数,通过调整此参数防止运行过程中,因为不断动态加载类导致的OOM。
JDK8使用元空间代替永久代,元空间在本地内存中分配,jdk8中Perm区中的所有内容中字符串常量移至堆内存,其他信息包括类元信息、字段、静态属性、方法常量等都移动至元空间。
-
JVM Stack(虚拟机栈)
是描述java方法执行的内存区域,是线程私有的。
栈帧是方法运行的基本结构;StackOverflowError表示请求的栈溢出,导致内存耗尽,通常出现在递归方法中。
-
局部变量表
存放方法参数和局部变量的区域。局部变量需要显示初始化
-
操作栈
是一个初始状态为空的桶式结构栈。jvm的执行引擎是基于栈的执行引擎。
-
动态连接
每个栈帧中包含一个在常量池中对当前方法的引用,目的是支持方法调用过程的动态连接
-
方法返回地址
方法退出的两种情况:一是正常退出;二是异常退出;方法退出过程相当于弹出当前栈帧。
-
-
Native Method Stacks(本地方法栈)
线程对象私有;
-
程序计数寄存器
用来存放执行指令的偏移量和行号指示器等,程序执行或恢复都要依赖程序计数器。
程序计数器在各个线程之间互不影响,也不会发生内存泄露。
对象实例化
对象创建执行步骤:
-
确认类元信息是否存在
在metaspace内检查需要创建的类元信息是否存在。
-
分配对象内存
引用变量仅分配引用变量空间,4个字节大小。
-
设定默认值
-
设置对象头
-
执行init方法
垃圾回收
典型的三种垃圾回收器:Serial、CMS、G1
Serial
主要应用于YGC,采用串行单线程的方式,STW(stop the world)
CMS
回收停顿时间比较短、比较常用;
经历初始标记、并发标记、重新标记、并发清除四个步骤;
采用了标记-清除算法,会产生大量空间碎片;
通过配置-XX:+UseCMSCompactAtFullCollection参数,强制JVM在FGC完成后对老年代进行压缩;
G1
采用Mark-Copy的方式;
一大优势在于可预测的停顿时间,能够尽可能快的在指定时间内完成垃圾回收任务;
JDK11将G1设为默认垃圾回收器;
G1的Concurrent Marking的步骤:
- Initial Mark,其实就是YoungGC,会引起STW
- Root Region Scan,根区域扫描
- Concurrent Mark并发标记
- Remark 重新标记,会STW
- Cleanup,为Mixed GC做准备,统计所有区域中的存活对象,并将待回收区域按回收价值排序,优先回收垃圾最多的区域。