JVM(一)内存结构

72 阅读5分钟

背景知识

1、定义

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

2、好处

一次编写,到处运行

自动内存管理,垃圾回收机制

数组下标越界检查

3、比较

图片1.png

一、内存结构

图片2.png

1、程序计数器

1.1 定义

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

作用,是记住下一条jvm指令的执行地址

1.2特点

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

2、虚拟机栈

2.1 定义

Java Virtual Machine Stacks (Java 虚拟机栈)

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

问题

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

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

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

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

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

  • 如果方法内局部变量没有逃离方法的作用范围,则是线程安全的
  • 如果如果局部变量引用了对象,并逃离了方法的作用范围,则需要考虑线程安全问题

2.2 栈内存溢出

  • 栈帧过多导致栈内存溢出
  • 栈帧过大导致栈内存溢出

2.3 线程运行诊断

案例1: cpu 占用过多

定位

  • 用top定位哪个进程对cpu的占用过高
  • ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
  • jstack 进程id。可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号。(注意jstack查找出的线程id是16进制的,需要转换)

案例2:程序运行很长时间没有结果

3、本地方法栈

一些带有native关键字的方法就是需要JAVA去调用本地的C或者C++方法,因为JAVA有时候没法直接和操作系统底层交互,所以需要用到本地方法

4、堆

4.1 定义

Heap 堆

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

特点

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

有垃圾回收机制

4.2 堆内存溢出

4.3 对内存诊断

(1)jps 工具

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

(2)jmap 工具

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

(3)jconsole 工具

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

(4)jvirsalvm 工具

案例

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

5、方法区

5.1 定义

注意:方法区是一个逻辑概念

5.2 组成

图片3.png

图片4.png

注意:

1.5中常量池,包含运行时常量池,也包括字符串常量池也就是StringTable

1.7中字符串常量池也就是StringTable

1.8中字符串常量池也就是StringTable

5.3 方法区内存溢出

1.8 以前会导致永久代内存溢出(或者1.8之前方法区=永久代)

  • 演示永久代内存溢出 java.lang.OutOfMemoryError: PermGen space * -XX:MaxPermSize=8m

1.8 之后会导致元空间内存溢出

  • 演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace * -XX:MaxMetaspaceSize=8m

5.4 运行时常量池

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

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

5.5 StringTable

图片5.png

5.6 StringTable 特性

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

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

(3)字符串变量拼接的原理是 StringBuilder (1.8)

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

(5)字符串常量池中存储的都是对象

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

  • 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
  • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回

图片6.png

图片7.png

图片8.png

图片9.png

图片10.png

图片11.png

图片12.png

图片13.png

图片14.png

6、直接内存

6.1 定义Direct Memory

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

6.2 分配和回收原理

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

之前的读写

图片15.png

使用了DirectBuffer,直接内存是操作系统和Java代码都可以访问的一块区域,无需将代码从系统内存复制到Java堆内存,从而提高了效率

图片16.png

图片17.png

图片18.png

图片19.png