JVM内存结构

5.1 程序计数器

5.1.1定义

Program Counter Register程序计数器(寄存器)

  • 作用:用来记住下一条JVM指令执行地址,物理上用寄存器实现

  • 特点:

    • 是线程私有的。因为会切换线程,每一个线程需要记住自己执行到哪一行了

    • 不会存在内存溢出

5.2 虚拟机栈

5.2.1 定义

  • 栈:线程运行需要的内存空间

    • 每个线程运行时所需要的内存,称为虚拟机栈

    • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存

    • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

  • 栈帧:每个方法运行时需要的内存(如参数、局部变量、返回地址等)

  • 问题解析:

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

      答:不需要。因为栈内存里是栈帧内存,完成后就弹出,因此不需要回收

    2. 栈内存越大越好吗?

      答:不是。栈越大,能容纳的线程越少。可能会影响线程的数量。

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

      答:是。如果是私有的,则需要考虑线程安全,是共享的,则不需要考虑线程安全。

      是每个线程的局部变量,是每个线程私有的。

      如果方法内局部变量没有逃离方法的作用范围,那么就是线程安全的,反之则不安全(如果是引用对象或者返回值)

5.2.2 栈内存溢出

  1. 栈帧过多导致栈内存溢出(报的错是:StackOverDFlowError)

  2. 栈帧过大导致栈内存溢出

5.2.3 线程运行诊断

  1. 案例一:CPU占用过多

    定位:

    • 用top定位哪个进程堆cpu占用过高

    • ps H -eo pid,tid,%cpu | grep 进程id(用ps命令进一步定位是哪个线程引起的cpu占用过高)

    • jstack + 进程id:

      • 可以根据线程id找到有问题的线程,进一步定位到问题代码的源码行号

  2. 程序运行很长时间没有结果

    可能出现了死锁

5.3 本地方法栈

英文:Native Method Stack:给本地方法的运行提供接口,如Object类中的clone()方法,是用native修饰的

5.4 Java堆

程序计数器、虚拟机栈、本地方法栈是私有的,Java堆是公有的

5.4.1 定义

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

  • 特点:

    • 它是线程共享的,堆中对象都需要考虑线程安全的问题

    • 有垃圾回收机制

5.4.2 堆内存溢出

错误:OutOfMemoryError:Java heap space

5.4.3 堆内存诊断

  1. jps工具

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

  2. jmap工具

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

  3. jconsole工具

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

案例:用垃圾回收后,内存占用仍然很高

堆转储,用dump功能抓取目前占用内存最高的线程

5.5 方法区

5.5.1 方法区内存溢出

  1. 1.8以前会导致永久代内存溢出

    Java.lang.OutOfMemoryError:PerGen space

  2. 1.8以后会导致原空间方法溢出

    Java.lang.OutOfMemoryError:Metaspace

5.5.2 运行时常量池

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

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

String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = s1+s2;
String s5 = "a"+"b";

过程:

  1. 常量池中的信息,都会被加载到运行时常量池中,此时 a b ab都是常量池中的符号,还没有变成Java字符串对象

  2. ldc #2 会把a符号变成“a"字符串对象(先在stringtable里找,发现没有,则在stringtable里创建“a”对象)

  3. 只有在使用时才会创建,且此时的StringTable是个hashtable结构,["a","b","ab" ]不能扩容

说明不是一开始就创建了,而是边执行边创建,发现stringtable里没有,才会创建。(长度是java.lang.String的长度)

  1. 在第四步,过程是 new Stringbuilder().append("a").append("b").toString() new String("ab"),因为new了所以在堆里,故s3 != s4;(s3在StringTable里,而s4在堆里)

  2. s5执行时,javac在编译期间的游湖啊,结果已经在编译期确定为ab,不会再变化;而s4因为是引用变量,不确定会不会变化

5.5.3 StringTable特性

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

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

  • 字符串变量拼接的原理是StringBuileder

  • 字符串常量拼接的原理是编译期优化

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

String x = "ab";    
String s = new String("a") + new String("b")//new String("ab");此时s仅存在于堆中
String s2 = s.intern();//将这个字符串对象尝试放入串池,如果有则不会放入,没有则放入串池,会把串池中的对象返回
(s2 == x)?// 是的,因为串池中已经有了,所以就是x
(s == x)? //不是,因为是两个东西
​

5.5.4 StringTable位置

  • 在1.8以后,就放入了heap中,因为放在永久代中,只有在父辈被回收的时候才能被回收,但是串池用的地方很频繁,容易造成内存不够

5.5.5 StringTable性能调优

  • 调整 -Xx:StringTableSize=桶个数

    因为StringTable底层是个哈希表,因此桶越多,越不容易出现Hash碰撞,性能越快

  • 考虑将字符串对象是否入池

5.6 直接内存(directBuffer)

Direct Memory

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

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

  • 不受JVM内存回收管理

  • 使用了Unsafe对象完成直接内存的分配回收,并且回收需要主动调用unsafe.freeMemory(base)方法

  • ByteBuffer的实现类内部,使用了Cleaner(虚引用)来检测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHander线程通过Cleaner的clean方法调用freeMemory来释放直接内存

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值