黑马程序员JVM完整教程

黑马程序员JVM完整教程


前言

黑马程序员JVM完整教程
b站链接:https://www.bilibili.com/video/BV1yE411Z7AP?spm_id_from=333.337.search-card.all.click&vd_source=a1565026d150073a042e4cd0cc263851
配套资料:https://pan.baidu.com/s/167mob33EfeD-on6dWrEWGQ 提取码:bqob


引言

  • 什么是JVM?
    Java Virtual Machine - java 程序的运行环境(java 二进制字节码的运行环境)。

  • 好处:
    一次编写,到处运行;
    自动内存管理,垃圾回收功能;
    数组下标越界检查;
    多态。

  • 比较:JVM JRE JDK
    比较
    JVM:java虚拟机, 可以屏蔽java代码与底层虚拟机之间的关系
    JRE:java的运行时环境,在JVM的基础上结合一些基础类库
    JDK:java开发工具包,在JRE的基础上增加编译工具

  • 常见的JVM:
    常见JVM

  • JVM的整体架构:
    学习路线

1. 内存结构

1. 1 程序计数器

  • Program Counter Register 程序计数器(寄存器)
  • 作用:记住下一条jvm指令的执行地址
    作用
  • 特点:
    • 是线程私有的
      • CPU会为每个线程分配时间片,当前线程的时间片使用完以后,CPU就会去执行另一个线程中的代码
      • 程序计数器是每个线程所私有的,当另一个线程的时间片用完,又返回来执行当前线程的代码时,通过程序计数器可以知道应该执行哪一句指令
    • 不会存在内存溢出

1.2 虚拟机栈

1.2.1 定义与作用

  • Java Virtual Machine Stacks(Java虚拟机栈)
    • 每个线程运行时所需要的内存,称为虚拟机栈
    • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
    • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
      栈
      在这里插入图片描述
  • 问题辨析:
    1. 垃圾回收是否涉及栈内存?
      不需要。栈内存是一次次方法调用所产生的栈帧内存,栈帧内存在每一次方法调用后都会弹出栈,也就是说会被自动的回收掉,因此不需要垃圾回收来进行管理。
    2. 栈内存分配越大越好吗?
      不是。物理内存大小一定,栈内存分配的越大,所能划分的线程越少。 栈内存分配的大仅仅只能使得能够进行更多次的递归调用。
    3. 方法内的局部变量是否线程安全?
      如果方法内局部变量没有逃离方法的作用范围,它是线程安全的。
      如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全。如静态变量、方法参数、方法返回值等。

1.2.2 栈内存溢出

  • 栈内存溢出:java.lang.StackOverflowError
    • 栈帧过多导致栈内存溢出:如没有终止条件的方法递归调用等
      SOF

    • 栈帧过大导致栈内存溢出

    • 可以通过参数-Xss来控制程序栈的大小
      在这里插入图片描述
      在这里插入图片描述

1.2.3 线程运行诊断

  • 线程诊断:
    • CPU占用高
      • top定位哪个进程对cpu的占用过高
      • ps H -eo pid,tid,%cpu | grep 进程id :用ps命令进一步定位是哪个线程引起的cpu占用过高(可以得到十进制的线程id)
      • jstack 进程id
        可以根据线程id(此时的线程id为十六进制,在匹配时需要将上一步得到的十进制的换算为十六进制) 找到有问题的线程,进一步定位到问题代码的源码行号
    • 迟迟得不到运行结果
      使用jstack 进程id,在最后会有一些提示信息
      在这里插入图片描述

1.3 本地方法栈

  • 在java虚拟机调用一些本地方法时需要给本地方法提供的内存空间
    • 本地方法:由于java有限制,不可以直接与操作系统底层交互,所以需要一些用c/c++编写的本地方法与操作系统底层的API交互,java可以间接的通过本地方法来调用底层功能
  • 举例:Object的clone()、hashCode()、notify()、notifyAll()、wait()等
    在这里插入图片描述

1.4 堆

1.4.1 定义

  • Heap堆:通过new关键字创建对象都会使用堆内存
  • 特点
    • 它是线程共享的,堆中对象都需要考虑线程安全的问题
    • 有垃圾回收机制

1.4.2 堆内存溢出

  • 堆内存溢出:java.lang.OutofMemoryError :java heap space.
    OOM
    • 可以通过参数-Xmx来控制程序堆的大小

1.4.3 堆内存诊断

  1. jps工具:查看当前系统中有哪些java进程
    在这里插入图片描述

  2. jmap工具:查看某一时刻堆内存占用情况
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  3. jconsole工具:图形界面的,多功能的监测工具,可以连续监测
    在这里插入图片描述

  4. jvisualvm工具
    在这里插入图片描述
    可以查看堆内存的具体信息:占用200多M内存
    在这里插入图片描述
    查看占用内存最大的对象:ArrayList
    在这里插入图片描述
    ArrayList中的元素Student对象中包含的big属性占用1M内存,共有200个元素,占用200M内存
    在这里插入图片描述
    对应源码:
    在这里插入图片描述

1.5 方法区

1.5.1 定义

  • 所有java虚拟机线程的共享区域
  • 存储类的结构的相关信息,如运行时常量池、成员变量、方法数据、成员方法和构造器的代码等
  • 方法区在虚拟机启动时创建,其逻辑上是堆的一个组成部分,但在实现时不同的JVM厂商可能会有不同的实现
    方法区

1.5.2 组成

  • 组成:以Oracle的HotSpot为例
    • jdk1.6:永久代,占用JVM内存空间
      在这里插入图片描述
    • jdk1.8:元空间,移出JVM内存(除StringTable),放入系统内存
      在这里插入图片描述

1.5.3 方法区内存溢出

  • 1.8以前会导致永久代内存溢出**java.lang.OutOfMemoryError: PernGen space**
    在这里插入图片描述
  • 1.8之后会导致元空间内存溢出:java.lang.OutOfMemoryError: Metaspace
    在这里插入图片描述
    在这里插入图片描述

1.5.4 运行时常量池

运行时常量池

  • 常量池:常量池,就是一张,存储在*.class字节码文件中。虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
反编译字节码获取
  • 方法一:
    编译: javac -encoding utf-8 Demo1_22.java,生成字节码文件
    反编译字节码文件:javap -v Demo1_22.class,在控制台打印出字节码文件的内容

  • 方法二:
    直接在IDEA中进行配置,可直接右键查看反编译好的字节码文件,方法见文章:Intellij IDEA 查看字节码
    在这里插入图片描述
    在这里插入图片描述

  • 运行时常量池常量池是*.class文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址

1.5.5 StringTable

StringTable特性
  • 常量池中的字符串仅是符号,第一次用到时才变为对象。

  • 利用串池的机制,来避免重复创建字符串对象

  • 字符串拼接

    package cn.itcast.jvm.t1.stringtable;
    
    // StringTable [ "a", "b" ,"ab" ]  hashtable 结构,不能扩容
    public class Demo1_22 {
         
        // 常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
        // ldc #2 会把 a 符号变为 "a" 字符串对象
        // ldc #3 会把 b 符号变为 "b" 字符串对象
        // ldc #4 会把 ab 符号变为 "ab" 字符串对象
    
        public static void main(String[] args) {
         
            String s1 = "a"; // 懒惰的, 运行到此行才会创建字符串对象放入串池
            String s2 = "b";
            String s3 = "ab";
            String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString()  new String("ab")
            String s5 = "a" + "b";  // javac 在编译期间的优化,结果已经在编译期确定为ab
    
            System.out.println(s3 == s4);//false
            System.out.println(s3 == s5);//true
        }
    }
    

    右键查看反编译后的字节码文件如下:

    Classfile /E:/00_learning/JAVA/资料-解密JVM/代码/jvm/out/production/jvm/cn/itcast/jvm/t1/stringtable/Demo1_22.class
      Last modified 2022年7月17日; size 1039 bytes
      MD5 checksum 8815b8f391f7f5387dc065e4053bf5d1
      Compiled from "Demo1_22.java"
    public class cn.itcast.jvm.t1.stringtable.Demo1_22
      minor version: 0
      major version: 52
      flags: (0x0021) ACC_PUBLIC, ACC_SUPER
      this_class: #11                         // cn/itcast/jvm/t1/stringtable/Demo1_22
      super_class: #12                        // java/lang/Object
      interfaces: 0, fields: 0, methods: 2, attributes: 1
    Constant pool:
       #1 = Methodref          #12.#36        // java/lang/Object."<init>":()V
       #2 = String             #37            // a
       #3 = String             #38            // b
       #4 = String             #39            // ab
       #5 = Class              #40            // java/lang/StringBuilder
       #6 = Methodref          #5.#36         // java/lang/StringBuilder."<init>":()V
       #7 = Methodref          #5.#41         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       #8 = Methodref          #5.#42         // java/lang/StringBuilder.toString:()Ljava/lang/String;
       #9 = Fieldref           #43.#44        // java/lang/System.out:Ljava/io/PrintStream;
      #10 = Methodref          #45.#46        // java/io/PrintStream.println:(Z)V
      #11 = Class              #47            // cn/itcast/jvm/t1/stringtable/Demo1_22
      #12 = Class              #48            // java/lang/Object
      #13 = Utf8               <init>
      #14 = Utf8               ()V
      #15 = Utf8               Code
      #16 = Utf8               LineNumberTable
      #17 = Utf8               LocalVariableTable
      #18 = Utf8               this
      #19 = Utf8               Lcn/itcast/jvm/t1/stringtable/Demo1_22;
      #20 = Utf8               main
      #21 = Utf8               ([Ljava/lang/String;)V
      #22 = Utf8               args
      #23 = Utf8               [Ljava/lang/String;
      #24 = Utf8               s1
      #25 = Utf8               Ljava/lang/String;
      #26 = Utf8               s2
      #27 = Utf8               s3
      #28 = Utf8               s4
      #29 = Utf8               s5
      #30 = Utf8               StackMapTable
      #31 = Class              #23            // "[Ljava/lang/String;"
      #32 = Class              #49            // java/lang/String
      #33 = Class              #50            // java/io/PrintStream
      #34 = Utf8               SourceFile
      #35 = Utf8               Demo1_22.java
      #36 = NameAndType        #13:#14        // "<init>":()V
      #37 = Utf8               a
      #38 = Utf8               b
      #39 = Utf8               ab
      #40 = Utf8               java/lang/StringBuilder
      #41 = NameAndType        #51:#52        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      #42 = NameAndType        #53:#54        // toString:()Ljava/lang/String;
      #43 = Class              #55            // java/lang/System
      #44 = NameAndType        #56:#57        // out:Ljava/io/PrintStream;
      #45 = Class              #50            // java/io/PrintStream
      #46 = NameAndType        #58:#59        // println:(Z)V
      #47 = Utf8               cn/itcast/jvm/t1/stringtable/Demo1_22
      #48 = Utf8               java/lang/Object
      #49 = Utf8               java/lang/String
      #50 = Utf8               java/io/PrintStream
      #51 = Utf8               append
      #52 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
      #53 = Utf8               toString
      #54 = Utf8               ()Ljava/lang/String;
      #55 = Utf8               java/lang/System
      #56 = Utf8               out
      #57 = Utf8               Ljava/io/PrintStream;
      #58 = Utf8               println
      #59 = Utf8               (Z)V
    {
      public cn.itcast.jvm.t1.stringtable.Demo1_22();
        descriptor: ()V
        flags: (0x0001) ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 4: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lcn/itcast/jvm/t1/stringtable/Demo1_22;
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: (0x0009) ACC_PUBLIC, ACC_STATIC
        Code:
          stack=3, locals=6, args_size=1
             0: ldc           #2                  // String a
             2: astore_1
             3: ldc           #3                  // String b
             5: astore_2
             6: ldc           #4                  // String ab
             8: astore_3
             9: new           #5                  // class java/lang/StringBuilder
            12: dup
            13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
            16: aload_1
            17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            20: aload_2
            21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
            27: astore        4
            29: ldc           #4                  // String ab
            31: astore        5
            33: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
            36: aload_3
            37: aload         4
            39: if_acmpne     46
            42: iconst_1
            43: goto          47
            46: iconst_0
            47: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
            50: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
            53: aload_3
            54: aload         5
            56: if_acmpne     63
            59: iconst_1
            60: goto          64
            63: iconst_0
            64: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
            67: return
          LineNumberTable:
            line 11: 0
            line 12: 3
            line 13: 6
            line 14: 9
            line 15: 29
            line 17: 33
            line 18: 50
            line 19: 67
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      68     0  args   [Ljava/lang/String;
                3      65     1    s1   Ljava/lang/String;
                6      62     2    s2   Ljava/lang/String;
                9      59     3    s3   Ljava/lang/String;
               29      39     4    s4   Ljava/lang/String;
               33      35     5    s5   Ljava/lang/String;
          StackMapTable: number_of_entries = 4
            frame_type = 255 /* full_frame */
              offset_delta = 46
              locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
              stack = [ class java/io/PrintStream ]
            frame_type = 255 /* full_frame */
              offset_delta = 0
              locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
              stack = [ class java/io/PrintStream, int ]
            frame_type = 79 /* same_locals_1_stack_item */
              stack = [ class java/io/PrintStream ]
            frame_type = 255 /* full_frame */
              offset_delta = 0
              locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
              stack = [ class java/io/PrintStream, int ]
    }
    SourceFile: "Demo1_22.java"
    
    • 对于字符串变量拼接String s4 = s1 + s2;:原理是 StringBuilder (1.8)
      在这里插入图片描述
      其过程为:new StringBuilder().append("a").append("b").toString() ,即`new String(“ab”),其应当存储在堆中,而不是常量池
      在这里插入图片描述

    • 对于字符常量拼接String s5 = "a" + "b"; :原理是编译期优化
      在这里插入图片描述
      javac 在编译期间的优化,结果已经在编译期确定"ab",因此会直接找到常量池中的值

  • 字符串延迟加载:在执行时是执行一行创建一个对象放入串池

    package cn.itcast.jvm.t1.stringtable;
    
    /**
     * 演示字符串字面量也是【延迟】成为对象的
     */
    public class TestString {
         
        public static void main(String[] args) {
         
            int x = args.length;
            System.out.println(); // 字符串个数 2177
    
            System.out.print("1"); // 字符串个数 2178
            System.out.print("2");
            System.out.print("3");
            System.out.print("4");
            System.out.print("5");
            System.out.print("6");
            System.out.print("7");
            System.out.print("8");
            System.out.print("9");
            System.out.print("0"); // 字符串个数 2187
            System.out.print("1"); // 字符串个数 2187
            System.out.print("2");
            System.out.print("3");
            System.out.print("4");
            System.out.print("5");
            System.out.print("6");
            System.out.print("7");
            System.out.print("8");
            System.out.print("9");
            System.out.print("0");
            System.out.print(x); // 字符串个数
        }
    }
    

    可以使用IDEA的Debug工具进行内存查看:
    在这里插入图片描述

  • 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池

    • 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回

      • 串池中有此字符串对象,不会放入
        package cn.itcast.jvm.t1.stringtable;
        
        public class Demo1_23 {
                 
        
            //  ["ab", "a", "b"]
            public static void main(String[] args) {
                 
        
                String x = "ab";
                String s = new String("a") + new String("b");
        
                // 堆  new String("a")   new String("b") new String("ab")
                String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
        
                System.out.println( s2 == x);//true
                System.out.println( s == x);//false
            }
        }
        
      • 串池中没有此字符串对象,放入串池, 并把串池中的对象返回
        package cn.itcast.jvm.t1.stringtable;
        
        public class Demo1_23 {
                 
            //  ["ab", "a", "b"]
            public static void main(String[] args) {
                 
        
              	String s = new String("a") + new String("b");
        
        		// 堆  new String("a")   new String("b") new String("ab")
        		String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
        
        		String x = "ab";
        		System.out.println( s2 == x);//true
        		System.out.println( s == x);//true
            }
        }
        
    • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回

      • 串池中有此字符串对象,不会放入
        package cn.itcast.jvm.t1.stringtable;
        
        public class Demo1_23 {
                 
        
            //  ["ab", "a", "b"]
            public static void main(String[] args) {
                 
        
                String x = "ab";
                String s = new String("a") + new String("b");
        
                // 堆  new String("a")   new String("b") new String("ab")
                String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
        
                System.out.println( s2 == x);//true
                System.out.println( s == x);//false
            }
        }
        
      • 串池中没有此字符串对象,放入串池, 并把串池中的对象返回
        package cn.itcast.jvm.t1.stringtable;
        
        public class Demo1_23 {
                 
            //  ["ab", "a", "b"]
            public static void main(String[] args) {
                 
        
              	String s = new String("a") + new String("b");
        
        		// 堆  new String("a")   new String("b") new String("ab")
        		String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
        		//s 拷贝一份,放入串池
        
        		String x = "ab";
        		System.out.println( s2 == x);//true
        		System.out.println( s == x);//false
            }
        }
        
  • 面试题:

    package cn.itcast.jvm.t1.stringtable;
    
    public class Demo1_21 {
         
    
        public static void main(String[] args) {
         
            String s1 = "a";
            String s2 = "b";
            String s3 = "a" + "b"; // ab
            String s4 = s1 + s2;   // new String("ab")
            String s5 = "ab";
            String s6 = s4.intern();
    
    // 问
            System.out.println(s3 == s4); // false
            System.out.println(s3 == s5); // true
            System.out.println(s3 == s6); // true
    
            String x2 = new String("c") + new String("d"); // new String("cd")
            String x1 = "cd";
            x2.intern();
    
    // 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢//调换位置后:true//jdk1.6:false
            System.out.println(x1 == x2);//false
        }
    }
    
StringTable位置

在这里插入图片描述

  • jdk1.6时,StringTable是常量池的一部分,随常量池存储在永久代中;jdk1.7/jdk1.8时StringTable从永久代转移到了堆中。
  • 原因:
    永久代内存回收效率很低,只有Full GC时才会触发永久代的垃圾回收,而Full GC只有在整个老年代的空间不足时才会触发,时机较晚。而StringTable使用频繁,其中存储着大量字符串常量,若其回收效率不高就会占用大量内
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值