白话JVM之内存区域与内存溢出异常

内存区域与内存溢出异常

Java虚拟机运行时数据区

Java虚拟机运行时区域只是一个逻辑概念、定义了一个规范,也就是《java虚拟机规范》。在程序代码不停的运行去使用内容时,定义一下属于哪块内存,更方便了内存管理以及垃圾收集。除了最常用的hotpot虚拟机外,其他厂商的虚拟机对内存的管理也要遵循这个规范。

程序计数器

简答理解:程序计数器最简单理解就是程序运行到了哪一行,不然多线程运行状态下,线程切换回来都不知道之前运行到哪一行了就乱套了,所以他是线程隔离的。

深入一下:如果调用的Native方法,则计数器为空(Undefined)

虚拟机栈

简单理解:虚拟机栈是存放局部变量表、操作数栈、动态链接、方法出口等信息的。栈的特点都知道就是先进后出,比如要写一个1+2=3的方法,最简单就是定义变量A=1,B=2,然后输出A+B,计算机只听命令,你让他去哪他去哪,所以要告诉他去找A,还有去找B,还有A和B要干嘛,A和B就是局部变量,“+”就是操作。动态链接比较抽象,大概意思就是把对方法区的符号引号转化为对内存地址的直接引入,这个有待进一步理解,方法出口就是我方法内调用方法后,内部的那个方法结束后该去哪接着执行外面那个。

深入一下:局部变量表存放的东西一般有:八大基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference),其中long和double占用两个变量槽,其他的占用一个。

本地方法栈

本地方法栈就是执行Native方法的地方

简单理解:堆是java内存管理最大的那块,理论上(先不管逃逸分析等)所有你new出来的对象或者数组都在堆上开辟空间,也就是大家都用过的-Xmx和-Xms,堆在物理上不一定连续,但是逻辑上要连续,我猜这是为了GC算法。

方法区

简单理解:方法区其实和堆差不多,只是管理的东西不一样,经常百度面试题的时候会发现,方法区总是和jdk7绑在一起,因为jdk7以前,方法区就是存对象类型、字符串常量池、静态变量这些,永久代都听过,可以这么理解,方法区是一个接口,永久代是实现类,这刚好符合上面说的,Java虚拟机运行时区域只是个规范,jdk6以后,oracle官方觉得这样哪里不对,就慢慢改造了下永久代这个实现类,jdk7就把字符串常量池、静态变量等移到了堆里,这就出现一个经典的面试题,就是字符串比较在不同jdk版本结果不一样,jdk8以后,永久代就无了,换成了元空间,其实就是实现方法变了而已。

运行时常量池

也是方法区的一部分,和字符串常量池有区别,存放的是字面量(final类型的、基本数据类型的值、文本字符串等等)、符号引用(类的描述,方法名称描述等)。

深入一点(这里有待深一步理解):符号引用比较通俗的回答

直接内存

这块儿不是虚拟机规范里的,也就是他不受java运行时内存的管理,一般比较常用到这的就是,NIO中的缓冲区,调用C语言库的时候。

OOM

除了程序计数器,其他地方都会oom,因为都涉及到内存分配,就都有可能内存不过,其中本地方法栈和虚拟机栈,由于线程空间大小有限,也会出现StackOverflowError。

对象的创建

当我们new了一个对象后,hotSpot都干了啥呢?
简单理解:new完以后,堆里面就要分配内存给他了,想象一下在教室,大家蒙着眼找座位,如果每个对象大家都排排坐,我就直接从第一个顺过去,看到第一个空位坐下,这就是指针碰撞,如果大家都乱坐,只能找个拥有上帝视角的人记录哪空,告诉我哪里可以坐,这就是空闲列表,具体使用哪个,和垃圾收集机制有关。
如果是好几个同时抢座位,只能是大家都尝试,只能一个人成功,我发现坐别人腿上了,就去下一个座位,这就是CAS,或者是我在进门的时候就预定一个位置,我就先坐下,如果我是俩人一起的,那没办法,只能再申请了,这就是(TLAB线程预先申请内存)。
内存分配完以后,其实对象只是刚找到位置,然后才会开始创建对象,也就是Class文件中的,可以参照周志明《深入理解JAVA虚拟机》的2.3.1的hotSpot源码来看一个对象是怎么被创建的。

对象的内存布局

对象分配一个连续的内存后,自己在内部也是有小的划分,基本三个组成:对象头、实例数据、对齐填充。

对象头就是给虚拟机提供额外服务的。比如多线程下我怎么让对象不出问题,我要对堆进行GC的时候不能乱GC等问题,所以这个对象就需要有个地方去配合多线程工作(偏向锁id,指向锁记录的指针,指向重量级锁的指针),还需要配合GC(分代年龄)

实例数据就是存你对象里的那些属性的。包括继承来的。

对齐填充就是为了r满足hotSpot对象的大小必须是8字节的整倍数。
深入一点:实例数据中默认的分配顺序是:Longs/doubles、ints、shorts、chars、bytes/booleans、oops,是按照长度从大到小的,而且相同宽度的总在一起放着。所以有继承的情况下,一般都是父类的在前面,因为他们已经排好了。

对象的访问定位

对象的访问定位有两个:句柄访问和直接指针
刚上面说的虚拟机栈里的 局部变量表,除了基本数据类型外,还有一个对象引用,reference,这个东西就有用了。
刚才说到,一个对象本身是在堆里开辟的内存,就是常说的实例,方法区还有这个对象的具体的数据。
1、句柄访问的方式,在堆里有一个句柄池,维护着一个对象在堆中的实例数据和在方法区中的类型数据,我在虚拟机栈中的reference就直接指向这个句柄池中对应的句柄地址就好,对象发生变化只需要修改句柄池的指针
2、直接地址,就是虚拟机栈中的reference指向堆中的实例数据,然后实力数据在指向方法区中的对象类型数据。这中就比较快,而且省空间,hotSpot用的就是这种。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值