JVM内存管理(内存分配和内存溢出异常)
前言
上篇对JVM做了一个入门级的了解,之后的学习大部分都是根据JVM的体系结构细化出来学习的,在复习一下JVM体系结构看下图,今天总结一下java虚拟机的内存的各个区域,跟我一起了解虚拟机是怎样使用内存的,如何排查错误,让我们一起走JVM体系结构中的运行时数据区(内存区)
运行时数据区
Java虚拟机在执行Java程序 的过程中会把它管理的所有内存划分为若干个不同数据区域
- 橙色是由线程共享的区域,随着虚拟机的启动一直存在(线程私有)
- 蓝色则依赖用户线程的启动和结束而建立和销毁(线程隔离的数据区,线程共享)
程序技术器
可以看做是当前线程所执行的字节码的行号指示器,JVM工作时,就是通过改变这个计时器来选取下一条需要执行的字节码指令
Java虚拟机栈
虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,虚拟机会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等.
局部变量表: 存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、
、short、int、float、long、double),类型引用和returnAddress类型,局部变量表的内存空间在编译期间完成分配,方法在帧分配多少内存固定
本地方法栈
与虚拟机栈非常相似,区别是虚拟机栈执为虚拟机执行Java方法,而本地方法栈是为虚拟机使用到本地(Native)方法服务
Java堆
虚拟内存最大的一块,存放对象实例
方法区
存储已被虚拟机加载的类型信息,常量,静态变量,即使编译器编译后的代码缓存.
运行时常量池,也是方法区的一部分,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中
内存分配
介绍完java虚拟机的运行时数据区,大致知道了内存结构都有什么,了解了内存中放什么,那么它们是如何创建的,如何布局的及如何访问的.
对象的创建
创建兑现通常仅仅一个new关键字而已,而在虚拟机中,对象的创建时什么过程呢?
1.Java虚拟机遇到一个new字节码,首先检查这个指定的参数是否能再常量池中中定位到
2.检查这个符号引用代表的类是否被加载,解析和初始化,如果没有那必须执行类加载过程
3.类加载检查通过后,为新生对象分配内存
-
指针碰撞
从java堆中划分一块大小确定的内存,如果内存规整的,所有被使用的放在一边未被使用过的放在另一边,中间放着一个指针作为分界点的指示器,内存分配多少,指针像空闲方向就挪动多少 -
空闲列表
内存如果不是规整的,内存和空闲区相互交错在一起,那么使用空闲列表,用空闲列表记录剩余可用的内存空间,每次为变量分配内存后动态维护这个列表
对象内存布局
对象在堆内存中的存储布局可以划分为三个部分:对象头,实例数据和对齐填充
对象头包括两个部分:
1.用于存储对象自身的运行时数据,如哈希码,GC分带年龄,锁状态标志,线程持有的锁
2.对象的另一部分是类型指针,java虚拟机通过这个指针来确定该对象是哪个类的实例
实例数据: 真正存储有效信息,程序代码里定义的各种类型的字段内容
对齐填充: 没有特别意义,仅仅是占位符,HotSpot内存管理要求任何对象大小必须是8的倍数
对象的访问定位
对象有了,怎么去使用它呢?两种方式,句柄和直接指针,一般说两个东西的时候都会做对比优缺点,那么来比较比较它们两个
使用句柄: 如果使用句柄,java堆会划分出一块内存来作为句柄池,句柄中包含了实例数据与类型数据格子具体地址,看下图
优点:reference中存储稳定句柄,在对象移动时改变实例数据指针即可
使用直接指针:
reference直接就是对象地址,如果访问对象本身不需要多一次间接访问开销(和上图比少实例池)
优点:速度更快
内存溢出异常(OOM)
除了程序计数器外,虚拟机的其他几个运行时区域都会发生OOM异常,那么我们怎么排除是哪里出错了呢
总结
最后我们明白了虚拟机里边的内存结构与划分,如果内存溢出了可以快速定位到哪个结构什么原因溢出了,帮助我们可以快速解决问题.
本文地址:https://blog.csdn.net/a15076159739/article/details/108233132