B站视频地址:千锋教育JVM全套教程
一、JVM介绍
1.JVM是什么?
三个组成部分:
- 类加载系统
- 运行时数据区
- 执行引擎
2.学习的目的
充分理解JVM内部工作流程,掌握如何通过相应参数配置,实现JVM的调优
二、类加载系统
1.类的加载过程
- 加载: 通过io流的方式把字节码⽂件读⼊到jvm中(方法区)
- 校验:通过校验字节码文件的头8位的16进制是否是java魔数cafebabe
- 准备:为类中的静态部分开辟空间并赋初始化值
- 解析:将符号引用转换成直接引用。——静态链接
- 初始化:为类中的静态部分赋指定值并执行静态代码块。
类被加载后,类中的类型信息、方法信息、属性信息、运行时常量池、类加载器的引用等信息会被加载到元空间中。
2.类加载器
- Bootstrap ClassLoader 启动类加载器
- ExtClassLoader 扩展类加载器
- AppClassLoader 应用类加载器
- 自定义类加载器
三、双亲委派机制
1.双亲委派机制介绍
2.为什么要有双亲委派机制?
- 防止核心类库中类被随意篡改
-
3.双亲委派机制核心代码
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
4.全盘委托机制
当一个类被当前ClassLoader加载时,该类中其他类也会被当前该ClassLoader加载。除非指定由其他类加载器加载。
5.自定义类加载器实现双亲委派机制
@Override
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
try {
//1.读入指定路径的classPath下的类
String path = name.replace('.', '/').concat(".class");
FileInputStream fileInputStream = new FileInputStream(classPath+"/"+path);
byte[] data = new byte[fileInputStream.available()];
fileInputStream.read(data);
fileInputStream.close();
//2.加载该类
return defineClass(name, data,0,data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
6.自定义类加载器打破双亲委派机制
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//对于Object类,使用父加载器
if(!name.startsWith("com.qf.jvm")){
c = this.getParent().loadClass(name);
}else{
c = findClass(name);
}
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
‘7’.梳理类加载完整流程
四、运行时数据区
1.运行时数据区介绍
该内存区域也被称为JVM内存模型——JMM
蓝色区域为线程共享,黄色区域为线程独享
- 堆空间:存放new出来的对象
- 元空间:存放类元信息、模板、常量池、静态部分
- 线程栈:存放方法的栈帧(因此递归调用和循环过多会引发栈溢出)
- 本地方法区:存放本地方法运行时产生的数据
- 程序计数器:配合执行引擎执行指令
2.程序执行时数据区内存变化
通过在程序的.class目录下执行如下指令,可查看程序的汇编指令javap -c JVMAnalyze
public class com.qf.jvm.JVMAnalyze {
public com.qf.jvm.JVMAnalyze();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public int add();
Code:
0: bipush 10
2: istore_1
3: bipush 20
5: istore_2
6: iload_1
7: iload_2
8: iadd
9: iconst_5
10: imul
11: istore_3
12: iload_3
13: ireturn
public static void main(java.lang.String[]);
Code:
0: new #2 // class com/qf/jvm/JVMAnalyze
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method add:()I
12: istore_2
13: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
16: iload_2
17: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
20: return
}
执行一个方法会在线程栈中创建一个栈帧
栈帧包含如下四个内容:
- 局部变量表
- 操作数栈
- 动态链接
-
五、对象的创建流程
1.创建流程
2.类加载校验
校验该类是否已被加载。主要是检查常量池中是否存在该类的类元信息。
如果没有,则需要进行加载。3.分配内存
指针碰撞
-
4.设置初值
5.设置对象头
对象头
Mark Work字段
- 类型指针
6.执行init方法
六、垃圾回收机制
1.对象成为垃圾的判断依据
- 引用计数法
可达性分析法
GC在垃圾对象回收之前,先标记垃圾对象,被标记的对象的finalize方法将被调用
- 调⽤finalize方法如果对象被引用,那么第⼆次标记该对象,被标记的对象将移除出即将
被回收的集合,继续存活
- 调用finalize方法如果对象没有被引用,那么将会被回收
- 注意,finalize方法只会被调用⼀次。
3.对象的逃逸分析
public void test1(){ User user = new User(); user.setId(1); user.setName("a"); } public void test2(){ User user = new User(); user.setId(1); user.setName("b"); return user; }
这种对象没有外部访问,且在堆空间上频繁创建,当方法结束,需要被gc,浪费性能
jdk1.7之后,进行一次逃逸分析(默认开启),这样的对象直接在栈上创建,不需要进行gc。
在栈分配内存的时候:会把聚合量替换成标量,减少栈空间的开销,防止栈上没有足够的连续空间直接存放对象。
- 标量:java基本数据类型
- 聚合量:引用数据类型
七、垃圾收集算法
1.标记清除算法、复制算法、标记整理算法
2.分代收集算法
- 堆空间被分成了新⽣代(1/3)和⽼年代(2/3),新⽣代中被分成了eden(8/10)、
survivor1(1/10)、survivor2(1/10)
- 对象的创建在eden,如果放不下则触发minor gc
- 对象经过⼀次minorgc 后存活的对象会被放⼊到survivor区,并且年龄+1
- survivor区执⾏的复制算法,当对象年龄到达15.进⼊到⽼年代。
- 如果⽼年代放满。就会触发Full GC
Full GC 非常慢-STW
使用VisualVM
3.对象进入老年代条件
- 大对象直接进入老年代
- 设置年龄参数
- 根据对象动态年龄判断,如果s区对象总和超过s区50%,那么下一次复制的时候,把年龄大于这次最大年龄的对象一次性全部放入老年代
- 老年代空间分配担保机制