程序计数器
- 线程私有
- 主要字节码解释器通过读取程序计数器来选取下一条需要执行的指令,比如分支,循环,跳转和异常处理
- 如果执行的是java 方法,那么程序计数器记录的时候虚拟机字节码指令的地址,如果执行的是native 方法,那么是计数器的值为空,
- 这个区域不会发生OOM。
虚拟机栈
- 线程私有
- 每个方法在执行是都会创建栈帧,用于存放局部变量,操作数栈,动态链接和方法出口
- 存放了编译时期可知的各种基本数据类型,对象引用, 和returnAddress 类型(指向了一条字节码指令的地址),其中long 和double 占用两个局部变量空间,其余类型占用一个,局部变量所需的空间再编译期完成分配,当进入一个方法时,分配多大的局部变量是已经确定的,不会改变局部变量的大小
- 如果线程请求的栈深度大于虚拟机允许深度,会抛出stackOverFlow 异常;如果虚拟机栈可以动态扩展,但是无法申请到足够的内存,那么将抛出outOfMemory 异常
本地方法栈
- 和虚拟机栈的功能类似,不同点主要是为虚拟机的native 方法服务。hotSpot 虚拟机直接把本地方法栈和虚拟机栈合二为一。
- 参数设置
-
- -xss 每个线程的栈大小,1.5+ 的每个线程栈大小为1M
Java堆
- 线程共享
- 主要存放对象实例,是java 内存收集器管理的主要区域,可以分为新生代和老年代。新生代又可以分为eden 区、from Survior区域和to Survior 区域。
- 参数设置
-
- -Xms 最小堆内存
- -Xmx 最大堆内存
- -XX NewSize 设置新生代最小空间大小
- -XX MaxNewSize 设置新生代最大空间大小
- -XX NewRatio 新生代和老年代的比例,比如NewRatio=2 ,那么新生代占2/3,老年代占1/3
- -XX SurviorRatio 新生代中eden 和survior 的比例,默认值为8
方法区(永久代)
- 线程共享
- 已经被虚拟机加载的存储类信息、常量、静态变量。
- 不同的虚拟机的不同方法区有不同的实现,hotSpot 方法区在1.7 前后发生了变化
-
- jdk1.7 版本把原本放在方法区的字符串常量放在了java 堆中;
- jdk8 则把永久代给删除了,取而代之的是metaSpace
- 参数设置
-
- -XX PermSize 设置永久代的最小空间大小
- -XX MaxPermSize 设置永久代最大空间大小
运行时常量池
- 是方法区的一部分
- 运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。
metaSpace
- 1.8 版本之后取代了永久代,因此元空间回收不是传统的垃圾回收过程中进行的
- 元空间的内存管理是通过对类和元数据的加载、卸载和转移来实现的。当类或元数据不再被使用时,会被标记为可卸载状态,并等待垃圾回收器执行卸载操作。元空间的回收是通过类和元数据的卸载来实现的,这些操作通常由JVM的类卸载器或元数据卸载器来执行。
- 为啥要用元空间替换永久代
-
- 动态分配内存:元空间的内存是在堆外分配的,并且可以动态调整大小,因此可以更好地适应应用程序的需要。
- 元空间的内存使用效率更高:元空间的实现方式不需要使用传统的对象引用,而是使用指针或其他方式来表示类和元数据,因此可以更有效地利用内存。
- 更易于调优:元空间的大小可以通过命令行参数或JVM启动参数进行设置和调整,而永久代的大小无法动态调整。