概述
c和c++拥有内存管理的权利,而java则是通过虚拟机的自动内存管理机制去管理内存,不需要我们手动进行内存管理。自动内存管理机制不容易出现内存泄露问题,但是一旦出现内存泄露和内存溢出问题,排查问题就需要java虚拟的相关知识。
运行时的数据区域
这里借助一张书里的图片
线程私有内存区域
程序计数器
程序计数器这个概念是在计算机组成原理课程中首次见到。在jvm里,程序计数器是一块较小的内存空间,作用可以看做是当前线程执行的字节码行号指示器。字节码解释器的工作就是通过改变计数器的值来选取下一条要执行的指令,这和计算机组成原理所学内容是吻合的。
java虚拟机多线程的实现是通过 线程轮流切换 并 分配处理器执行时间 来实现的,类似于操作系统的进程调度。对于一个处理器(多核中的一个核)来说,任何一个确定时刻,只会执行一个线程中的一条指令,所以多线程的实现,就需要线程不断切换。线程切换后,要恢复到原来执行位置,这就需要每个线程都有一个私有的程序计数器。这就解释了为什么程序计数器是线程私有的内存区域。
程序计数器如果执行的是Native的方法,值为空。程序计数器,是唯一一块不会出现 OOM的内存区域
java虚拟机栈
java虚拟机栈的生命周期和线程一样。
虚拟机栈描述 java方法执行的内存模型:每个方法执行的时候会创建一个栈帧,方法被调用执行到执行完成的过程就是 栈帧入栈到出栈的过程。 栈帧保存的是 局部变量表,操作栈,动态链接,方法出口等信息。
局部变量表: 即方法体内的局部变量,包括基本数据类型变量,对象引用 reference,returnAddress类型。以变量槽(Slot)为最小单位
returnAddress类型:一条字节码的地址
动态链接:指向运行时,常量池中该方法的引用。
这个内存区域会抛出两种异常,StackOverFlowError-线程的请求的栈深度大于虚拟机允许的深度。还有另外一个就是OOM
信息的具体意义
https://blog.csdn.net/qq_40121580/article/details/107441214?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2aggregatepagefirst_rank_ecpm_v1~rank_v31_ecpm-1-107441214.pc_agg_new_rank&utm_term=%E6%96%B9%E6%B3%95%E5%87%BA%E5%8F%A3&spm=1000.2123.3001.4430
各种信息类型所占大小
https://blog.csdn.net/a616413086/article/details/51272309
线程公有内存区域
本地方法栈
本地方法栈的作用和java虚拟机栈的作用十分类似,只是本地方法栈执行的是native方法,而虚拟机栈执行的是java方法。有的虚拟机是将本地方法栈和虚拟机栈合而唯一。例如 Sun HotSpot。
java堆
java堆是虚拟机管理内存最大的一块,用于存放对象实例。这是一块线程共享的内存区域,虚拟机启动时创建。
java堆是垃圾回收器管理的主要区域。由于现在收集器基本采用分代收集算法,所以java堆还分为几个区域。
java堆分为老年代和新生代。新生代又分为 eden区,from Survior区,to Survior区。
java堆可以物理上不联系,只要逻辑上连续即可。
java堆如果没有内存完成实例分配,而且不能扩展,则会抛出 OOM异常。
方法区
方法区是线程共享的内存区域,用于存储 已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
Hotspot虚拟机也把方法区称为永久代,但其他虚拟机没有永久代这个概念。
垃圾回收行为在这个区域比较少出现,回收目标是常量回收和类型卸载
这个区域无法满足分配需求时,会抛出OOM异常。
运行时常量池
运行时的常量池是方法区的一部分,用于存放 编译期生产的各种字面量和符号引用,这些内容在类加载后,会被放入运行时的常量池中。一般情况下,被翻译出来的直接引用也会被储存在运行时的常量池中。
运行时的常量池还具备动态性,不止编译期产生的常量,运行期间产生的新常量也可以放入常量池中。例如String类的intern()方法。
常量池无法再申请到内存时,会报OOM异常。
JDK1.6后,常量池被放入了堆
String.intern解析,挺深刻的,建议阅读
https://blog.csdn.net/seu_calvin/article/details/52291082
符号引用和直接引用
https://blog.csdn.net/weixin_41490593/article/details/95110259
直接内存
直接内存不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域,但是这部分内存被频繁使用。可能到时OOM。
JDK1.4中加入了NIO(new Input/Output)类,一种基于通道与缓冲区的i/o方式,它可以使用Native的函数库直接分配堆外内存,然后通过java堆里的DirectByBuffer对象作为这块内存的引用,避免了Java堆和Native堆之间来回复制数据,从而提高性能。
对象访问
最简单的对象访问,都会涉及java栈,java堆,方法区三个内存区域。
Object obj=new Object();
前面的声明,会在java栈的局部变量表里,产生一个reference类型的数据。 而new的过程,则会在java堆里生成一块包含所有实例数值,对象类型数据信息地址的结构化内存。而实例对象的类型数据,则存放在方法区里。
主流的对象访问方式有两种,使用句柄和直接指针。
使用句柄
java堆会划分一块内存,作为句柄池,里面包含对象实例数据的具体地址和类型数据的具体地址。reference中储存得就是句柄地址。
优点:refercence中存放的是稳定的句柄地址,对象在gc时移动,reference不用变,只用改变实例数据地址即可
直接指针
java堆对象布局中,就要考虑如何放置访问类型数据的相关信息(对象类型数据地址在对象实例数据里),reference中就是对象地址。
**优点:**访问速度快,节省了一次指针定位的时间。sun hot spot使用的是直接指针访问
|