JVM内存模型剖析

类加载子系统:编译后的字节码是在磁盘中,通过类加载子系统将磁盘中的字节码读取到内存中,比如放到方法区后,字节码指令由执行引擎中的解释器,执行方法区中的字节码。

JIT解释器:热点字节码指令需要经常执行,我们就可以把该字节码指令缓存起来,通过JIT编译器将热点的字节码指令,提高执行效率。

线程共享区:

堆:JVM中规范所有的对象和数组都应该存放在堆中,在执行字节码指令时,会把创建的对象存入堆中,对象对应的应用地址存入虚拟机栈中的栈帧,方法执行完后,创建的对象不会立刻被回收,而是要等JVM后台执行GC,对象才会被回收。

方法区(概念):存放线程执行的方法,JDK8采用永久代实现,归属于堆的一部分,JDK8后迁移出堆归属于系统内存改元空间实现。

线程私有区:

虚拟机栈:每个线程运行所需要的内存,每个栈由多个栈帧(Frame)组成,对应着每次方法的调用,每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。

本地方法栈:native修饰的,JVM中使用C/C++写的一些方法。

程序计数器:线程中发生的切换时保存下一次需要执行的指令位置。即下一条预执行指令的地址。

栈帧:局部变量表+方法出口+操作数栈+动态链接

操作数栈:执行字节码期间进行变量运算和操作

局部变量表:存储方法参数和局部变量

动态链接:将符号引用转换为直接引用

方法出口:保存方法返回后需要继续执行的地址。

步骤:

1.加载:找到class文件,将磁盘上的class文件加载到内存中

2.验证:校验class文件格式问题

3.准备:为常量静态变量分配内存设置零值

4.解析:解析符号引用(System、String),将全限定类名解析为实际的类

5.初始化:执行静态代码块,显示赋值静态变量

6.执行main方法:创建对象,调用方法

双亲委派流程:

public class ParentDelegation {

// 加载类的流程:

// 1. 当前类加载器检查是否已加载

// 2. 未加载则委托给父类加载器

// 3. 父类加载器重复此过程,直到启动类加载器

// 4. 父类加载器无法加载时,才由子类加载器尝试加载

}

细说运行时数据区:

栈溢出:

1. 不断产生栈帧。 递归调用

2. 栈帧过大。 这种情况很少。

线程诊断: 1. cpu 占用过高 linux 中用 top 定位哪个进程 cpu 占用过高,再用 ps H -eo pid tid %cpu | grep 进程 id 进一步定位 jstack 线程 id id 转成 16 进制排查

2. 线程长时间运行无结果 死锁, jstack 线程 id 同上排查。

堆溢出:不断创建对象,但是又没有当成垃圾回收。 如不断往集合中添加对象。可以通过 Xmx 设置大小。

诊断工具:

1.jps 工具:查看当前系统有哪些 java 进程 2.jmap 工具:查看堆内存占用情况 jmap -heap 进程 ID 3.jconsole 工具:图形界面监测工具 4.jvirsualvm 图形化诊断页面

新生代:存放新创建出来的对象。老年代:存放多次 GC 后仍存活的对象。

新创建出来的对象存放在新生代的Eden区,当Eden区快满时触发MinorGC(新生代垃圾回收)通过垃圾回收算法回收掉标记的垃圾对象空闲出空间,再把存活下来的对象迁移到S0区并标记分代年龄,随着迁移S0区的对象变多,也会满,就会在S0区也触发MinorGC将存活的对象迁移到S1区,就这样,存活的对象在S0和S1区经过MinorGC来回迁移,到达分代年龄的阈值(默认15)后就会迁移到老年代中。

对于新创建出来的对象如果S0,S1区存放不了,可能在Eden区经历一次YGC直接存放到老年代中,对于Eden区也存放不了的则直接存入老年代中。

方法区:

JDK1.7 前采用永久代实现,位于堆内, JDK8 后采用元空间实现,位于堆外(本地内存)。

为什么要改变实现方式?

永久代:

1.受到JVM堆大小限制 2.垃圾回收效率低,基本FullGC和老年代一起回收。3.调优需要平衡堆和永久代大小。

**元空间:**1.性能提升,减少FullGC次数。2.效果回收垃圾,与GC堆分离,有专门的类加载回收。

这种变化是为了解决永久代内存限制和性能问题。

运行时常量池: 位于元空间中,存储符号引用和直接引用,各种常量,类和接口的符号引用,字段和方法的符号引用,属于编译期。

字符串常量池: 位于堆中,缓存字符串字面量,实现字符串复用机制,属于运行期。

JDK1.6前,运行时常量池包含了字符串常量池,JDK8后,运行时常量池迁移到堆中,而运行时常量池位于本地内存。两者是引用关系,当常量池中需要用到串池中的字面量时通过引用找到串池检查是否存在,存在复用,不存在新建。

字符串常量池特性 (StringTable):

1.拼接原理是StringBuilder(jdk8)

2.字面量自动入池,动态字符串手动调用intern

3.复用机制,减少内存占用

4.固定大小的哈希表,解决冲突哟个链表

5.JDK7+位于堆中,受GC管理。

JDK6时,StringTable是常量池的一部分,存储在永久代中,JDK7后转移到堆中,因为永久代内存回收率低。

相关推荐
简色3 小时前
预约优化方案全链路优化实践
java·spring boot·后端·mysql·spring·rabbitmq
nice_lcj5203 小时前
深入理解ArrayList与LinkedList:Java集合框架核心对比(含实战案例+面试考点)
java·面试
艾菜籽3 小时前
JVM中的内存区域划分
jvm
小蕾Java3 小时前
IDEA快速上手指南!
java·intellij-idea·swift
聪明的笨猪猪3 小时前
Java 内存模型(JMM)面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
羚羊角uou3 小时前
【Linux】线程的互斥
java·开发语言
学编程的小鬼3 小时前
SpringBoot日志
java·后端·springboot
来不及辣哎呀3 小时前
学习Java第三十天——黑马点评37~42
java·开发语言·学习
失散134 小时前
分布式专题——26 BIO、NIO编程与直接内存、零拷贝深入辨析
java·分布式·rpc·架构·nio·零拷贝