jvm思维脑图
2022-03-17 17:01:03 0 举报
AI智能生成
jvm的思维脑图,可以搭配jvm图示看更佳
作者其他创作
大纲/内容
对象分配
判断类是否被加载过,如果被加载过则直接开始分配内存<br>
分配流程
1、如果开启了-XX:+DoEscapeAnalysis逃逸分析,<br>非逃逸对象,会先尝试在栈类分配,如果空间允许,则直接栈类分配<br>
2、如果不满足,则会判断是否是大对象-XX:PretenureSizeThreshold,<br>如果超过阈值则直接会在老年代分配内存<br>
3、如果不是大对象,会尝试现在eden区线程私有的talb分配
4、如果tlab分配不了,则直接分配进eden区共享的内存空间中
内存分配方法<br>
指针碰撞
如果Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点<br>的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离
空闲列表<br>
如果Java堆中的内存并不是规整的,已使用的内存和空 闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟<br>机就必须维护一个列表,记 录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,<br>并更新列表上的记录
初始化<br>
虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头), 如果使用TLAB,这一工作过程也<br>可以提前至TLAB分配时进行,这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问<br>到这些字段的数据类型所对应的零值
设置对象头
对象头
类的元数据
对象的哈希码<br>
对象的gc年龄<br>
锁信息<br>
线程信息
类型指针<br>
,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
指针压缩
在64位平台的HotSpot中使用32位指针,内存使用会多出1.5倍左右,使用较大指针在主内存和缓存之间移动数据,占用较大宽带,同时GC也会承受较大压力
为了减少64位平台下内存的消耗,启用指针压缩功能,如果使用64位存地址,会导致浪费很多存储空间,应该2的64次方代表的内存空间太大,根本目前是用不到;
在jvm中,32位地址最大支持4G内存(2的32次方),可以通过对对象指针的压缩编码、解码方式进行优化,使得jvm只用32位地址就可以支持更大的内存配置(小于等于32G)
堆内存小于4G时,不需要启用指针压缩,jvm会直接去除高32位地址,即使用低虚拟地址空间
堆内存大于32G时,压缩指针会失效,会强制使用64位(即8字节)来对java对象寻址,这就会出现1的问题,所以堆内存不要大于32G为好<br>
执行init方法<br>
对普通变量赋值(程序员指定的值),执行构造方法
内存的回收
如何判断对象是否可以回收<br>
引用计数法
可达性算法<br>
如何判断一个类是无用的类<br>
该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
<article data-content="[{"type":"block","id":"Shuw-1647255306932","name":"paragraph","data":{"version":1,"style":{"textIndent":56}},"nodes":[{"type":"text","id":"5JZE-1647255306931","leaves":[{"text":"加载该类的 ClassLoader 已经被回收。tomcat采用该方式实现jsp的热部署","marks":[{"type":"backgroundColor","value":"#FFF2CC"}]}]}],"state":{}}]"><div yne-bulb-block="paragraph" style="white-space: pre-wrap; line-height: 1.75; font-size: 14px;">加载该类的 ClassLoader 已经被回收。tomcat采用该方式实现jsp的热部署<br></div></article>
该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
finalize<br>
gc回收垃圾的时候,执行finalize()方法,finalize()方法只会被执行一次,也就是说通过调用finalize方法自我救命的机会就一次。<br>
jvm
运行时数据区
堆
年轻代
eden
存放一些刚刚被创建的实例,如果设置
tlab
每个线程在Java堆中预先分配一小块内存,会在eden区中也分配一个私有空间tlab,<br>用于存储运行期间的一些实例对象,XX:+UseTLAB开启,XX:TLABSize大小<br>
survivor
from
gc后没有进入老年代的对象
to
gc后没有进入老年代的对象
老年代old
大对象直接进入老年代,VM参数 -XX:PretenureSizeThreshold(字节),<br>这个参数只在 Serial 和ParNew两个收集器下有效<br>
长期存活的对象进入老年代
对象动态年龄判断,from|to空间不足50%也会进入老年代<br>
字符串常量池
jdk1.8字符串常量池存在堆里面;一些class中的字符串常量,<br>会在加载类时丢进运行时常量池;会在执行的时候,会加载到字符串常量池中;<br>
栈
程序计数器
记录当前线程执行到哪一行
本地方法栈
登记native方法,在Execution Engine执行时加载本地方法库
栈帧
局部变量表
用于存储局部变量
动态链接
用于方法执行把方法的符号引用转成直接引用
操作数栈
用于变量的压栈和出栈,便于运算或变量的装载
方法出口
记录当前方法的出口
方法区(元空间)<br>
class
常量池
编译成class时,会把一些字面量和符号引用放进常量池中
字面量
文本字符串<br>
String str= "ad";
八种基本类型<br>
int
float
short
double
long
boolean
char
byte
被声明final的常量
符号引用
类与接口全限定名<br>
方法全限定名
字段名称及描述符
方法的名称及描述符<br>
接口或继承全限定名<br>
方法信息
字段信息<br>
类加载器的引用
对应类的实例引用
类型信息
运行时常量池
class中的常量池,在运行的时候会丢进运行时常量池中;
除此以外还会有一些运行期间的一些新的常量会丢进池中<br>
类装载子系统
class字节码文件结构<br>
魔数<br>
次版本号<br>
主版本号
常量池计数器<br>
常量池数据区
class的访问标识符<br>
this class name<br>
super class name<br>
<article data-content="[{"type":"block","id":"jXh6-1647434708471","name":"paragraph","data":{"version":1,"style":{"textIndent":28}},"nodes":[{"type":"text","id":"gduz-1647434708469","leaves":[{"text":"接口信息","marks":[{"type":"fontSize","value":14},{"type":"bold"}]}]}],"state":{}}]"><div yne-bulb-block="paragraph" style="white-space: pre-wrap; line-height: 1.75; font-size: 14px;"><span style="font-weight: bold;">接口信息</span></div></article>
字段表信息
方法表信息
文件属性
类加载过程<br>(当调用main方法和new的时候才会加载)<br>
加载<br>
IO流读取字节码class文件
验证
验证字节码文件中的语法格式是否正确
准备
给类的静态变量赋初始值(非指定值),int的默认0,引用的默认null
解析<br>
将静态方法的符号引用(常量池中的一条数据),修改成直接引用(内存中的指针或句柄);<br>因为静态方法在编译时就确定了由哪个类执行<br>
初始化
为静态变量赋指定值(程序员赋值);执行静态代码块;<br>
类加载器
启动类加载器
此类加载器是由C|C++创建的,用来加载jre lib包下的核心类库,比如 rt.jar、charsets.jar等,像string类<br>
拓展类加载器
此类是在JVM中创建的,并且把parent指向了启动类加载器;<br>用来加载jre lib包ext底下的一些拓展的类库,比如加密的一些jar<br>
应用程序加载器<br>
次类也是在jvm中创建的,并把parent指向了拓展类加载器;<br>用来加载classpath底下自己写的类<br>
自定义加载
可通过继承ClassLoader重写findclass去写自己加载类的逻辑;<br>当类加载器初始化的时候,会调用父类的构造方法,把parent指向getSystemClassLoader()应用程序加载器;<br>但是如果想要打破类的双亲委派需要重写loadClass,不去调用parent去加载;<br>
类的双亲委派
类的双亲委派,简单说就是一层一层判断当前类是否通过该类加载器加载过,<br>没有的话通过parent调用loadclass的方法去加载,如果最顶级的加载器没有,则会自己findclass尝试加载;<br>
防止Java自己的核心类不加载,被同样的命名类去加载了,导致不安全<br>避免类的重复加载<br>
防止已经加载过的类,重复加载
应用中很多的代码都是自己编写的,所以才需要一层层向上调用,因为大部分的类,<br>可能已经被应用程序加载器加载<br>
tomcat打破类加载<br>
为了每个war包有可能依赖不同版本的类库,但是jvm加载类的时候只会根据类的全限定名(com/lang/dddd)<br>
tomcat也有自己的类库,为了隔离开和应用的类库;<br>
jsp的热加载,jsp最终会成为一个个Selevt.class,如果想要改动jsp就能改动class。<br>除非类加载器被卸载,该类没有任何实例引用和反射才能被卸载后重新加载;<br>所以tomcat每个jsp都会创建一个类加载器,当jsp被修改的时候会把当前的类加载器=null,让当前的selevt被回收后重新加载;<br>
字节码执行引擎
字节码解释器
jit编译器
机械无关优化<br>
中间代码<br>
机械相关优化<br>
中间代码<br>
寄存器分配器
中间代码
目标代码生成器
目标代码
0 条评论
下一页