前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >简述JVM基础(二):Java内存区域与内存溢出异常

简述JVM基础(二):Java内存区域与内存溢出异常

作者头像
open
发布2020-03-19 17:07:37
6430
发布2020-03-19 17:07:37
举报

一、概述

我们在进行Java开发的时候,很少关心Java的内存分配等等,因为这些活都让JVM给我们做了。不仅自动给我们分配内存,还有自动的回收无需再占用的内存空间,以腾出内存供其他人使用。但是我们经常面临的一个问题就是内存泄漏,JVM无法完成回收工作,导致内存占用暴涨,最后可能让程序奔溃。本章主要了解下运行时数据区域分布情况以及溢出异常。

说明: 本系列多处摘抄《深入理解Java虚拟机》中内容,主要精简了本书的要点,并叙述自己对本书的理解。本人才疏学浅,文章中有不对的地方,还望批评指教。

二、运行时数据区域

1、程序计数器

  • 线程私有
  • 当前线程所执行的字节码的行号指示器
  • Java多线程是通过再一个内核中轮流执行实现的,计数器就保证了切换线程的时候可以回到原来正确的执行位置
  • 程序计数器必须每个线程单独一个,是线程私有的内存区域
  • 程序计数器是唯一一个JVM没有规范OutOfMemoryError的区域

2、Java虚拟机栈(java方法)

  • 线程私有
  • Java方法执行的内存模型,即方法执行时会创建一个栈帧,保存了需要的局部变量表、操作数栈、动态链接、方法出口等信息;
  • 线程请求的栈深度>JVM允许的深度时,报StackOverflowError;
  • 大多数的JVM可以动态扩展内存,如果无法申请到足够的内存时,报OutOfMemoryError;

3、本地方法栈(native方法)

  • 同Java虚拟机栈

4、Java堆

  • 线程共享
  • 唯一目的:存放对象实例
  • 分类:新生代、老生代,或者Eden空间、From Survior 空间、To Survivor空间
  • 分类目的:更好的回收和分配内存
  • 没有内存完成实例分配,或者不能再扩展,报OutOfMemoryError异常
  • 可以自己配置大小(-Xmx和-Xms)

5、方法区

  • 线程共享
  • 目的:存储类信息、常量、静态变量、即时编译器编译后的代码等数据;
  • 该区内存回收目标:主要针对常量池的回收和对类型的卸载;
  • 无法满足内存分配要求时,报OutOfMemoryError异常

6、运行时常量池

  • 注意:运行时常量池属于方法区
  • 目的:存储编译期生成的各种字面量和符号引用
  • 特征:并非只有编译期置入Class文件中的常量池内容才能进入运行时常量池,在运行期间也可以置入新的常量,比如String的intern()方法;
  • 无法申请足够内存时,报OutOfMemoryError异常

三、 直接内存

  • 非运行时数据区域内存
  • Native函数分配堆外内存,堆内的DirectByteBuffer作为这块内存的引用
  • 性能显著提高,避免了Java堆和native之间来回复制数据

四、 对象

1、New对象过程

  • new指令发出
  • 检查new的参数是否在常量池中存在这个Class的符号引用
  • 检查对应的Class是否已经初始化
    • 若没有则先执行初始化过程
  • 分配内存,检查堆是否规整(垃圾收集器是否带有压缩整理功能决定)
    • 规整:指针碰撞方式分配内存
    • 不规整:空闲列表方式分配内存
  • 内存空间初始化为零值(不包括对象头)
  • 对对象进行重要的配置
  • 执行 < init > 方法

2、对象的内存布局

对象头(Mark Word)

  • 自身运行时数据
    • GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID
    • 类型指针:确定对象是哪个Class的实例

实例数据

  • 存储有效信息,定义的各种字段
  • 相同宽度的字段总是被分配到一起

对齐填充

  • 不一定存在
  • 实例数据没有对齐,需要填充

3、对象的访问定位

  • 句柄(reference):
    • 堆中划分句柄池
    • 句柄地址
      • 到对象实例数据的地址
      • 到对象类型的地址
    • 优势:稳定,对象移动时,(如GC时会移动),这个时候只改变指针地址。句柄信息不变,相对稳定;
  • 指针:
    • 直接存储了上述的对象地址
    • 优势:速度快

五、OOM

  • 堆溢出:举例一直new新的实例对象
  • 栈溢出:举例无限循环调用执行某个方法
  • 方法区和运行时常量池溢出:
    • String.intern():如果常量池已存在,则返回String对象,如果不存在,则先添加到常量池,再返回String对象。
    • 动态定义大量的Class,需要注意内存的回收情况。

六、小结

程序运行时,需要不停的将数据在内存中分配、计算等。JVM将不同类型的数据放在不同的位置,这样分工才能够让程序有序的跑起来。我们所定义的方法,以及new的对象实例都分别存在方法区和堆中,这两个区域是属于内存共享的地方,也就是说任何线程取的都是一样的。但是,因为有线程的存在。所以,我们需要给线程必要的私有空间。故,在程序运行的时候,我们通过栈来保存该线程自由的局部变量、引用等,通过程序计数器保存了各个线程的执行位置。这样,在线程切换的时候,才能找到自己的上一次执行位置,继续完成未完成的工作。如果,程序执行过程中没有足够的空间分配,就报对应的OOM异常。

小贴士

本文由原作者井方哥独家授权Open软件开发小组发布,著作权归原作者所有。如需转载请联系原作者申请授权。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-10-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Open软件开发小组 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、概述
  • 二、运行时数据区域
  • 1、程序计数器
  • 2、Java虚拟机栈(java方法)
  • 3、本地方法栈(native方法)
  • 4、Java堆
  • 5、方法区
  • 6、运行时常量池
  • 三、 直接内存
  • 四、 对象
  • 1、New对象过程
  • 2、对象的内存布局
  • 对象头(Mark Word)
  • 实例数据
  • 对齐填充
  • 3、对象的访问定位
  • 五、OOM
  • 六、小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档