JVM1 内存结构

一、程序计数器

在这里插入图片描述
1、定义: Program Counter Register 程序计数器(寄存器),程序计数器在物理上通过寄存器实现。
2、作用: 记住下一条JVM指令的执行地址,根据执行地址找到对应的指令。
3、特点

  • 线程私有:每个程序都有自己的程序计数器
  • 不会存在内存溢出

4、程序执行流程: 二进制字节码(jvm指令)-》解释器-》机器码-》CPU

二、虚拟机栈

在这里插入图片描述

1、定义: Java Virtual Machine Stacks (Java 虚拟机栈),每个线程运行时所需要的内存,称为虚拟机栈。
2、栈帧: 每个方法运行时需要的内存(参数 局部变量 返回地址),一个虚拟栈可有多个栈帧
3、活动栈帧: 线程正在调用的方法栈帧
4、调用流程: 使用方法时将栈帧压入虚拟栈,使用完后释放内存
5、注意:

  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

6、问题

  • 垃圾回收是否涉及栈内存?
    • 垃圾回收只回收堆内存,不回收栈内存
  • 栈内存分配越大越好吗?
    • 栈内存分配越大,线程内存越少,因此不是越多越好
  • 方法内的局部变量是线程安全?
    • 如果方法内局部变量没有逃离方法的作用范围,它是线程安全的
    • 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全

7、栈内存溢出

  • 栈帧过多导致栈内存溢出(例如方法递归调用结束条件不正确)
  • 栈帧过大导致内存溢出

8、线程运行诊断

  • 案例一:CPU占用过多(LInux环境下)
    • 用top定位哪个进程对cpu的占用过高
    • ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
    • jstack 进程id
      • 可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号(nid为16进制)
  • 案例二:程序运行没有结果(线程死锁)
    • Jstack 进程id deadlock

三、本地方法栈

在这里插入图片描述
作用:给本地方法调用提供内存空间

  • 例如:Object类中的native方法

四、堆

在这里插入图片描述
1、定义: Heap 堆,通过new关键字创建对象都会使用堆内存
2、特点

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

3、堆内存溢出: 例如new的对象过多导致堆内存溢出
4、堆内存诊断

  • 打开IDEA 控制台
  • Jps-查看java进程
  • Jmap-查看堆内存占用情况 jamp -heap 进程id Eden Space used
  • Jconsole-图形界面 多功能检测 连续监测 Jconsole打开建立连接

5、案例

  • 垃圾回收后,内存占用仍然很高
    • 使用Java VisualVm工具
    • 监视-》堆dump-》查找

五、方法区

在这里插入图片描述
1、定义: 所有Java虚拟机共享的一块区域,存储类结构有关的信息。方法区在虚拟器启动时创建,逻辑上是堆的一部分。方法区也存在内存溢出的错误。

  • 1.6方法区实现被称为永久代
    在这里插入图片描述
  • 1.8以后方法区实现被称为元空间
    在这里插入图片描述

2、内存溢出

  • 1.8以前永久代空间不足导致内存溢出
  • 1.8以后元空间不足(默认使用系统内存,没有限制)导致内存溢出
  • 实际场景:框架使用(spring、mybatis)

3、运行时常量池

  • 反编译查看字节码:Java -p 类名 反编译
  • 常量池:就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
  • 运行时常量池:常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址

4、StringTable

  • 定义:实际为串池,结构为hashtable,不能扩容

  • 特点

    • 常量池中的字符串仅是符号,第一次用到时才变为对象
    • 利用串池的机制(延迟加载),来避免重复创建字符串对象
    • 字符串变量拼接的原理是 StringBuilder (1.8)
    • 字符串常量拼接的原理是编译期优化
    • 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
      • 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
      • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池,会把串池中的对象返回
    • 面试题
      	    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")
      	    x2.intern();
      	    String x1 = "cd";
      	
      	    // 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢
      	    System.out.println(x1 == x2);//true,调换后为false 1.6为false
      
  • 位置

    • 1.6 是常量池的一部分
    • 1.8 是StringTable的一部分
  • 性能调优

    • 调整 -XX:StringTableSize=桶个数(字符串常量越多,桶个数相应调大)
    • 考虑将字符串对象是否入池 (存在重复)

六、直接内存

1、定义: Direct Memory

  • 常见于 NIO 操作时,用于数据缓冲区

  • 分配回收成本较高,但读写性能高

    • Java文件读取流程
      在这里插入图片描述
    • 使用直接内存读取流程,相比于IO操作少了一次复制操作
      在这里插入图片描述
  • 不受 JVM 内存回收管理

2、内存溢出:直接内存也会存在内存一出问题

3、内存释放原理

  • 使用了 Unsafe 对象完成直接内存的分配(allocateMemory)和回收,并且回收需要主动调用 freeMemory 方法
    public static void main(String[] args) throws IOException {
        Unsafe unsafe = getUnsafe();
        // 分配内存
        long base = unsafe.allocateMemory(_1Gb);
        unsafe.setMemory(base, _1Gb, (byte) 0);
        System.in.read();

        // 释放内存
        unsafe.freeMemory(base);
        System.in.read();
    }

    public static Unsafe getUnsafe() {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            Unsafe unsafe = (Unsafe) f.get(null);
            return unsafe;
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
  • ByteBuffffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffffer 对象,一旦 ByteBuffffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调 用 freeMemory 来释放直接内存
 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1Gb);
        System.out.println("分配完毕...");
        System.in.read();
        System.out.println("开始释放...");
        byteBuffer = null;
        System.gc(); // 显式的垃圾回收,Full GC
        System.in.read();
  • 禁用显示垃圾回收会导致直接内存得不到释放
    • 解决:unsafe对象调用freeMemory释放
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值