目录
-
线程程序计数器
-
线程栈
-
栈帧
-
堆
-
方法区
-
对象创建流程
-
类加载检查
-
分配内存
-
初始化
-
设置对象头
-
执行`
`方法 -
内存分配流程
-
对象栈中分配
-
大对象直接进入老年代
-
长期存活的对象进入老年代
-
对象动态年龄判断
-
老年代空间分配担保机制
运行时数据区内存模型
参考官方文档,运行时数据区主要由以下部分组成:
-
线程程序计数器(The pc Register) -
线程栈(Stacks) -
堆(Heap) -
方法区(Method Area) -
运行时常量池(Run-Time Constant Pool) -
本地方法栈(Native Method Stacks)
图示:

线程程序计数器
每一个Java线程都有自己的pc寄存器,如果不是native
方法,则存储的值为JVM正在执行的字节码指令的地址,如果方法是native
方法,则存储的值undefined
。
线程栈
JVM每个线程会分配一块内存区域,这块内存区域一般称为栈,不同线程不能共享栈内存空间,栈中存储了栈帧,栈帧随着方法的调用创建,随着方法调用结束而销毁。
栈帧
栈帧的内存空间是创建它的线程分配的,用于存储方法数据和方法过程的数据结构,栈中只能存储基本数据类型和引用类型数据。
栈帧由以下几个部分组成:
-
局部变量表,方法的局部变量存储,通过索引访问。JVM使用局部变量来完成访问调用的参数传递,当调用非静态方法时,索引0位置局部变量存储的是该对象实例的引用(即 this
,这也是为什么静态方法中不能使用this
的原因)。 -
操作数栈,操作数栈可以类比计算机组成原理中的寄存器,JVM通过指令从局部变量表中加载常量、变量到操作数栈中,通过入栈、出栈操作通过指令进行运算,再把结果入栈。 -
动态链接,一个方法如果调用另外一个方法、访问变量,需要通过符号引用表示,动态链接即把符号引用转换为直接引用。 -
方法出口地址,当方法返回时,会恢复调用方法的局部变量表和操作数栈,把返回值压入调用者栈帧的操作数栈,调整PC寄存器为执行方法调用后的指令地址。
堆
JVM中,堆是各个线程共享的运行时内存区域,也是类和数组分配内存的区域,这些内存区域由GC管理,决定何时销毁。
方法区
方法区也是各个线程共享的内存区域,存储了类的信息,包括运行时常量池、方法的字节码内容等。
对象内存分配
对象创建流程
对象创建流程大致为类加载检查–>分配内存–>初始化零值–>设置对象头–>执行<init>
方法。
图示:

类加载检查
JVM创建对象前,会检查是否能定位到类的符号引用,并且检查符号引用的类是否已经被加载,如果没有,则先加载类
分配内存
划分内存给对象进行存储。
划分内存有两种方式:
-
指针碰撞(默认使用),内存需要规整,即使用中的内存为一边,空虚内存为一边,分界线指针作为分界点,当需要分配时,向后挪动指针对象大小即可。 -
空闲列表方式,内存不规整,JVM维护一个列表,用来记录空虚内存块,在分配时,找到一块足够大的内存用于分配。
这两种方法都存在并发问题,解决办法:
-
CAS方式 -
本地线程分配缓冲(Thread Local Allocation Buffer, TLAB),每个线程会在堆中预先分配一块内存,JVM默认开启 -XX:+UseTLAB
初始化
内存分配完成后,将分配的内存空间初始化为零值,这一步是为了保证在Java代码中可以不赋初始化就可以直接使用。
设置对象头
初始化零值后,需要设置对象头信息,比如元数据信息、哈希码、GC分代年龄、锁状态标志、线程持有的锁指针、类型指针(指向方法区类元数据的指针,通过这个指针来确定对象属于哪个类的实例)。
对象头很复杂,暂时不做过多深入,源码注释可以在markOop.hpp
文件中找到:
#include "oops/oop.hpp"
// The markOop describes the header of an object.
//
// Note that the mark is not a real oop but just a word.
// It is placed in the oop hierarchy for historical reasons.
//
// Bit-format of an object header (most significant first, big endian layout below):
//
// 32 bits:
// --------
// hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
// size:32 ------------------------------------------>| (CMS free block)
// PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
// 64 bits:
// --------
// unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)
// PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
// size:64 ----------------------------------------------------->| (CMS free block)
//
// unused:25 hash:31 -->| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object)
// JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object)
// narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
// unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
//
// - hash contains the identity hash value: largest value is
// 31 bits, see os::random(). Also, 64-bit vm's require
// a hash value no bigger than 32 bits because they will not
// properly generate a mask larger than that: see library_call.cpp
// and c1_CodePatterns_sparc.cpp.
//
// - the biased lock pattern is used to bias a lock toward a given
// thread. When this pattern is set in the low three bits, the lock
// is either biased toward a given thread or "anonymously" biased,
// indicating that it is possible for it to be biased. When the
// lock is biased toward a given thread, locking and unlocking can
// be performed by that thread without using atomic operations.
// When a lock's bias is revoked, it reverts back to the normal
// locking scheme described below.
//
// Note that we are overloading the meaning of the "unlocked" state
// of the header. Because we steal a bit from the age we can
// guarantee that the bias pattern will never be seen for a truly
// unlocked object.
//
// Note also that the biased state contains the age bits normally
// contained in the object header. Large increases in scavenge
// times were seen when these bits were absent and an arbitrary age
// assigned to all biased objects, because they tended to consume a
// significant fraction of the eden semispaces and were not
// promoted promptly, causing an increase in the amount of copying
// performed. The runtime system aligns all JavaThread* pointers to
// a very large value (currently 128 bytes (32bVM) or 256 bytes (64bVM))
// to make room for the age bits & the epoch bits (used in support of
// biased locking), and for the CMS "freeness" bit in the 64bVM (+COOPs).
//
// [JavaThread* | epoch | age | 1 | 01] lock is biased toward given thread
// [0 | epoch | age | 1 | 01] lock is anonymously biased
//
// - the two lock bits are used to describe three states: locked/unlocked and monitor.
//
// [ptr | 00] locked ptr points to real header on stack
// [header | 0 | 01] unlocked regular object header
// [ptr | 10] monitor inflated lock (header is wapped out)
// [ptr | 11] marked used by markSweep to mark an object
// not valid at any other time
//
// We assume that stack/thread pointers have the lowest two bits cleared.
32位对象头结构:
|-------------------------------------------------------|--------------------|
| Mark Word (32 bits) | State |
|-------------------------------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 | Normal |
|-------------------------------------------------------|--------------------|
| thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 | Biased |
|-------------------------------------------------------|--------------------|
| ptr_to_lock_record:30 | lock:2 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:30 | lock:2 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
| | lock:2 | Marked for GC |
|-------------------------------------------------------|--------------------|
64位对象头结构:
|------------------------------------------------------------------------------|--------------------|
| Mark Word (64 bits) | State |
|------------------------------------------------------------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 | Normal |
|------------------------------------------------------------------------------|--------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2 | Biased |
|------------------------------------------------------------------------------|--------------------|
| ptr_to_lock_record:62 | lock:2 | Lightweight Locked |
|------------------------------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:62 | lock:2 | Heavyweight Locked |
|------------------------------------------------------------------------------|--------------------|
| | lock:2 | Marked for GC |
|------------------------------------------------------------------------------|--------------------|
执行<init>
方法
属性赋值和执行构造方法。
内存分配流程
流程图:

对象栈中分配
为了提升性能,通过逃逸分析确定对象不会被外部访问,则将该对象分配在栈上,对象内存空间随着栈帧出栈而销毁。
JVM开启逃逸分析-XX:+DoEscapteAnalysis
,通过标量替换分配在栈上。Java的基本数据类型和引用类型就是标量,Java对象可以通过分解为标量,使其可以分配在栈内存中。
大对象直接进入老年代
通过设置JVM参数-XX:PretenureSizeThreshold
设置大对象的大小,当对象大小超过这个值是,直接进入老年代,需要注意的是,这个参数只有在Serial
和ParNew
收集器下有效。
目的:避免大对象分配内存和GC时因为复制操作而降低效率。
长期存活的对象进入老年代
每个对象都会有一个对象年龄,存储在对象头中,当对象在Eden区域经历第一次Young GC后依然存活,并且能被Survivor容纳,则将被移动到Survivor区域,并将对象年龄设为1,之后每经过一次Young GC,年龄就增加1,直到增加到-XX:MaxTenuringThreshold
值(默认15,CMS收集器默认6,不同收集器可能不同)后,晋升到老年代。
对象动态年龄判断
当Young GC后,如果当前Survivor区域中一批对象总大小超过了Survivor区域内存的50%(-XX:TargetSurvivorRatio
),那么大于等于这批对象年龄最大值的对象,晋升到老年代。
目的:让长期存活的对象,尽早进入老年代
老年代空间分配担保机制
Young GC之前JVM会计算老年代剩余可用空间,如果可用空间不足以容纳年轻代所有对象,会查看是否配置-XX:-HandlePromotionFailure
(1.8默认配置),如果有这个参数,会比较老年代剩余可用空间是否大于之前每一次Young GC后进入老年代的对象评价大小,如果小于或者没有这个参数,触发Full GC,Full GC后如果还是没有足够空间触发OOM
。
Young GC之后如果老年代无法容纳存活对象也会触发Full GC,Full GC之后如果还是空间不足,则也会发生OOM
。
参考文档
-
Oracle 官方文档 -
Java对象头分析及Synchronized锁
原文始发于微信公众号(erpang coding):JVM内存模型和对象内存分配机制
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/37334.html