原图地址,关注公众号“奇闻技巧”,回复“JVM剖析图“ 获取
代码编译
原始文件通过JavaC编译,经历语法解析器,语意解析器等,变成class字节码文件。
类加载器
class文件加载过程
- 从磁盘中拿到文件流
- 将磁盘文件静态结构载入内存方法区,转换为运行时数据结构《类信息》
- 把载入后的类信息进行组装,在堆空间中生成类对象(class对象),做为数据入口。
- 从磁盘中拿到文件流
- 将磁盘文件静态结构载入内存方法区,转换为运行时数据结构《类信息》
- 把载入后的类信息进行组装,在堆空间中生成类对象(class对象),做为数据入口。
简单来说:
双亲委派机制:就是拿到字节码信息之后不会直接加载,会丢给父类去加载,直到没有符合加载的父类为止,这样的好处就是避免同一个文件出现重复加载的情况。
但是在Tomcat里面的加载机制是违背双亲委派原则的,因为有可能一个Tomcat容器里面要部署多个系统,这样不同系统里面会有同名的文件,就会出现加载混乱。
JIT及时编译
简单来说
HotSpot中的Java程序通常情况下都是通过解释器,一边解释,一边执行,并不是全部把Class字节码文件全部加载进去。也不是每次使用的时候才解释加载,里面有个热点代码检测机制,当部分代码被识别为热点代码就会提前加载变成机器码。
其实JIT编译只是一个统称,具体要看jvm是client端还是server端的,不同的端会分为C1,C2编译器
记得以前有个概率叫 codeCache 空间,不知道是不是放这玩意的。
简言之codeCache是存放JIT生成的机器码(native code)。当然JNI(java本地接口)的机器码也放在codeCache里,不过JIT编译生成的native code占主要部分。
都是属于非堆空间。
这里记住几个关键字
方法调用计数器:顾名思义就是记录调用次数。
回边计数器:统记方法体内的循环体。
而且:编译的最小单元是方法,运行前要先检查是否编译。
如果编译了就直接运行。
如果没有编译就需要先经过JIT的加载,提交OSR编译请求,执行编译。
记住几个关键点:
- 方法是编译的最小单元。
- JIT里面会做“热点测探”,通过技术器的方式记录方法的执行次数等。
- JIT内部会做“栈上替换”,通把原本的方法调用地址替换为编译后的方法地址。
Java对象创建的过程
这里先要解释一下什么是TLAB?
TLAB线程本地分配缓存区是什么?工作原理分析,TLAB全称Thread Local Allocation Buffer,即线程本地分配缓存区,是一个线程专用的内存分配区域。在线程初始化时,虚拟机会为每个线程分配一块TLAB空间,只给当前线程使用。
简单来说:
就是JVM为每个线程在堆内存中划分的一个“可分配空间”,这样的好处就是为了避免多线程下,在堆空间上的分配错乱问题。其实内存读取是共享的,只是有独有的分配权。
TLAB空间的内存也非常小,默认情况下仅占有整个Eden空间的1%,所以一些大对象是不经过TLAB区域分配的,这种分配就需要进行同步控制,所以经常说的小的对象比大的对象分配起来更加高效。
这样可以提升分配效率,且不影响对象的移动和回收。
也可以通过选项-XX:TLABWasteTargetPercent设置TLAB空间所占用Eden空间的百分比大小。
Java对象创建过程?
new -> 类加载检测 -> 对象内存分配 -> 值初始化 -> 设置对象头 -> 执行init 方法。
遇到new 指令时
先看是否能在常量池中找定位 类的符号引用
类加载检测
后看涉及的class相信是否已经加载。
对象内存分配
分配堆内存,具体分配方式看堆的整齐性,这个️GC机制决定。
分配方式:
1.指针碰撞(堆整齐的话)??? 啥玩意叫指针碰撞,是不是可以理解直接指针指向内存地址就行。。
2.空闲列表 (堆不整齐的话)
分配时并发问题及解决方案:
1.CAS自选(比较交换机制,乐观锁) 这就是上面所说的大对象分配机制。。?
2.TLAB本地内存,指定一块区域的分配权限。
值初始化
为对象的属性分配一些初始化值,比如int = 0, long = 0 等默认值。
设置对象头
填充对象头信息,比如HashCode, GC 年龄,锁标记,锁信息,Class类型指针等等。。
说白了就是内存也整好了,初始化也正好了,要复盘记录自己的财产信息了。。
执行Init方法
最后一步执行init函数,也就是类的构造函数,主要对属性赋值。
仔细理解下面分配过程: 细品!!!



