运行时数据区域
<font color="#c41230">程序计数器PCR</font>
1.较小的内存
<font color="#c41230">2.当前线程执行的字节码行号指示器</font>
3.字节码指示器:通过改变这个计数器的值来选取下一条需要执行的指令
<font color="#c41230">4.线程私有</font>
5.如果执行的是java方法,则记录的是字节码指令的地址,如果是Native方法,计数器值为空
<font color="#c41230">6.不会内存溢出</font>
java虚拟机栈
<font color="#c41230">1.线程私有</font>
<font color="#c41230">2.生命周期和线程相同</font>
<font color="#c41230">3.每个方法创建</font><font color="#0076b3">栈帧</font>
<font color="#c41230">1.局部变量表</font>
A.存放编译器可知的各种<font color="#c41230">基本数据类型</font>,<font color="#c41230">对象引用</font>
B.64位的long,dubbo占用两个Slot
C.所需的内存在编译器就确定
<font color="#c41230">2.操作数栈(参数传递池)</font>
算术运算的时候是通过操作数栈来进行的
<font color="#c41230">调用其他方法</font><font color="#0076b3">的时候是通过操作数栈来</font><font color="#c41230">进行参数传递</font><font color="#0076b3">的</font>
<font color="#c41230">3.动态链接(运行中方法的符号引用的实际指向地址)</font>
保存当前运行方法<font color="#c41230">符号引用的实际指向地址</font>, 指向常量池的类方法地址
解析
1. Class文件在编译过程中,一切方法调用在Class文件里面存储的都只是符号引用
2. 在class文件的加载过程中, 会把符号引用转换为直接引用(方法在实际运行时内存布局中的入口地址)
<font color="#0076b3">以上为解析调用的过程, 是静态的, 即在类加载时便清楚对象调用方法的实际指向地址</font>
分派
1. 静态分派--场景:重载--编译过程中根据方法的参数类型确定使用哪个方法的符号引用
2. 动态分派--场景:重写--运行过程中根据对象的实际类型选择使用父类/子类中定义的方法的类方法 <br>
<font color="#c41230">4.返回地址</font>
5.异常情况
A:线程请求的栈深度大于虚拟机允许的深度
B:栈拓展内存不够用,内存溢出
本地方法栈
1.线程私有
2.执行的是native方法
<font color="#c41230">3.异常情况和虚拟机栈相同</font>
Java 堆
1.jvm中内存最大的一块
<font color="#c41230">2.线程共享</font>
<font color="#c41230">3.存放对象实例</font>
<font color="#c41230">4.垃圾回收的主要区域</font>
<font color="#c41230">5.新生代</font>
1.Eden
2.From Survivor
3.To Survivor
<font color="#c41230">6.老年代</font>
<font color="#c41230">7.多个线程私有的分配缓存区TLAB</font>
<font color="#c41230">8.物理上不连续的内存空间</font>
9.异常:内存溢出
(永久代)方法区
1.线程共享
<font color="#c41230">2.存储虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码</font>
<font color="#c41230">3.运行时常量池(存储编译器生成的字面量和符号引用)</font>
直接内存
<font color="#c41230">NIO中使用这块内存居多</font>(例如: netty零拷贝)
非堆内存受总内存大小限制
对象的创建
1.new 指令创建对象
A:检查这个指令的参数能否在<font color="#c41230">常量池中找到类的符号引用</font>
B:检查这个符号引用是否已经被<font color="#c41230">加载,解析,初始化</font>过
C:没有的话先进行类加载过程
<font color="#c41230">D:类加载完成后,分配内存</font>
2.分配内存方式
<font color="#c41230">A:指针碰撞</font>
假设内存规整,指针一边是空闲空间一边是被分配空间,通过指针移动表示使用了多少内存
<font color="#c41230">B:空闲列表</font>
C:两种方式取决于垃圾收集器类型,带有压缩整理的垃圾收集器可以使用指针碰撞
<font color="#c41230">D:并发情况下线程不安全</font>
<font color="#c41230">同步</font>
<font color="#c41230">按照线程划分在不同的空间TLAB</font>
E:内存分配好的话将内存空间初始化为0,然后将hashCode,分代年龄等信息保存在对象头
3.对象的内存布局
<font color="#c41230">A:对象头</font>
第一部分存储<font color="#c41230">hashCode,分代年龄,锁信息</font>等数据(MarkWord)
<font color="#c41230">第二部分类型指针(通过这个指针确定是哪个类的实例)</font>
数组的话还存储数据的长度
<font color="#c41230">B:实例数据</font>
类的字段信息,包括父类的字段信息等
存储顺序受分配策略的影响:相同宽度的字段总是被分配到一起
父类中的字段信息在子类之前
<font color="#c41230">C:对齐填充</font>
虚拟机要求对象大小是8字节的整数倍
如果对象大小不是8字节的整数倍的话,需要通过占位符补全
4.对象的访问定位
<font color="#c41230">句柄访问</font> 详看对象访问句柄图 优点:对象移动reference不用修改,句柄池更新即可
<font color="#c41230">直接指针访问</font> 详细看对象指针图 优点:速度块
假设内粗不规整,占用内存后的对象会记录在一张列表上
内存分配策略
年轻代
<font color="#c41230">对象主要分配在新生代的Eden区域</font>
<font color="rgba(0, 0, 0, 0)">启动本地线程分配缓存的话,则优先在TLAB上分配</font>
优先将对象分配在新生代上, <font color="#c41230">Eden区域内存不够时发送Minor GC</font>
老年代
<font color="#c41230">大对象直接进入老年代</font>
可以配置参数指定大于多大对象直接进入老年代,防止年轻代内存复制频繁
<font color="#c41230">长期存活的对象进入老年代</font>
1.虚拟机给对象添加年龄计数器
2.动态年龄判断
3.空间分配担保
1.Minor GC之前, <font color="#c41230">JVM会判断老年代最大可用的内存是否大于年轻代所有对象内存总和</font>,条件成立那么认为是安全的
2.如果不成立,尝试进行一次Minor GC,这个是有风险的,因为老年代剩余的内存可能不够
3.老年代会判断<font color="#c41230">历史晋升到老年代对象的平均值是否大于老年代剩余的内存</font>, 大于则需要<font color="#c41230">FULL GC</font> 来腾出更多的内存
常用的工具
jps 显示指定系统内所有的虚拟机进程
jstat 监视虚拟机各种运行状态的信息
jinfo 实时查看,调整虚拟机参数
jmap 生成堆转储快照
jhat 虚拟机堆转储快照分析工具
jstack 生成虚拟机当前时刻的线程快照