JVM
3.1 JVM内存区域/内存结构


线程私有的:
- 程序计数器
- 虚拟机栈
- 本地方法栈
线程共享的:
- 堆
- 方法区
- 直接内存 (非运行时数据区的一部分)
3.1.1 程序计数器
他的核心作用:用于存储下一个所要执行的JVM指令的内存地址
是当前线程所执行的字节码的行号指示器,它是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。
3.1.2 Java 虚拟机栈
- 每个线程运行需要的内存空间,称为虚拟机栈
- 每个栈由多个栈帧组成,对应着每次调用方法时所占用的内存
- 每个线程只有一个活动的栈帧,对应着正在运行的方法
虚拟机栈。每一个方法是一个栈帧。栈帧里面包含,变量,参数,返回值。会出现栈内存溢出。
执行过程中:当且仅有一个栈帧在执行。
问题辨析
- 垃圾回收是否涉及栈内存
- 不需要。因为一个线程一个虚拟机栈是有多个栈帧(方法嵌套顺序)组成的,在方法执行完成之后,对应的栈帧就会被弹出栈。所以无需通过垃圾回收机制回收内存。
- 栈内存的分配越大越好吗?
- 不是。因为电脑的物理内存是固定的,如果增大了虚拟机栈的内存,虽然当前线程可以支持更多的(递归)方法,但是整体上可执行的线程个数据就减少了。
- 方法中的局部变量是线程安全的吗?
- 如果局部变量没有脱离方法的作用范围,就是线程安全的变量
- 如果局部变量引用了对象,并且逃离了方法的作用范围,则需要考虑线程安全问题
3.1.3 本地方法栈
一些代用native关键字的方法就是需要JAVA调用本地的C或者C++方法。
因为Java有时候没法直接和操作系统底层交互,所有需要用到本地方法。
在执行本地方法的时候,会给其分配一片内存空间。
本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 两种错误。
3.1.4 堆
通过new的对象和数组都会存放在堆内存区域中【几乎所有的对象实例都在这里分配内存】。
Java堆是被所有线程共享的一块内存区域。
Java堆是Java虚拟机所管理的内存中最大的一块。
堆这里最容易出现的就是 OutOfMemoryError 错误。
Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap)。从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代(Eden、Survivor0、Survivor1)和老年代;进一步划分的目的是更好地回收内存,或者更快地分配内存。
3.1.5 方法区
JVM1.6的方法区的内容:

JVM1.8+的方法区调整为元空间,内部的常量池,class,classloader存储在直接内存中。stringtable移到堆内存区域中。

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域。
它用于存储JVM加载的类信息,常量,静态变量,即时编译器编译后的代码缓存等数据。
永久代:
JDK8以前,通常吧方法区称呼为永久代(Permanent Generation),或将俩者混为一谈。
本质上这俩这并不是等价的,因为仅仅是当时的HotSpot虚拟机设计团队选择把收集器的分代设计扩展至方法区,或者说使用永久代来实现方法区而已,这样使得虚拟机的垃圾收集器能够像管理Java堆一样管理这部分内存,省的专门为方法区编写内存管理代码的工作。
3.1.6 运行时常量池
用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
运行时常量池相对于class文件常量池另外一个重要特征是具备动态性,Java不要求常量一定只有编译期才能产生,运行期间也可以将新的常量放入池中,例如:string类的intern()方法。
既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常
3.1.7 直接内存
- 属于操作系统,常见于NIO操作是,用于数据缓冲区
- 分配回收成本较高,但读写性能高
- 不受JVM内存回收管理,会受到本机总内存大小以及处理器寻址空间的限制
而且也可能导致 OutOfMemoryError 错误出现。
使用场景例如:NIO中DirectBuffer,使用本地内存读取文件,效率提高。
