运行时数据区概念
Java虚拟机在运行Java程序时,需要一定内存存储运行时所需的全部数据(类文件信息、对象、引用、常量等等),JVM采用栈、堆、方法区等等将这些数据所占内存分区域管理,有的区域随着虚拟机进程的启动而创建,有的则随着用户线程(不需要内核支持而在用户程序中实现的线程)的启动和结束而建立和销毁,Java虚拟机规范(JDK7)规定的运行时数据区如下图所示。
各区域的区别与作用
建议看这张表的同时结合表后的知识点(标红的信息)看
区域 | 存放数据 | 特点 | 补充说明 |
堆 | 所有的对象实例及数组 | 1.对于大多数应用,其所占内存区域最大,是JVM内存回收的重点区域,也称‘GC堆’ 2.随着虚拟机进程启动而创建,所有线程共享 3.按照垃圾回收‘分代回收’策略,Java堆可分为新生代、老年代;再细分可分为Eden空间、From Survivor空间、To Survivor空间 4.Java堆内存在物理上可处于不连续的内存空间,但是逻辑上连续即可,可通过-Xmx、-Xms扩展Java堆大小,如果在堆中无内存分配,并且不可扩展时,发生OOM | 1.随着JIT编译器发展和逃逸分析技术发展,栈上分配、标题替换优化技术导致所有对象在堆上分配变得不那么绝对 2.有可能线程共有的Java堆划分出多个线程私有的分配缓冲区(TLAB),因为多线程的情况下,对象地址分配可能不安全,所以每个线程分配一块不会冲突的内存区域,保证线程安全,虚拟机可以设置参数是否开启TALB |
方法区 | 已被虚拟机加载的类信息(类版本、字段、方法等)、常量(final)、静态变量(static)、即时编译器(JIT)编译的代码等等 | 1.线程共享 2.Java虚拟机对其限制很宽松,不需要连续的内存,可以选择固定大小也可通过-XX:PermSize,-XX:MaxPermSize进行扩展,还可以选择不进行垃圾回收(但是这部分区域的回收确实是必要的) | 1.对于HotSpot虚拟机,因其把垃圾回收扩展至方法区,所以该区域也称‘永久代’,但是HotSpot虚拟机也规划将放弃永久代(放弃原因),因为这样更容易遇到内存溢出问题 2.垃圾收集行为在这个区域的回收是比较少见的,但并非不进行回收,其主要回收的目标是对常量池的回收和对类型的卸载,但是回收的成绩比较难另人满意 |
运行时常量池(属于方法区) | 编译期生成的各种字面量和符号引用 | 1.运行时常量池相对于Class文件常量池具备动态性,即常量并不一定只有在编译期产生,运行时也可将新的常量放入池中(String.intern()方法) | 1.不同的提供商实现的虚拟机可以按照自己的需要来实现这个区域,但是一般来说,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储到运行时常量池中 |
程序计数器 | 类似字节码的行号指示器 | 1.线程私有 2.次内存区域没有规定OOM | 1.字节码解释器在工作时通过改变这个计数器的值来选择下一条需要执行的指令 2.分支,跳转,异常处理,循环,线程切换都依赖这个程序计数器 3.如果正在执行的是一个Java方法,则计数器记录的字节码指令的地址;如果执行的是一个native方法,则计数器的值为空 |
虚拟机栈 | 编译期可知的各种基本数据类型、对象引用等 | 1.线程私有 2.栈帧中局部变量表的大小在编译时已确定 | 1.虚拟机栈描述的是Java方法的内存模型(栈帧) 2.每一个方法从调用直至执行完成的过程,对应着一个栈帧从虚拟机中入栈到出栈的过程 3.如果线程请求的栈深度大于虚拟机允许的栈深度,将抛出StackOverflowError;如果栈空间扩展时无法申请到足够的内存,将抛出OOM |
本地方法栈 | 1.为虚拟机调用native方法服务 | 1.本地方法栈的作用类似于虚拟机栈,在虚拟机规范中堆本地方法栈中使用的语言、使用方法和数据结构没有规定,不同的虚拟机自由实现,有的虚拟机(HotSpot)将其合二为一 | |
直接内存 | 1.其不属于JVM运行时数据区,但也可能导致OOM | 1.JavaNIO类,通过Native函数库(Unsafe类)直接分配对外内存,然后通过存储在堆中的对象最为这块内存的引用进行操作,可以在一些场景提升性能 2.其分配的内存大小不受堆大小限制,但是受本机总内存大小以及处理器寻址空间的限制 |
1.数组:数组引用存放在栈中,数组中的对象存放在堆中,基本数据类型和对象类型都是如此
2.放弃原因:HotSpot虚拟机才由 永久代的概念,而在JDK7中已将放在永久代中的字符串常量池移到堆中;HotSpot使用永久代是处于垃圾回收考虑,不必为方法区再写单独的垃圾回收机制,但是这样更容易出现OOM,因为我们通常通过-XX:PermSize和-XX:MaxPermSize设置方法区大小,但是通常我们不知道设置多大合适,所以一旦达到永久代上限,则容易出现OOM
3.类卸载:虚拟机加载、连接和初始化某个类,使其可用,但是当其生命周期结束,不可达时,虚拟机可选择卸载它(垃圾回收);而使用启动类加载器(虚拟机自带的类加载器)加载的类是不可被卸载的,只有用户自定义的类加载器加载的类才能被卸载;判定一个类是否可被卸载,需要满足类中所有的实例已被回收、该类的类加载器已经被回收、该类的Class对象没有任何地方被引用
4.字面量:例如int i=1;String s="hello";其中的i和hello都称为字面量
5.符号引用:用符号(任何形式的字面量)来表示所引用的对象,因为在编译时Java类并不知道所引用的类的实际地址,所以用符号引用来表示;符号引用相对于直接引用(指针、句柄等直接或间接定位到实际对象);在类加载阶段,JVM会将符号引用替换为直接引用
6.常量池相关概念可参考这个博文:https://blog.csdn.net/yuhualee/article/details/78549920
7.String.intern()方法:如果常量池中已经存在String对象(equals方法为true),则返回常量池中的对象;如果常量池中不存在,则将堆中的对象加入到常量池中,并返回堆中对象的一个引用,详情参考这个博文:https://blog.csdn.net/qq_38663729/article/details/78056959
8.栈帧:可参考这个博文:https://www.cnblogs.com/wade-luffy/p/6058067.html
9.栈深度:栈帧的个数,方法的调用链可能很长,无限递归等会导致StackoverflowError
该博文参考了深入理解Java虚拟机及一些博文,如有不对之处请指正!
-------------------------------------------------------------------------------------------------------------------
知其然,更要知其所以然...
-------------------------------------------------------------------------------------------------------------------