内存结构
1. 程序计数器
1.1 定义
Program Counter Register 程序计数器 (寄存器)
1.2 作用
-
记住下一条jvm指令的执行地址,在物理上通过寄存器实现
-
特点:
-
是线程私有的 (随着线程创建而创建,随着线程销毁而销毁)
-
不会存在内存溢出的区 (是一块较小的内存空间)
-
2. 虚拟机栈
2.1 定义
Java Virture Machine Stacks (Java虚拟机栈)
-
每个线程运行时所需要的内存,成为虚拟机栈
-
每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存
-
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
问题辨析:
-
垃圾回收是否涉及栈内存?
答:不会涉及
-
栈内存分配越大越好吗?
答:不是,每个线程在创建的时候都会创建一个虚拟机栈,而物理内存是固定的,栈内存划分的越大,可分配的线程数就越少
-
方法内的局部变量是否线程安全?
答:
-
判断变量是否是线程安全的,首先判断它是多个线程共享还是某个线程私有的
-
如果方法内局部变量没有逃离方法的作用范围,它是线程安全的 (比如将变量当做方法返回值返回,它就会存在线程安全问题)
-
如果这个局部变量引用了对象,并逃离了方法的作用范围,需要考虑线程安全
-
2.2 栈内存溢出
-
栈帧过多导致栈内存溢出 (java.lang.stackOverflowError)
-
栈帧过大导致栈内存溢出
2.3 线程运行诊断
-
案例一:cpu占用过多
-
案例二:程序运行很长时间没有结果
3. 本地方法栈
作用:给本地方法的运行提供空间
4. 堆
4.1 定义
Heap 堆
-
通过 new 关键字,创建对象都会使用堆内存
特点
-
它是线程共享的,堆中对象都需要考虑线程安全问题
-
有垃圾回收机制
4.2 堆内存溢出
堆内存溢出 (java.lang.OutOfMemoryError:Java heap space)
5. 方法区
5.1 定义
-
Java虚拟机中有一个被所有jvm线程共享的方法区。方法区有点类似于传统编程语言中的编译代码块或者操作系统层面的代码段。它存储着每个类的构造信息,譬如运行时的常量池,字段,方法数据,以及方法和构造方法的代码,包括一些在类和实例初始化和接口初始化时候使用的特殊方法。
-
方法区在jvm启动时候被创建。虽然方法区在逻辑层面上是堆的一部分,但是就简单实现来说既不会被回收也不会被压缩。这个规范并不强制指定方法区存放的位置也不会对编译过的代码有管理策略的限制。方法区可能有一个固定的大小或者也可以通过计算大小去扩展也可以在不需要的时候被压缩。方法区的内存也不需要是连续的。
5.2 方法区内存溢出
-
1.8以前会导致永久代内存溢出
-
1.8之后会导致元空间内存溢出
5.3 运行时常量池
-
常量池,就是一张表,虚拟机指令根据这张表找到要执行的类名、方法名、参数类型、字面量等信息
-
运行时常量池,常量池是*.class文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
5.4 StringTable 特性
-
常量池中的字符串仅是符号,第一次用到时才变为对象
-
利用串池的机制,来避免重复创建字符串对象
-
字符串变量拼接的原理是 Stringbuilder (1.8)
-
字符串常量拼接的原理是编译期优化
-
可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
5.6 StringTable 位置
1.7之后,StringTable从方法区转移到了堆中
5.7 StringTable 性能调优
-
调整 -XX:StringTableSize=桶个数
-
考虑将字符串对象是否入池
6.直接内存
6.1 定义
Direct Memory
-
常见于NIO操作,用于数据缓冲区
-
分配回收成本较高,但读写性能高
-
不受JVM内存回收管理
6.2 分配和回收原理
-
使用了Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
-
ByteBuffer 的内部实现类,使用了 Cleaner (虚引用) 来检测 ByteBuffer 对象,一旦 ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHander 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存