简析JVM内存结构

本篇笔记不定期更新

内存结构图

先上图
在这里插入图片描述
这张图差不多就把JVM的内存结构以及和class源文件,JVM执行引擎,以及操作系统自带的本地方法接口之间的关系囊括进去了。

下面我们就JVM内存结构的几个组成部分来逐一简单介绍一下

1. 程序计数器

1.1 定义

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

1.2 作用

  1. 在执行当前指令时,记住下一条JVM指令的执行地址,所以PCR也被称为寄存器,在物理上通过CPU寄存器实现
  2. 在多线程程序中起到一个记录上下文的作用,方便切换线程时可以继续运行

1.3 特点

  • Java支持多线程,而PCR是线程私有的,每个线程都有自己的PCR
  • 不会存在内存溢出

2. 虚拟机栈

栈–线程运行需要的内存空间,由栈帧组成,栈帧看为栈内的元素

栈帧–每个方法运行时所需要的内存,参数,局部变量,返回地址等等····

当调用某个方法时,会给栈帧分配内存,并将这个栈帧压入栈中,运行完毕后,会释放内存,也就是弹出栈帧。

当方法调用方法时,就会压入多个栈帧

2.1 定义

Java Virtual Machine Stacks

  • 每个线程运行时需要的内存,称为虚拟机栈
  • 每个栈由多个栈帧(Frame)祖传,对应着每次方法调用时所占用的内存,一般来说是局部变量
  • 每个线程只能由一个活动栈帧,对应着当前正在执行的那个方法,也就是目前栈顶的那个栈帧

问题辨析

  1. 垃圾回收是否涉及栈内存?不涉及,栈内存只和方法有关,方法运行完毕后栈帧出栈,内存自动回收。

  2. 栈内存分配越大越好吗?不是,内存分配得越大,线程就越少,因为物理内存是确定的,内存分配的越大,能更多次的进行方法递归。一般来说,默认的分配内存已经够用了。

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

    是的,因为每个线程只对应一个虚拟机栈,和其他方法的线程是互不干扰的,本质上操作的局部变量完全没有关系,因此是线程安全的。但如果不是局部变量,而是静态变量,或者是方法参数、或者是局部变量作为返回值返回了,那么就线程不安全,因为这时不同线程操纵的变量是同一个变量了。

    简单来说,如果方法内局部变量没有逃离方法的作用范围,那么他就是线程安全的

2.2 栈内存溢出

-Xss 栈内存分配大小命令

StackOverflowError

  • 栈帧过多导致栈内存溢出,如递归调用但没设置中止条件,或是出现了循环引用问题
  • 栈帧过大导致栈内存溢出

2.3 实际演示

在idea中,通过断点调试,我们可以观测到栈帧的存在
运行如下代码

public class StackTest {
  public static int add(int a, int b) {
    return a + b;
  }

  public static void main(String[] args) {
    add(1, 2);  //在这句打断点
  }
}

程序运行到断点处停止,我们看debugger窗口
在这里插入图片描述
可以看得到Frames这个子窗口,这表示的就是栈帧的集合,也就是虚拟机栈,我们的程序在main方法中的add暂停,所以目前栈帧中只有一个main方法,我们运行到下一步看看
在这里插入图片描述
可以看到main栈帧的上方有了add,这符合栈后进先出的特点,可以预见,如果addd中继续调用方法,那么add栈帧之上又会有新的栈帧。

现在我们继续运行程序,让add方法运行完毕
在这里插入图片描述
可以看到add的栈帧消失了,也就是add方法的运行内存被释放了。
这个小demo可以看出栈帧扮演的角色和运行方式。

3. 本地方法栈

Native Method Stacks

本地方法:Native Method ,不是Java编写的代码,通常是操作系统自带的方法代码。

本地方法栈为本地方法的运行提供内存空间

4. 堆

4.1定义

Heap 堆

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

特点

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

4.2 堆内存溢出

-Xmx 堆内存分配大小命令

OutOfMemoryError : Java heap space

垃圾回收:没人用的对象,就作为垃圾被回收

堆内存溢出:大量的对象被不断创建,同时一直被使用,可能导致堆内存溢出

4.3 堆内存诊断

在idea控制台窗口即可运行

  1. jps工具
    • 查看当前系统中有哪些Java进程
  2. jmap工具
    • 查看堆内存占用情况 ,不连续,只能查看某一时刻的情况
    • jmap -heap 进程id
  3. jconsole
    • 图形界面,多功能检测工具,可连续监测

案例:

  • 垃圾回收后,内存占用率仍然很高
  • 使用jvisualvm工具,通过堆dump功能查看对象在某一时刻的具体情况,从而做出诊断

5. 方法区

5.1 定义

Method Area

方法区是所有Java虚拟机线程共享的区域,它存储了与类结构相关的信息,如成员变量,方法数据,成员方法和构造器的代码部分,运行时常量池。

方法区在虚拟机启动时就被创建,逻辑上它是堆的组成部分,但具体实现不同的jvm有所不同

5.2 组成

参照官方笔记

5.3 方法区内存溢出

-XX:MaxMetaspaceSize 设置元空间大小

OutOfMemoryError:Metaspace

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

5.4 运行时常量池

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

5.5 StringTable特性

  • 常量池中的字符串仅是符号,第一次用到时才变为对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是StringBuilder(jdk1.8)
  • 字符串常量拼接的原理是编译期优化
  • 可以使用intern方法,主动将串池中还没有的字符串对象放入串池
    • 1.8中,将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回
    • 1.6中,将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则会把此对象复制一份,再放入串池,会把串池中的对象返回

5.6 StringTable位置

  • 1.8,StringTable在堆(Heap)中
  • 1.6,StringTable在永久代(PermGen)中

5.7 StringTable垃圾回收

5.8 StringTable性能调优

  • StringTable本质上是哈希表,因此调优就是调整桶的个数,适当的把桶的个数调大,减少哈希碰撞 -XX:StringTableSize=桶个数
  • 考虑将字符串对象是否入池

6. 直接内存

6.1 定义

Direct Memory

  • 常见于NIO操作,用于数据缓存区
  • 分配回收成本较高,但读写性能高
  • 不受JVM内存回收管理

6.3 分配和回收原理

  • 使用了Unsafe对象完成直接内存的分配回收,并且回收需要主动调用的freeMemory方法
  • ByteBuffer的实现类内部,使用了Cleaner(虚引用)来检测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会有ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放直接内存
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值