对象的创建
指针碰撞 : 假设 java 堆中的内存是绝对规整的, 所有被使用过的内存都放到一边, 空闲的内存放在另一边 , 中间放着一个指针作为分界点的指示器 . 那所分配内存就是仅仅把那个指针向空闲空间方向挪动一段与对象大小相等的距离 . 这种分配方法成为”指针碰撞” .
空闲列表 : 如果内存并不是规整的, 已被使用的和空闲的内存交互在一起, 没有办法进行简单的指针碰撞 . 虚拟机就维护一个列表, 记录哪些内存块是可用的, 在分配的时候从列表中找到一块足够大的内存块划分给实例对象, 并更新列表上的记录 . 这种分配方式叫做 “空闲列表”
选用哪种分配方式由 Java 堆是否完整决定, 而 Java 堆是否完整又取决于所采用的垃圾收集器是否带有空间压缩的能力来决定的 . Serial . ParNew 等带压缩整理过程的收集器就使用 指针碰撞 . 基于 CMS 这种清除算法就使用 空闲列表
并发情况下不是线程安全的 :
CAS + 失败重试 保证原子操作
本地线程分配缓冲 ( TLAB ) : 把内存分配的动作按照线程划分在不同的空间之中进行, 即每个 线程在 Java 堆中预先分配一小块内存 ,成为本地线程分配缓冲 ,哪个 线程要分配内存, 就在那个线程的本地缓冲区中分配 .
对象的内存布局
在Hotspot虚拟机里, 对象在堆内存中的存储布局可以分为三个部分 : 对象头 实例数据 对齐填充
对象头包含两部分
Mark Word
哈希码 (HashCode)
GC分代年龄
锁相关信息 ( 锁状态标志 线程持有的锁 偏向线程 ID )
类型指针 : 类型元数据的指针, 来确定它是哪个类的实例
如果对象是数组, 对象头中还有记录数组长度的数据
实例数据即我们代码中定义的各种类型字段的内容
对象的访问定位
创建对象自然是为了后续使用该对象, Java 程序会通过栈上的 reference 数据来操作栈上的具体对象 .
主流的访问方式主要有 句柄 和 直接指针
句柄 : java 堆中将分出一块内存作为句柄池 , reference 中存储的就是对象的句柄地址, 而句柄中又包含了对象实例数据和对象类型数据各自的地址信息 .
直接指针 : reference 中存储的就是对象地址,
使用句柄来访问的最大好处就是 reference 中存储的是 稳定的句柄地址 , 在对象被移动 (垃圾收集时移动对象是非常普遍的) 时只需要改变 句柄 中的实例数据指针, 而reference 本身不需要修改 .
使用直接指针的好处就是速度更快, 他节省了一次指针定位的时间开销 (HotSpot虚拟机主要采取的是这种对象访问定位)
内存分配策略
Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
堆空间的基本结构:
Eden 区、From Survivor0(“From”) 区、To Survivor1(“To”) 区都属于新生代,Old Memory 区属于老年代。
大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。
大对象直接进入老年代
大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。
为什么要这样呢? 为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。
针对 HotSpot VM 的实现,它里面的 GC 其实准确分类只有两大种:
部分收集 (Partial GC):
新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集;
老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集;
混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。
整堆收集 (Full GC):收集整个 Java 堆和方法区。
对象已死 ?
引用计数算法 : 在对象中添加一个引用计数器, 每当有一个地方引用它, 计数器加一,当引用失效时, 计算器就减一. 任何时刻计算器为零的对象都不可以再被使用 . (无法解决循环引用)
可达性分析算法: 如果某个对象到 GCRoots 没有任何引用链相连, 或者用图论的话是 从 GCRoots到这个对象不可达, 则证明这个对象是不可能在被使用的 .
引用计数法
给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。
这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。