B站视频地址:千锋教育JVM全套教程

image.png

一、JVM介绍

1.JVM是什么?

三个组成部分:

  • 类加载系统
  • 运行时数据区
  • 执行引擎

image.png

2.学习的目的

充分理解JVM内部工作流程,掌握如何通过相应参数配置,实现JVM的调优

二、类加载系统

1.类的加载过程

  • 加载: 通过io流的方式把字节码⽂件读⼊到jvm中(方法区)
  • 校验:通过校验字节码文件的头8位的16进制是否是java魔数cafebabe
  • 准备:为类中的静态部分开辟空间并赋初始化值
  • 解析:将符号引用转换成直接引用。——静态链接
  • 初始化:为类中的静态部分赋指定值并执行静态代码块。

类被加载后,类中的类型信息、方法信息、属性信息、运行时常量池、类加载器的引用等信息会被加载到元空间中。

2.类加载器

  • Bootstrap ClassLoader 启动类加载器
  • ExtClassLoader 扩展类加载器
  • AppClassLoader 应用类加载器
  • 自定义类加载器

image.png
⭐源码解读 P7-P8

三、双亲委派机制

1.双亲委派机制介绍

image.png

2.为什么要有双亲委派机制?

  • 防止核心类库中类被随意篡改
  • 防止重复加载

    3.双亲委派机制核心代码

    1. protected Class<?> loadClass(String name, boolean resolve)
    2. throws ClassNotFoundException
    3. {
    4. synchronized (getClassLoadingLock(name)) {
    5. // First, check if the class has already been loaded
    6. Class<?> c = findLoadedClass(name);
    7. if (c == null) {
    8. long t0 = System.nanoTime();
    9. try {
    10. if (parent != null) {
    11. c = parent.loadClass(name, false);
    12. } else {
    13. c = findBootstrapClassOrNull(name);
    14. }
    15. } catch (ClassNotFoundException e) {
    16. // ClassNotFoundException thrown if class not found
    17. // from the non-null parent class loader
    18. }
    19. if (c == null) {
    20. // If still not found, then invoke findClass in order
    21. // to find the class.
    22. long t1 = System.nanoTime();
    23. c = findClass(name);
    24. // this is the defining class loader; record the stats
    25. sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
    26. sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
    27. sun.misc.PerfCounter.getFindClasses().increment();
    28. }
    29. }
    30. if (resolve) {
    31. resolveClass(c);
    32. }
    33. return c;
    34. }
    35. }

    4.全盘委托机制

    当一个类被当前ClassLoader加载时,该类中其他类也会被当前该ClassLoader加载。除非指定由其他类加载器加载。

5.自定义类加载器实现双亲委派机制

  1. @Override
  2. protected Class<?> findClass(final String name)
  3. throws ClassNotFoundException
  4. {
  5. try {
  6. //1.读入指定路径的classPath下的类
  7. String path = name.replace('.', '/').concat(".class");
  8. FileInputStream fileInputStream = new FileInputStream(classPath+"/"+path);
  9. byte[] data = new byte[fileInputStream.available()];
  10. fileInputStream.read(data);
  11. fileInputStream.close();
  12. //2.加载该类
  13. return defineClass(name, data,0,data.length);
  14. } catch (Exception e) {
  15. e.printStackTrace();
  16. throw new ClassNotFoundException();
  17. }
  18. }

6.自定义类加载器打破双亲委派机制

  1. protected Class<?> loadClass(String name, boolean resolve)
  2. throws ClassNotFoundException {
  3. synchronized (getClassLoadingLock(name)) {
  4. // First, check if the class has already been loaded
  5. Class<?> c = findLoadedClass(name);
  6. if (c == null) {
  7. // If still not found, then invoke findClass in order
  8. // to find the class.
  9. long t1 = System.nanoTime();
  10. //对于Object类,使用父加载器
  11. if(!name.startsWith("com.qf.jvm")){
  12. c = this.getParent().loadClass(name);
  13. }else{
  14. c = findClass(name);
  15. }
  16. // this is the defining class loader; record the stats
  17. sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
  18. sun.misc.PerfCounter.getFindClasses().increment();
  19. }
  20. if (resolve) {
  21. resolveClass(c);
  22. }
  23. return c;
  24. }
  25. }

‘7’.梳理类加载完整流程

image.png

四、运行时数据区

1.运行时数据区介绍

image.png
该内存区域也被称为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
}

image.png
执行一个方法会在线程栈中创建一个栈帧
栈帧包含如下四个内容:

  • 局部变量表
  • 操作数栈
  • 动态链接
  • 方法出口

    五、对象的创建流程

    1.创建流程

    image.png

    2.类加载校验

    校验该类是否已被加载。主要是检查常量池中是否存在该类的类元信息。
    如果没有,则需要进行加载。

    3.分配内存

  • 指针碰撞

  • 空闲列表

    4.设置初值

    根据数据类型,为对象空间赋初始化值。

    5.设置对象头

    对象头

  • Mark Work字段

  • 类型指针

指针压缩

6.执行init方法

为对象中的属性赋值和执行构造方法。

六、垃圾回收机制

1.对象成为垃圾的判断依据

  • 引用计数法
  • 可达性分析法

    • gc roots根节点:局部变量表中变量、静态变量、本地方法栈中变量都被称为gc roots根结点。
    • 判断依据:向上遍历看能不能找到gc roots根结点。

      2.对象中的finalize方法

      对象被回收前的最后一根救命稻草
  • 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.分代收集算法

image.png

  • 堆空间被分成了新⽣代(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%,那么下一次复制的时候,把年龄大于这次最大年龄的对象一次性全部放入老年代
  • 老年代空间分配担保机制

image.png

八、垃圾回收器

1.Serial收集器

2.Parallel收集器

3.ParNew收集器

4.CMS收集器

5.三色标记算法

6.垃圾收集器组合方案