JAVA 内存
一、程序计数器
- 虚拟机需要通过『程序计数器』记录指令执行到哪了。
- 线程要轮流使用 CPU 时间片,因此需要『程序计数器』来记住正在执行的字节码的地址。例如
线程 A
的计数器记录当前执行到了第三行字节码,这时候时间片用完了,CPU 切换到其它线程运行,当 CPU 再次切换到线程 A
时,它就会从计数器得知上次执行的代码位置,继续向下运行。
二、虚拟机栈
- 作用: 一个线程使用的内存大小。线程内调用一次方法,就会产生一个栈帧,栈帧内包含方法内局部变量,方法参数,返回地址等。多个栈帧合称为『栈』,而正在执行的方法称为『活动栈帧』,一个线程内同一时刻只能有一个『活动栈帧』
- 配置:
java
-Xss
The default value depends on the platform:
* Linux/x64 (64-bit): 1024 KB
* macOS (64-bit): 1024 KB
* Oracle Solaris/x64 (64-bit): 1024 KB
* Windows: The default value depends on virtual memory
*
- 特点:
- 方法执行完毕,栈帧内存即被释放
- 因为线程私有,不存在共享,因此线程安全
- 值越大,会让线程数更少
- 栈内存溢出情况
- 栈太小,方法调用过深(栈帧太多)
- 栈太小,方法内局部变量太多(栈帧太大)
- 测试代码如下:
java
public class Demo1 {
private static int count = 0;
public static void main(String[] args) {
method1();
}
private static void method1() {
count ++ ;
System.out.println(count);
method1();
}
}
三、本地方法栈
- 每个线程启动时,还会分配『本地方法栈』内存,来给哪些其它语言实现的方法(称为本地方法)使用。
+
四、堆
- Java堆通常是Java虚拟机所管理的内存中最大的一块。Java堆是被锁有线程共享的一块内存区域,在虚拟机启动时创建。这块区域唯一的目的就是存放对象实例,几乎所有对象实例及数组都在该区域分配内存,从 JDK1.7 开始,StringTable等也会使用堆内存。
- Java堆时垃圾收集器管理的主要区域(GC堆),从内存回收的角度(收集器一般采用分代收集算法),堆被划分为新生代和旧生代,新生代又被进一步划分为Eden(伊甸园) 和 Survivor(幸存区) 区,最后Survivor由FromSpace和ToSpace组成,结构图如下所示:
- 堆空间内存分配(默认情况下)
-
老年代 : 三分之二的堆空间
-
年轻代 : 三分之一的堆空间
-
eden区: 8/10 的年轻代空间
-
survivor From : 1/10 的年轻代空间
-
survivor To : 1/10 的年轻代空间
五、非JAVA内存(堆外内存)
1.元空间(Metaspace)
-
作用:用来存储类对象,类加载器,静态变量,StringTable,SymbolTable,即时编译器生成的代码等。
-
历史:
- 『方法区』是 Java VM 规范中定义的概念,具体实现根据各个虚拟机厂商的不同而不同。对于 Oracle 的 HotSpot 虚拟机来说,最初作为『方法区』的实现称之为『永久代』,从 Java 8 开始,『永久代』被替换为『元空间』。
- 『永久代』,垃圾回收仍然会考虑『永久代』,但回收效率不高,StringTable 最初也使用的是『永久代』内存,容易造成 OOM 问题。
- 『元空间』,使用了操作系统内存,默认没有上限。并且 StringTable 的空间被移至堆内存,『元空间』中仅存储类加载器、类对象等信息,垃圾回收不用考虑『元空间』,元空间自己管理内存释放。
2.直接内存
- 定义:在 NIO 进行 IO 操作时,用到的数据缓冲内存 DirectBuffer
- 特点:典型实现由 DirectByteBuffer,它使用了堆外内存,可以用 allocateDirect 方法创建
- 好处:
- 没有使用堆内存,减少 GC 压力
- I/O 读写操作直接操作堆外内存,省去了系统空间和用户空间的数据拷贝
- 堆外内存回收通过虚引用实现