黑马JVM学习笔记

1、什么是JVM

定义
Java Virtual Machine,JAVA程序的运行环境(JAVA二进制字节码的运行环境)

好处

  1. 一次编写,到处运行
  2. 自动内存管理,垃圾回收机制
  3. 数组下标越界检查 比较

JVM JRE JDK的区别
在这里插入图片描述
常见的jvm
在这里插入图片描述
JVM架构
在这里插入图片描述
JVM的组成
1.类加载器(ClassLoader)
2.运行时数据区(Runtime Data Area)
3.执行引擎(Execution Engine)
4.本地库接口(Native Interface)

2.运行时数据区组成
jvm的运行时数据区,不同虚拟机实现可能略微有所不同,但都会遵从Java虚拟机规范,Java 8 虚拟机规范规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域:

1.程序计数器(Program Counter Register)
2.Java虚拟机栈(Java Virtual Machine Stacks)
3.本地方法栈(Native Method Stack)
4.Java堆(Java Heap)
5.方法区(Methed Area)

2、内存结构

在这里插入图片描述

  1. 程序计数器
  2. 虚拟机栈
  3. 本地方法栈
  4. 方法区

2.1、程序计数器

作用: 用于保存JVM中下一条所要执行的指令的地址

特点

  • 线程私有
    • CPU会为每个线程分配时间片,当当前线程的时间片使用完以后,CPU就会去执行另一个线程中的代码
    • 程序计数器是每个线程所私有的,当另一个线程的时间片用完,又返回来执行当前线程的代码时,通过程序计数器可以知道应该执行哪一句指令
  • 不会存在内存溢出
    在这里插入图片描述

2.2、虚拟机栈

定义

  1. 每个线程运行需要的内存空间,称为虚拟机栈
  2. 每个栈由多个栈帧组成,对应着每次调用方法时所占用的内存
  3. 每个线程只能有一个活动栈帧,对应着当前正在执行的方法

演示代码在这里插入图片描述

public class StackDemo {

    public static void main(String[] args) {
        method1();
    }

    private static void method1() {
        method2(1, 2);
    }

    private static int method2(int a, int b) {
        int c = a + b;
        return c;
    }
}

问题辨析

  • 垃圾回收是否涉及栈内存?

    • 不需要。因为虚拟机栈中是由一个个栈帧组成的,在方法执行完毕后,对应的栈帧就会被弹出栈。所以无需通过垃圾回收机制去回收内存。
  • 栈内存的分配越大越好吗?

    • 不是。因为物理内存是一定的,栈内存越大,可以支持更多的递归调用,但是可执行的线程数就会越少
  • 方法内的局部变量是否是线程安全的?

    • 如果方法内局部变量没有逃离方法的作用范围,则是线程安全的
      如果如果局部变量引用了对象,并逃离了方法的作用范围,则需要考虑线程安全问题

2.3、栈内存溢出

Java.lang.stackOverflowError 栈内存溢出

  1. 栈帧过多导致内存溢出
  2. 栈帧过大导致栈内存溢出
/**
 * -Xms 为jvm启动时分配的内存,比如-Xms200m,表示分配200M
 * -Xmx 为jvm运行过程中分配的最大内存,比如-Xms500m,表示jvm进程最多只能够占用500M内存
 * -Xss 为jvm启动的每个线程分配的内存大小
 * 栈帧过多导致内存溢出演示
 * -Xss256k
 * @author xjy
 * @Date 2021/12/3 9:37
 */
public class StackOverflowDemo {

    private static int count;

    public static void main(String[] args) {
        try {
            method1();
        }catch (Throwable e){

            e.printStackTrace();
            System.out.println(count);
        }

    }

    private static void method1() {
        count++;
        method1();
    }

}

public class StackOverflowDemo2 {
    public static void main(String[] args) throws JsonProcessingException {
        Dept d = new Dept();
        d.setName("Market");

        Emp e1 = new Emp();
        e1.setName("zhang");
        e1.setDept(d);

        Emp e2 = new Emp();
        e1.setName("li");
        e1.setDept(d);

        d.setEmps(Arrays.asList(e1,e2));

        ObjectMapper mapper = new ObjectMapper();
        // 把java对象输出成字符串实例
        System.out.println(mapper.writeValueAsString(d));
    }
}

class Emp{
    private String name;
    private Dept dept;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Dept getDept() {
        return dept;
    }

    public void setDept(Dept dept) {
        this.dept = dept;
    }
}

class Dept{
    private String name;
    private List<Emp> emps;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Emp> getEmps() {
        return emps;
    }

    public void setEmps(List<Emp> emps) {
        this.emps = emps;
    }

2.4、线程诊断

CPU占用过高

  • Linux环境下运行某些程序的时候,可能导致CPU的占用过高,这时需要定位占用CPU过高的线程
    • top命令,查看是哪个进程占用CPU过高
    • ps H -eo pid,tid,%cpu | grep 47296(进程id)刚才通过top查到的进程号通过ps命令进一步查看是哪个线程占用CPU过高
    • jstack 进程id通过查看进程中的线程的nid,刚才通过ps命令看到的tid来对比定位,注意jstack查找出的线程id是16进制的,需要转换

2.5、本地方法栈

一些带有native关键字的方法就是需要JAVA去调用本地的C或者C++方法,因为JAVA有时候没法直接和操作系统底层交互,所以需要用到本地方法

2.5、堆

定义
通过new关键字创建的对象都会被放在堆内存

特点

  • 所有线程共享,堆内存中的对象都需要考虑线程安全问题
  • 有垃圾回收机制

2.6、堆内存溢出

java.lang.OutofMemoryError :java heap space. 堆内存溢出

堆内存诊断

  • jps
  • jmap
  • jconsole
  • jvisualvm

1.jps查看java进程

2.jmap -heap
Cannot connect to core dump or remote debug server. Use jhsdb jmap instead
(这里不知道为什么会出错可能jdk版本不一样我用的是jdk11)
改用. jhsdb jmap --heap --pid 22188

终端输入,jconsole即可调出jconsole图像界面
在这里插入图片描述

2.7、方法区

方法区是所有java虚拟线程的共享区,它存储了跟类相关的信息,有类的成员变量,方法数据,成员方法,构造方法。
由此可见,方法区存的是类相关的信息。

方法区是在虚拟机启动是被创建,逻辑上是堆的一部分。
在这里插入图片描述
方法区结构
在这里插入图片描述

内存溢出

  • 1.8以前会导致永久代内存溢出
  • 1.8以后会导致元空间内存溢出

2.7、常量池

二进制字节码的组成:类的基本信息、常量池、类的方法定义(包含了虚拟机指令)
通过反编译来查看类的信息

  • javac编译文件,获得.class文件

  • 在控制台输入 javap -v 类的绝对路径

    • 类的基本信息在这里插入图片描述
    • 常量池
      • 虚拟机中执行编译的方法(框内的是真正编译执行的内容,#号的内容需要在常量池中查找
        在这里插入图片描述
  • 类的方法定义
    在这里插入图片描述
    运行时常量池

  • 常量池

    • 就是一张表(如上图中的constant pool),虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量信息
  • 运行时常量池

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

常量池与字符串池的关系
串池StringTable
特征

  1. 常量池中的字符串仅是符号,只有在被用到时才会转化为对象
  2. 利用串池的机制,来避免重复创建字符串对象
  3. 字符串变量拼接的原理是StringBuilder 字符串常量拼接的原理是编译器优化
  4. 可以使用intern方法,主动将串池中还没有的字符串对象放入串池中
    注意:无论是串池还是堆里面的字符串,都是对象
public class StringTableStudy {
	public static void main(String[] args) {
		String a = "a"; 
		String b = "b";
		String ab = "ab";
	}
}

常量池中的信息,都会被加载到运行时常量池中,但这是a b ab 仅是常量池中的符号,还没有成为java字符串

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: return

当执行到 ldc #2 时,会把符号 a 变为 “a” 字符串对象,并放入串池中(hashtable结构 不可扩容)

当执行到 ldc #3 时,会把符号 b 变为 “b” 字符串对象,并放入串池中

当执行到 ldc #4 时,会把符号 ab 变为 “ab” 字符串对象,并放入串池中

最终StringTable [“a”, “b”, “ab”]

注意:字符串对象的创建都是懒惰的,只有当运行到那一行字符串且在串池中不存在的时候(如 ldc #2)时,该字符串才会被创建并放入串池中。

使用拼接字符串变量对象创建字符串的过程

public class StringTableStudy {
	public static void main(String[] args) {
		String a = "a";
		String b = "b";
		String ab = "ab";
		//拼接字符串对象来创建新的字符串
		String ab2 = a+b; 
	}
}
 Code:
      stack=2, locals=5, 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/Str
ing;
        27: astore        4
        29: return

通过拼接的方式来创建字符串的过程是:StringBuilder().append(“a”).append(“b”).toString()

最后的toString方法的返回值是一个新的字符串,但字符串的值和拼接的字符串一致,但是两个不同的字符串,一个存在于串池之中,一个存在于堆内存之中。

String ab = "ab";
String ab2 = a+b;
//结果为false,因为ab是存在于串池之中,ab2是由StringBuffer的toString方法所返回的一个对象,存在于堆内存之中
System.out.println(ab == ab2);

使用拼接字符串常量对象的方法创建字符串

public class StringTableStudy {
	public static void main(String[] args) {
		String a = "a";
		String b = "b";
		String ab = "ab";
		String ab2 = a+b;
		//使用拼接字符串的方法创建字符串
		String ab3 = "a" + "b";
	}
}
  • 使用拼接字符串常量的方法来创建新的字符串时,因为内容是常量,javac在编译期会进行优化,结果已在编译期确定为ab,而创建ab的时候已经在串池中放入了“ab”,所以ab3直接从串池中获取值,所以进行的操作和ab = “ab” 一致。
  • 使用拼接字符串变量的方法来创建新的字符串时,因为内容是变量,只能在运行期确定它的值,所以需要使用StringBuilder来创建

intern方法 1.8
调用字符串对象的intern方法,会将该字符串对象尝试放入到串池中

如果串池中没有该字符串对象,则放入成功
如果有该字符串对象,则放入失败
无论放入是否成功,都会返回串池中的字符串对象

注意:此时如果调用intern方法成功,堆内存与串池中的字符串对象是同一个对象;如果失败,则不是同一个对象

例1

public class Main {
	public static void main(String[] args) {
		//"a" "b" 被放入串池中,str则存在于堆内存之中
		String str = new String("a") + new String("b");
		//调用str的intern方法,这时串池中没有"ab",则会将该字符串对象放入到串池中,此时堆内存与串池中的"ab"是同一个对象
		String st2 = str.intern();
		//给str3赋值,因为此时串池中已有"ab",则直接将串池中的内容返回
		String str3 = "ab";
		//因为堆内存与串池中的"ab"是同一个对象,所以以下两条语句打印的都为true
		System.out.println(str == st2);
		System.out.println(str == str3);
	}
}

例2

public class Main {
	public static void main(String[] args) {
        //此处创建字符串对象"ab",因为串池中还没有"ab",所以将其放入串池中
		String str3 = "ab";
        //"a" "b" 被放入串池中,str则存在于堆内存之中
		String str = new String("a") + new String("b");
        //此时因为在创建str3时,"ab"已存在与串池中,所以放入失败,但是会返回串池中的"ab"
		String str2 = str.intern();
        //false
		System.out.println(str == str2);
        //false
		System.out.println(str == str3);
        //true
		System.out.println(str2 == str3);
	}
}

intern方法 1.6
此时无论调用intern方法成功与否,串池中的字符串对象和堆内存中的字符串对象都不是同一个对象

StringTabe
在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值