本栏目讲叙JVM简介、JVM内存结构、垃圾回收机制和类加载与字节码技术
文章目录
程序计数器
1、概述
- :用于记录下一条 JVM 指令的执行地址,是线程私有的,不存在内存溢出的情况
2、工作流程
- :
解释器
从程序计数器
中获取下一条 JVM 指令执行地址,解释器根据地址找到相关指令,将指令转换为机器码,再交由 CPU 执行
虚拟机栈
1、概述
- :线程运行时所需要的内存空间,由多个栈帧构成(栈帧由参数、局部变量和返回地址等组成),且每个线程只能有一个活动栈桢
2、栈内存溢出
- 栈帧过多(方法递归调用层级过多)
- 栈帧过大
3、查看线程情况
# 方式1:使用linux相关指令查看
# 查看进程CPU、内存的占用情况,还可以知道进程的PID
top
# 根据进程PID可以查看该进程下的所有线程的占用情况
ps H -eo pid,tid,%cpu | grep <pid>
# 方式2:使用jdk相关指令查看
# 查看Java进程,获取进程pid
jps
# 根据进程PID查看该进程下所有线程,再根据TID去查看原因
jstack <pid>
注意:单个栈空间分配越多,所能创建的线程数就越少
本地方法栈
概述
:调用本地方法(C/C++所编写的方法)运行时所需要的内存空间
堆(Heap)
1、概述
- :通过 new 关键字,创建的对象(包含对应的 class 信息=>方便获取对应的操作指令)都会使用堆内存。它是线程共享的,堆中的对象都需要考虑线程安全问题,具有垃圾回收机制
2、组成
- 新生代:由伊甸园、幸存区 From 和幸存区 To 组成,伊甸园用来存放临时引用的对象,幸存区 To 存放还有引用的对象,有希望进阶到永久代
- 永久代:存储长期使用的对象
3、查看堆情况
# 方式1:使用命令
# 查看Java进程,获取进程pid
jps
# 根据进程PID查看堆内存占用情况
jmap -heap <pid>
# 方式2:使用图形工具
1、JConsole
2、JVisualVM
方法区
1、简介
概述
:存储类结构相关的信息(类名称、成员变量、成员方法和构造函数等)和运行时常量池。该区域被线程共享组成
-
JDK 1.6 之前
:Class、ClassLoader和运行常量池(StringTable)使用永久代实现,存放在堆中
-
JDK1.8 之后
:Class、ClassLoader和运行常量池使用元空间实现,存放在直接内存中,而StringTable存放在堆中
-
2、常量池
概述
:就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息存储内容
- 符号引用
- 类和结构的完全限定名
- 字段名称和描述符
- 方法名称和描述符
- 字面量
- 文本字符串
- final常量值
- 基本数据类型的值
- 符号引用
3、运行时常理池
概述
:运行期间产生的常量、类加载后常量池中的信息都会存放到运行时常量池
4、StringTable
概述
:运行时常量池的一部分,用于存储字符串对象,由一个 HashTable 组成特性
// 常量池中的信息在类加载时加载到运行时常量池中,但这时像"Hello "常量在运行时常量池中只是符号,
// 只有在执行引擎加载该字符串的指令时,才会创建字符串对象,然后在StringTable中查找,
// 如果没有则存入该对象,如果有则使用StringTable中的对象
String s1 = "Hello ";
String s2 = "World";
// 字符串变量拼接的原理是在堆中new StringBuilder()对象去拼接
String s3 = s1 + s2;
// 字符串常量拼接原理是编译期优化,即在运行时已经优化成s4 = "Hello World"
String s4 = "Hello " + "World";
// 使用intern方法,主动将字符串对象放入StringTable
s4.intern();
特性
// 设置串池的大小桶大小
-XX:StringTableSize=<size> 例:-XX:StringTableSize=200000
// 打印串池中的统计信息
-XX:+PrintStringTableStatistics
// 案例:如果需要存放大量的字符串,且字符串有大量的重复,可以先让字符串入池(调用intern方法),
// 再将返回结果放在堆中,节约堆内存的使用
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream("words.txt"), StandardCharsets.UTF_8))) {
String word;
while (true) {
word = reader.readLine();
if (null == word) {
break;
}
word.intern();
}
} catch (IOException e) {
e.printStackTrace();
}
}
5、直接内存
概述
:即系统内存,常用于 NIO 操作时的数据缓冲区特点
- 分配和回收成本高,但读写性能高
- 不受 JVM 内存回收管理
分配与回收
- 创建 ByteBuffer 对象时使用 Unsafe 对象完成直接内存的分配
- ByteBuffer 内部使用了 Cleaner 对象(虚引用)来监测该对象,当该对象被回收时,那么就会由 ReferenceHandler 线程通过 Cleaner 对象的 clean() 方法调用 Unsafe的freeMemory() 来释放直接内存
禁用显示回收
:-XX:DisableExplictGC