jvm

JVM(1)

一、什么是JVM

本文档是在https://www.bilibili.com/video/BV1yE411Z7AP做的笔记。

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

好处

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

比较

JVM,JRE,JDK的区别
在这里插入图片描述

二、学习路线

在这里插入图片描述
1.class -->类加载器 -->加载到jvm内存结构中 --> 类基本都是放在方法区的部分 --> 类创建的实例对象放在堆中 --> 而堆里面的实例对象在调用方法时才会在虚拟机栈,程序计算器,本地方放栈中。

2.方法执行时,是由执行引擎中的解释器来执行每一行代码,方法里面的被频繁调用的代码,被即时编译器 来执行。

3.垃圾回收:会对堆里面的对象不再被引用,进行垃圾回收。

4.本地方法接口:就是调用底层,操作系统提供的接口交互。

JVM内存结构

三、程序计数器

1.物理上是通过寄存器来实现的。
在这里插入图片描述

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

多个线程运行的时候,cpu会有一个调度器会分配时间片,
时间片:在时间片内,代码没有执行完,会把这个状态还行一个暂存,切换到线程2去,线程2的时间片用完了,在切回来去线程1剩余的代码。

四、虚拟机栈

在这里插入图片描述

:一个栈是由多个栈帧组成,一个栈帧就对应一次方法的调用。
栈帧:每个方法运行时需要的内存。
方法执行完了,他会对应把方法对应的栈帧出栈,释放内存。
活动栈帧:在栈顶部的正在执行的方法。

1.定义

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

用debug模式调试,在Frames面板上就很清晰的看到压栈出栈的过程。
public class Stack {
    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;
    }
}
问题辨析

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

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

2.栈内存的分配越大越好吗?

不是。因为物理内存是一定的,栈内存越大,可以支持更多的递归调用,但是可执行的线程数就会越少。

3.方法内的局部变量是否是线程安全的?

如果方法内局部变量没有逃离方法的作用范围,则线程是安全的。
如果局部变量引用了对象,并逃离了方法的作用范围,则需要考虑线程安全的问题。在这里插入图片描述

public class ThreadSafety {

    public static void main(String[] args) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(4);
        stringBuilder.append(5);
        stringBuilder.append(6);
        new Thread(() -> m2(stringBuilder)).start();
    }
    public static void m1(){
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(1);
        stringBuilder.append(2);
        stringBuilder.append(3);
        System.out.println(stringBuilder.toString());
    }

    /*
     * 作为方法的参数传递进来,可能就会有其他的线程会访问进来,可能对多个线程是共享的
     */
    public static void m2(StringBuilder stringBuilder) {
        stringBuilder.append(1);
        stringBuilder.append(2);
        stringBuilder.append(3);
        System.out.println(stringBuilder.toString());
    }

    /*
     *这个方法是返回了,其他线程就有可能拿到对象去调这个方法,改变返回值。所以也是线程不安全的
     */
    public static StringBuilder m3() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(1);
        stringBuilder.append(2);
        stringBuilder.append(3);
        return stringBuilder;
    }
}
2.栈_内存溢出
  1. 栈帧过多导致栈内存溢出(递归调用)
  2. 栈帧过大导致占内存溢出

代码案例:

public class StackOverflow {

    private static int count;

    // 栈帧过多,导致栈内存溢出
    // 可以设置 -Xss256k
    public static void main(String[] args) {
        try {
            method();
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("12312321");

        }

    }

    private static void method() {
        count++;
        method();
    }
}
public class StackDolls {
    /*
     *循环引用导致栈内存溢出
     */

    public static void main(String[] args) throws JsonProcessingException {
        Dept dept = new Dept();
        dept.setName("market");

        Emp emp = new Emp();
        emp.setName("zhang");
        emp.setDept(dept);

        Emp emp1 = new Emp();
        emp1.setName("li");
        emp1.setDept(dept);

        dept.setEmps(Arrays.asList(emp, emp1));

        ObjectMapper objectMapper = new ObjectMapper();
        System.out.println(objectMapper.writeValueAsString(dept));

    }
}

class Emp {
        private String name;
        // 生成json格式,忽略这个属性
        @JsonIgnore
        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.3.线程运行诊断
  1. cpu占用过多

定位:

  • 用top定位那个进程对cpu的占用过高
  • ps H -eo pid,tid,%cpu l grep进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
  • jstack进程id
  • 线程id 转换成16进制,再看nid。
  • 可以根据线程id找到有问题的线程,进一步定位到问题代码的源码行号
  1. 程序运行很长时间没有结果
    就是两个线程互相抢锁,导致成为死锁。

在这里插入图片描述

五、堆

之前的虚拟机栈还有本地方法栈,都是线程私有的,而堆,方法区是线程公有的。

1.定义

Heap 堆

  • 通过new关键字,创建对象都会使用堆内存

特点

  • 它是线程共享的,堆中对象都需要考虑线程安全的问题
  • 有垃圾回收机制
2.堆内存溢出
public class HeapOverflow {
    /*
     * java.lang.OutOfMemoryError: Java heap space  堆内存溢出
     * - Xmx8m
     */
    public static void main(String[] args) {
        int i = 0;

        try {
            ArrayList<Object> list = new ArrayList<>();
            String s = "加1";
            while (true) {
                list.add(s);
                s = s + s;
                i++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println(i);
        }
    }
}
3.堆内存诊断

1.jps工具

  • 查看当前系统中有哪些java进程

2.jmap 工具

public class HeapDiagnosis {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("1....");
        Thread.sleep(30000);
        byte[] bytes = new byte[1024 * 1024 * 10];
        System.out.println("2....");
        Thread.sleep(30000);
        bytes = null;
        System.gc();
        System.out.println("3.....");
        Thread.sleep(1000000L);
    }
}

在这里插入图片描述

命令行:jps
就会显示进程id

  • 查看堆内存占用情况,jmap - heap 进程id

3.jconsole工具

  • 图形界面的多功能的检测工具,可以连续监测

命令行直接 打jconsole 显示图形化界面线程,点进去可以直接看到堆,内存各种信息。

4.jvisualvm工具

命令行输入jvisualvm,可以进行排查堆内存谁占用最多

六、方法区

在这里插入图片描述

方法区就是储存一些类的数据。

1. 方法区内存溢出

1.8以前是永久代内存溢出
1.8以后是元空间内存溢出

public class MethodArea extends ClassLoader{
    // 演示元空间内存溢出
    // -XX:MaxMetaspaceSize=8m   设置内存大小
    public static void main(String[] args) {
        int j = 0;

        try {
            MethodArea methodArea = new MethodArea();
            for (int i = 0; i < 10000; i++, j++) {
                // ClassWriter 作用是生成类的二进制字节码
                ClassWriter classWriter = new ClassWriter(0);
                // 版本号,public,类名,包名,父类,接口
                classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class"+i, null, "java/lang/Object", null);
                // 返回bute[]
                byte[] code = classWriter.toByteArray();
                // 执行了类的加载
                methodArea.defineClass("Class"+i, code, 0, code.length); // class 对象
            }
        } finally {
            System.out.println(j);
        }
    }
}
2.常量池

输入编辑java文件 成为class文件后
在控制台输入 javap -v 类的绝对路径
就能在控制台看到反编译类的信息

在这里插入图片描述
常量池
在这里插入图片描述
在这里插入图片描述
虚拟机中执行编译的方法(框内的是真正编译执行的内容,#号的内容需要在常量池中查找)

在这里插入图片描述
运行时常量池

  • 常量池
    就是一张表(如上图中的constant pool),虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量信息
  • 运行时常量池
    常量池是.class文件中的,当该*类被加载以后,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
3.StringTable特性
  • 常量池中的字符串仅是符号,第一次用到时才变为对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是StringBuilder(1.8)
  • 字符串拼接的原理是编译期优化
  • 可以使用intern方法,主动将串池中还没有的字符串对象放入串池。
  • 1.8 讲这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回。
  • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池,会把串池中的对象返回。
public class StringTableTest {
    // 进入编辑好的class文件,输入命令   javap -v xxx.class
    // StringTable ["a", "b", "ab"] hashtable 结构,不能扩容
    public static void main(String[] args) {
        // 常量池中的信息,都会被加载到运行时常量池中,这时 a b ab都是常量池中的符号,还没有变为java字符串对象
        // ldc #2 会把 a 符号变为 "a" 字符串对象
        // ldc #3 会把 a 符号变为 "b" 字符串对象
        // ldc #4 会把 a 符号变为 "ab" 字符串对象
        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);  // 一个在常量池中,一个在堆内存中
        System.out.println(s3 == s5); // ldc           #4 String ab 

    }
}

intern特性

String x = "ab";
        String q = new String ("a") + new String("b");

        String w = q.intern(); // 将这个字符串对象尝试放入串池中,如果有则不会放入,如果没有则放入串池,会把串池中的对象返回

        System.out.println(w == x);
        System.out.println(q == x);

在这里插入图片描述

4.String Table存放位置

在这里插入图片描述

public class StringTableTest {

    /**
     * 演示StringTable位置
     * 在jdk8下设置 -Xmx10m -XX :-UseGCoverheadlimit
     * 在jdk6下设置-XX:MaxPermSize=10m
     * stringTable:1.8 在堆中存放,1.6 在永久代存放
     */

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        int i = 0;

        try {
            for (int j = 0; j < 260000; j++) {
                list.add(String.valueOf(j).intern());
                i++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }finally {
            System.out.println(i);
        }

    }
}

七、直接内存

1.定义
  • 常见于NIO操作时,用于数据缓冲区
  • 分配回收成本较高,但读写性能高
  • 不受JVM内存回收管理
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值