背景知识
1、定义
Java Virtual Machine,JAVA程序的运行环境(JAVA二进制字节码的运行环境)
2、好处
一次编写,到处运行
自动内存管理,垃圾回收机制
数组下标越界检查
3、比较
一、内存结构
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 组成
注意:
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.6 StringTable 特性
(1)常量池中的字符串仅是符号,第一次用到时才变为对象
(2)利用串池的机制,来避免重复创建字符串对象
(3)字符串变量拼接的原理是 StringBuilder (1.8)
(4)字符串常量拼接的原理是编译期优化
(5)字符串常量池中存储的都是对象
(6)可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
- 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
- 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回
6、直接内存
6.1 定义Direct Memory
- 常见于 NIO 操作时,用于数据缓冲区
- 分配回收成本较高,但读写性能高
- 不受 JVM 内存回收管理
6.2 分配和回收原理
- 使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
- ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存
之前的读写
使用了DirectBuffer,直接内存是操作系统和Java代码都可以访问的一块区域,无需将代码从系统内存复制到Java堆内存,从而提高了效率