一、对于JVM的内存区域
- 简单来说 JVM的内存区域分为五个区
- 根据特点可以分为三个:
- 线程私有:程序计数器、虚拟机栈、本地方法栈
- 线程共享:Java堆
- 线程共享:方法区(JDK8以前:永久代;JDK8以后:元空间)

一、程序计数器
它存的是当前线程正在执行的字节码指令的地址 为什么需要它:CPU是时间片轮转调度的,一个线程随时可能因为CPU耗尽被切换出去,当该线程被切换回来时,怎么知道它运行到那一步了呢?这就需要程序计数器,它相当于线程运行时的一个"书签" 特点: 线程私有:好类比与每个线程有自己的书签,互不干扰 唯一不会发生OOM的区域:《JAVA虚拟机规范》里唯一没有规定任何OOM情况的区域。原因很简单------它只存一个固定大小的地址,不需要扩容,自然不会溢出。 一个细节:如果线程执行的是Java方法,计数器记录字节码地址。如果执行的是native方法,计数器值为空------因为native方法的执行不归JVM的字节码解释器管
二、Java虚拟机栈
它描述的是Java方法执行时的内存模型。每个方法被调用时,JVM就创建一个栈帧(Stack Frame)压入栈中;方法执行完,栈帧弹出。一个方法从调用到返回,就对应一个栈帧的入栈到出栈
栈帧由四个部分组成:
- 局部变量表:方法内的局部变量:基本类型存值本身,对象存引用地址
- 操作数栈:方法执行过程中的临时计算数据
- 动态链接:指向运行时常量池中该方法的引用,支持多态
- 方法返回地址:方法执行完后该回到哪里
三、本地方法栈
和虚拟机栈结构一样,只不过描述的是本地native的方法调用到结束的内存模型。HotSpot直接把两者合并实现,所以规范层面的两个区域在主流JVM里其实是同一块
四、Java堆
Java堆存储几乎所有的对象实例和数组。"几乎"是因为JIT编译器的逃逸分析可能让对象在栈上分配,但这时优化手段,语义上对象仍属于堆。
五、方法区
方法区存的是每个类的元数据(类名、父类、接口、字段方法描述)、运行时常量池、JIT编译后的代码缓存。需要注意的是它管的是"类"的信息而不是对象的信息------类是图纸,堆里的对象是依靠类造出的房子
二、对象分配 新生代 老生代
一、对象从出生到死亡,在堆里是怎么流转的

一、对象分配的四条路:越快越靠前
设计理念:能不仅共享区就不进,能不加锁就不加。分配是Java里最高频的操作,每一步优化都直接决定吞吐量。
- 栈上分配: 让对象"根本不进堆"
JIT编译做逃逸分析:如果分析出当前对象只在方法内部调用、不会逃出方法(没有被return,以及没有在外部引用),就不必在堆上分配------直接在栈帧里以"标量替换的方式存在"(把对象拆解成基本类型字段,直接放局部变量表)。方法返回,栈帧弹出,对象自动消失,零GC压力。 注意:栈上分配是JIT优化,代码写法不变。这也是为什么说"所有对象都在堆里"严格讲是不成立的。
- TLAB:堆里的私人车道
绝大多数对象逃逸出了方法,所以它就不能用JIT的栈帧优化,会进入堆内存。但是堆是共享的------两个线程同时new,在Eden区抢同一块内存,必须加锁同步,高频分配下开销巨大。 JVM的解法就是用TLAB(Thread Local Allocation Buffer):每个线程在Eden里预先圈一小块私有地,对象优先在自己地盘上分配,无锁,指针碰撞,几条指令搞定。TLAB用完了,才去共享的Eden区CAS分配(重试到成功)
- Eden区:新生的主战场
TLAB装不下的大对象,或TLAB耗尽后的分配,落在Eden区。Eden占新生代的8/10。 Eden满了 -> 触发MinorGC------新生代唯一的GC触发方式,不是定时任务,是分配失败驱动的
二、新生代:两个Survivor的设计智慧
复制算法的运作:Minor GC时,Eden里绝大部分对象已经死了。把Eden + From Survivor中存活的对象,一次性复制到To Survivor, 并且年龄+1, 然后整块清空Eden 和 From。
为什么必须两个Survivor?
- 复制算法需要"来源区"和"去往区"物理隔离 ------ 一个survivor在复制中同时充当来源和目标,内存位置会重叠
- 单SUrvivor会导致下一次Minor GC时 本轮存活对象无处安放 两个Survivor互为镜像、角色互换:这轮时From,下轮就是To。这就是空间换正确性的经典取舍
什么时候对象离开新生代:
- 年龄达标:每熬过一次Minor GC,年龄就会增大一岁,超过15岁时就会离开新生区,这个是因为对象头里标记GC年龄的字段只有4bit,最大的就是15。
- Survivor中同龄及以下年龄的对象总大小超过Survivor一半,这批对象整体晋升,这是为了防止Survivor被挤爆
- 大对象直接进:超大对象(如大数组)绕过新生代直接进入老生代
- Survivor装不下:Minor GC后存活对象太多,To Survivor塞不下,通过空间分配担保直接进老年代
三、老年代
存什么:
- 晋升的长寿对象
- 大对象直接分配
- 字符串常量池