最近在看深入Java虚拟机,因为这本书理论为主,我之前看过前几章,没多久还是全忘了,为此这遍我想做点笔记,哪怕又忘了,也可以快速拾起来。
Java内存区域和内存溢出异常
内存区域介绍
首先,来看看内存区域。java运行时的内存区域主要分为:程序计数器,堆区,栈区,方法区。
- 程序计数器
主要保存字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能。 - 栈区
主要保存的是基本数据类型,以及对象的引用。在这块内存区域会抛出两种异常:StackOverFlowError 这表示栈的深度不够;OutOfMemoryError 这表示无法申请到足够的内存空间了 - 堆区
这块区域是Java虚拟机管理的最大的一块区域,这块区域也是所有线程共享的区域,这块区域主要保存的Java对象,当这块内存区域不够的时候也会抛出OutOfMemoryError。这块区域的详细情况我们下面在介绍。 - 方法区(永久代)
这块区域主要保存的是常量,静态变量,类信息等,这块区域也是共享的,当这块区域内存不足的时候也会抛出OutOfMemoryError。
对象的创建
我们来分析一下Java中new一个对象的过程到底什么样的呢?
首先,这条指令会去做一个check,检查一个常量池是否可以定位到一个类的符号的引用;接下来就是要在对内存创建一块内存空间了,分配内存空间有两种方式: 指针碰撞方式和空闲列表。
- 指针碰撞
首先,假设Java堆内存是绝对的规整,使用过的内存放一边,未使用过的内存放另外一边,中间放着一个指针作为分界点指示器。 - 空闲列表
如果内存不是规整的就没办法进行指针碰撞,虚拟机就必须维护一个空闲列表,那些是可用的那些是不可用的是通过这个空闲列表来记录的,因此目前大多数都使用空闲列表。如何划分内存空间也是一个很大的问题,因为这块区域是共享的,对象的分配是非常频繁的,在并发的情况下线程又不是安全的,如何保证内存分配不出问题呢?第一种:可以使用cas的方式,保证原子性;第二种:可以使用本地线程分配缓冲器(TLAB),也就是说每个线程在Java堆区域里预先分配一块内存,只有这块预先分配的内存使用完了才会使用同步操作。
对象的内存布局
对象在内存布局可以分为三块:对象头,实例数据,对象填充。
- 对象头
第一部分数据为:哈希码,GC分代信息,线程所持有的锁,偏向线程ID,偏向时间戳。
第二部分数据为: 类型指针,对象指向它的类元数据的指针。 - 实例数据
对象存储的真正有效信息。 - 对象填充
没有什么含义,起到站位的作用。
对象的访问
对象的访问主流的方式是使用句柄和直接指针
- 句柄
Java堆中将划分出一块存储句柄,访问的时候先从Java栈到句柄池再到具体的对象。 - 直接指针
栈中保存的就是对象的引用,可以通过引用直接访问到具体的对象。
这两种方式比较的话:直接指针更有优势,因为它会少移动一次指针。
网友评论