深入理解JVM虚拟机总结——Java内存区域与内存溢出异常

目录

一、运行时数据区域:

1.1 程序计数器

1.2 Java虚拟机栈

1.3 本地方法栈

1.4 Java堆

1.5 方法区

1.5.1 运行时常量池(方法区的一部分)

1.6 直接内存

二、对象的创建

2.1.过程

2.1.1需要考虑的问题?

2.2 对象的内存布局

2.2.1 对象头

2.2.2 实例数据

2.2.3对齐填充

2.3对象访问定位


一、运行时数据区域:

1.1 程序计数器

定义:是一块较小的内存空间,可以看作是当前线程所执行的字节码行号指示器。

要点:

1.字节码解释器就是通过改变这个计数器的值来选取下一条要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等

2.每个线程有自己独立的程序计数器,各条线程之间互相不影响。

3.如果线程在执行Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址。

   如果正在执行的是Native方法,这个计数器值为空

4.此区域在java虚拟机中是唯一一个没有OOM情况的区域。

1.2 Java虚拟机栈

定义:也是线程私有的,描述的是Java方法执行的内存模型。每个方法在执行是会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口等。方法的调用对应栈帧在虚拟机栈中的出栈入栈过程。

要点:

1.局部变量表:存放了编译期可知的各种基本数据类型,对象引用。所需空间在编译期间内完成分配,在进入方法时,这个方法分配的空间是固定的,且在方法运行时也不会改变大小。

2.该区域规定了两种异常情况

①如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError;

②虚拟机是可以动态扩展的,如果扩展时无法申请到足够内存,则会抛出OOM;

1.3 本地方法栈

定义:与虚拟机栈类似,本地方法栈为虚拟机使用到的Native方法服务。

要点该区域也会抛出两种异常,StackOverflowError与OOM。有的虚拟机会把Java虚拟机栈与本地方法栈合二为一。

1.4 Java堆

定义:是Java虚拟机所管理的内存中最大的一块,Java堆是被所有线程共享的一块内存区域,唯一目的就是存放对象实例,几乎所有对象实例都在这里分配。他也是垃圾回收管理的主要区域。也被很多人称为GC堆。

要点:

1.从内存回收角度,因为很多采用分代收集算法,所以java堆可以细分为新生代和老年代,再细致分为Eden空间,From Survivor空间,To Survivor空间等。

2.java堆可以处在内存不连续的空间中。

3.当没有空间分配时会抛出OOM异常。

1.5 方法区

定义:与Java堆一样,是各个线程共享的区域,用于存储一杯虚拟机加载的类信息,常量,静态变量,及时编译器编译后的代码等数据。

要点:

1.被称为永久代

2.该区域的回收目标主要针对常量池的回收和对类型的卸载。

3.当方法区无法满足内存分配会抛出OOM

1.5.1 运行时常量池(方法区的一部分)

定义:Class文件中除了有类的版本,字段,方法接口等描述信息外,还有一项就是常量池用于存放编译器生成的各种字面量和符号引用。这部分将会在类加载后进入方法区的运行时常量池。

要点:

1.具有动态性,Java不要求一定在编译期才能产生常量,在运行时也可以将新的常量放入池中,比如String类的intern方法。

2.当内存不够依旧抛出OOM

1.6 直接内存

定义:不是虚拟机运行时的数据区部分,也不是java虚拟机规范中定义的内存区域。和NIO使用有关,可以使用Native函数库直接分配堆外内存,然后通过DirectByteBuffer来操作。

二、对象的创建

2.1.过程

step 1. 当虚拟机遇到“new”指令,先去检查这个指令的参数能否在常量池中定位到一个类的符号引用,并且检查这个类是否已被加载,解析和初始化过。

step 2.没有加载过的类就回去执行类加载过程(后续类加载中讲)

step 3.当上面步骤结束,就会为新生对象分配内存,分配的大小在第二步便可确认。此时有两种分配方式对应虚拟机内存的另种情况

①当虚拟机内存是规整的,一边是分配的一边是未分配的,中间放着一个指针来作为分界点,而分配内存就是讲指向空闲区域的指针挪动一段与对象大小相等的距离这种叫指针碰撞。

②如果内存不规整,则虚拟机通过维护一个列表来记录那块内存区域是可用的,从而找到足够分配内存的空间给对象实例。这种叫空闲列表。

以上两种的选择由Java堆是否规整来决定,而是否规整则由所采用的垃圾收集器是否带有压缩整理功能决定。

2.1.1需要考虑的问题?

如果通过修改指针的方式,在并发的情况会有一些问题,如果给A分配内存,指针还未来得及移动,就分配B。两种方案解决

①对分配内存空间做同步处理。

②把内存分配的动作按照线程划分在不同空间中进行。即每个线程在Java堆中预先分配一小块内存,成为本地线程分配缓冲区,只有当次区域分配完才需要同步锁定。

step 4.虚拟机会对对象进行必要设置,ex:这个对象是哪个类的实例,如果找到类的元数据信息,对象的哈希码,对象的GC分代年龄,这些信息存放在对象头之中。

step 5.从虚拟机角度一个新对象已经创建,从java角度还没有执行init,所有字段为零,所以当new执行完,会接着执行init。

2.2 对象的内存布局

2.2.1 对象头

定义:包含两部分信息

① 用于存储对象自身的运行时数据,如哈希码,GC分代年龄,锁状态标识,线程持有的锁,偏向线程ID,偏向时间戳。

② 类型指针,即对象指向它的类元数据的指针。

2.2.2 实例数据

定义:存储的真正有效信息,在代码中定义的字段内容,包括从父类继承下来的,这部分会受到虚拟机分配策略参数和字段在java源码中的定义顺序影响。

2.2.3对齐填充

定义:不是必然存在,没有特别含义,仅仅起占位符的作用。

2.3对象访问定位

如何通过引用找到堆中的具体对象?

① 句柄访问,Java堆中会划分出一块内存来作为句柄池,引用存储对象句柄的地址,句柄中包含对象实例数据与类型数据各自的地址信息。

②通过直接指针访问,引用中直接存储对象地址。

对比:

1.句柄访问好处,引用中存储稳定的句柄地址,在对象被移动时,只会改变句柄中的实例数据指针,而引用本身不需要改变。

2.直接指针访问好处,速度更快,节省了一次指针定位的时间开销。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值