黑马程序员JVM笔记01-内存结构

内存结构

程序计数器

在这里插入图片描述

  • 由寄存器实现,用来记住下一条JVM指令的执行地址
  • 线程私有
  • 不存在内存溢出

虚拟机栈

在这里插入图片描述

特点

  • 线程运行时需要的内存空间,一个线程对应一个栈
  • 每个线程只能有一个活动栈帧,即正在执行的方法(栈顶)
    在这里插入图片描述

问题:
1、垃圾回收是否涉及栈内存?不涉及
2、栈内存是否越大越好?
3、方法内的局部变量是否线程安全?
若方法内的局部变量没有逃离方法的作用范围(没有被return),就是线程安全的

栈内存溢出(StackOverflowError)

  • 栈帧过多导致栈内存溢出(如循环递归)
  • 栈帧过大导致栈内存溢出(较少出现)
  • 设置虚拟机栈的大小:-Xss Size

线程运行诊断

  • CPU占用过多诊断:
    linux环境下:
    使用top命令监测哪个进程占用CPU过高

    在这里插入图片描述
    使用ps H -eo pid,tid,%cpu | grep 32655进一步定位是哪个线程引起的cpu占用过高
    在这里插入图片描述
    使用jstack pid定位到有问题的线程(十进制线程号要转成十六进制),进一步定位到问题代码行数

  • 程序运行长时间无结果诊断(排查死锁):
    使用jstack pid
    在这里插入图片描述

本地方法栈

在这里插入图片描述

在这里插入图片描述

特点

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

堆内存溢出(OutOfMemoryError)

  • 设置堆大小:如:-Xmx8m改成8M

堆内存诊断

  • jps工具
    查看当前数系统中有哪些Java进程
    在这里插入图片描述

  • jmap工具
    查看该堆内存占用情况
    在这里插入图片描述
    在这里插入图片描述

  • jconsole工具
    图形界面的多功能的检测工具,支持连续监测
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 案例:垃圾回收后,内存占用仍然很高
    使用jvisualvm工具
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

方法区

在这里插入图片描述

特点

  • HotSpot虚拟机:JDK1.8以前,方法区的实现叫永久代,是堆内存的一部分;JDK1.8以后,方法区的实现叫元空间,是本地内存(操作系统)的一部分,默认无上限(电脑内存决定)
    在这里插入图片描述
    在这里插入图片描述

方法区内存溢出(OutOfMemoryError)

  • JDK1.8前设置永久代大小:-XX:MaxPermSize=8m
  • JDK1.8后设置元空间大小:-XX:MaxMetaspaceSize=8m

运行时常量池

  • 常量池就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息

    反编译字节码:
    在这里插入图片描述
    在这里插入图片描述

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

StringTable

一道面试题:

String s1 = "a";
String s2 = "b";
String s3 = "a" + "b";
String s4 = s1 + s2;
String s5 = "ab";
String s6 = s4.intern();
// 问
System.out.println(s3 == s4);
System.out.println(s3 == s5);
System.out.println(s3 == s6);
String x2 = new String("c") + new String("d");
String x1 = "cd";
x2.intern();
// 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢
System.out.println(x1 == x2);
StringTable特性
  1. 常量池中的信息都会被加载到运行时常量池中,此时的"a"、“b”、"ab"都是常量池中的符号,还没有变为Java中的字符串对象
// 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 == s5);
    }
}
  1. 利用串池的机制,来避免重复创建字符串对象
  2. 字符串变量拼接的原理是 StringBuilder (1.8)
  3. 字符串常量拼接的原理是编译期优化
  4. 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
    • JDK1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
      在这里插入图片描述

    • JDK1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回
      在这里插入图片描述

StringTable的位置

从1.6到1.8对StringTable位置的改动:更容易触发对StringTable的垃圾回收
在这里插入图片描述
演示StringTable内存溢出:

  • 在jdk8下设置堆最大内存并关闭UseGCOverheadLimit: -Xmx10m -XX:-UseGCOverheadLimit
    在这里插入图片描述

  • 在jdk6下设置永久代最大内存: -XX:MaxPermSize=10m
    在这里插入图片描述

StringTable垃圾回收
  • StringTable中的字符串在没有引用时也会被垃圾回收
  • 演示StringTable垃圾回收:-Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
public class Demo1_7 {
    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        try {
            for (int j = 0; j < 100000; j++) { // j=100, j=10000
                String.valueOf(j).intern();
                i++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            System.out.println(i);
        }
    }
}

StringTable性能调优
  1. 调整桶个数
  • StringTable底层也是哈希表,所以StringTable的性能调优主要也是改变底层桶的个数

  • 桶的个数默认60013,自定义设置必须在规定区间
    在这里插入图片描述

  • 调整桶个数,测试耗费时间:-Xms500m -Xmx500m -XX:+PrintStringTableStatistics -XX:StringTableSize=1009
    在这里插入图片描述

  1. 考虑是否将字符串对象入池
  • 不入池
    在这里插入图片描述
  • 入池(调用intern方法)
    在这里插入图片描述

直接内存

特点

  • 直接内存,即操作系统的内存
  • 常用于NIO操作时,作为数据缓冲区
  • 分配回收成本较高,但读写性能高
public class Demo1_9 {
    static final String FROM = "E:\\编程资料\\第三方教学视频\\youtube\\Getting Started with Spring Boot-sbPSjI4tt10.mp4";
    static final String TO = "E:\\a.mp4";
    static final int _1Mb = 1024 * 1024;

    public static void main(String[] args) {
        io(); // io 用时:1535.586957 1766.963399 1359.240226
        directBuffer(); // directBuffer 用时:479.295165 702.291454 562.56592
    }

    private static void directBuffer() {
        long start = System.nanoTime();
        try (FileChannel from = new FileInputStream(FROM).getChannel();
             FileChannel to = new FileOutputStream(TO).getChannel();
        ) {
            ByteBuffer bb = ByteBuffer.allocateDirect(_1Mb);
            while (true) {
                int len = from.read(bb);
                if (len == -1) {
                    break;
                }
                bb.flip();
                to.write(bb);
                bb.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.nanoTime();
        System.out.println("directBuffer 用时:" + (end - start) / 1000_000.0);
    }

    private static void io() {
        long start = System.nanoTime();
        try (FileInputStream from = new FileInputStream(FROM);
             FileOutputStream to = new FileOutputStream(TO);
        ) {
            byte[] buf = new byte[_1Mb];
            while (true) {
                int len = from.read(buf);
                if (len == -1) {
                    break;
                }
                to.write(buf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.nanoTime();
        System.out.println("io 用时:" + (end - start) / 1000_000.0);
    }
}

为什么直接内存的效率更高?

传统javaIO:
在这里插入图片描述
使用allocateDirect:
在这里插入图片描述

  • 不受JVM垃圾回收管理
    也会有内存溢出的情况:
    在这里插入图片描述

分配和回收的原理

  • 测试代码:
public class Demo1_26 {
    static int _1Gb = 1024 * 1024 * 1024;

    /*
     * -XX:+DisableExplicitGC 显式的
     */
    public static void main(String[] args) throws IOException {
        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 方法

  • ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测ByteBuffer 对象,一旦ByteBuffer 对象被垃圾回收,那么就会由ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存
    在这里插入图片描述

    在这里插入图片描述

思考上面的测试代码,问题:
当禁用显式的垃圾回收:-XX:+DisableExplicitGC,则System.gc();就会失效,所以即使设置byteBuffer = null;,由于堆内存空间仍充裕,byteBuffer不会被马上回收,进而影响到直接内存的释放

解决:直接手动使用Unsafe 对象完成直接内存的分配回收

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值