Java虚拟机剖析
2021-01-21 12:41:38 3 举报
Java虚拟机剖析、JVM
作者其他创作
大纲/内容
0
Young
25位
卸载5
23bit
偏向锁已启动
标记-清除算法 减少回收停顿时间 碎片-XX:CMSIntiationgOccupancyFractionConcurrent Mode Failure 启用Serial Old
实例数据Instance data
ZGC
int
S0From Space
偏向锁未启动
初始化3
1
准备2.2
到对象类型数据的指针
Car Class(类信息)
JVM Heap(-Xms -Xmx)
锁状态
54位
对象引用(引用E)
重量级锁
对象的访问
2bit锁标志位
指向互斥量(重量级锁)的指针
无锁态(new)
加载初始化
4
reference
TLAB
MinorGC,MajorGC,FullGCMinorGC的触发条件1、当年轻代空间不足时,就会触发MinorGC,这里的年轻代指的是Eden代满,Survivor满不会触发GC。每次MinorGC会清理年轻代的内存2、因为Java对象大多朝生夕灭,所以MinorGC非常频繁3、MinorGC会引发STW老年代GC(MajorGC/FullGC)触发条件1、指发生在老年代的GC,对象从老年代消失,我们说“MajorGC”“FullGC”发生了 2、出现了MajorGC,经常会伴随至少一次MinorGC①非绝对,在Parallel Scavenge收集器的收集策略里就直接进行MajorGC的策略选择过程②也就是老年代空间不足,会先尝试触发MinorGC,如果之后空间还不足,则触发MajorGC3、MajorGC的速度比MinorGC慢10倍以上,STW的时间更长4、如果MajorGC后,内存还不足,就报OOM了FullGC的触发机制1、调用System.gc()时,系统建议执行FullGC,但是不必然执行2、老年代空间不足3、方法区空间不足4、通过MinorGC后进入老年代的平均大小,大于老年代的可用内存5、由Eden区,Survivor 0区向Survivor 1区复制时,对象的大小大于ToSpace可用内存,则把改对象转存到老年代,且老年代的可用内存小于该对象的大小6、FullGC是开发或调优中尽量要避免的,这样暂停时间会短一些。
虚拟机栈(B)
轻量级锁
Survivor1
类加载器子系统classload(F)
连接2
对象在内存中的存储布局
GC标记信息
数组int[] a = new int[4]T[] a = new T[5]
通过句柄访问对象
方法区(D)
iload_0
F 如Car.java
线程ID
到对象实例数据的指针
对象布局
对象类型数据
字节码执行引擎execution engine
Eden(伊甸园)
01
JVM参数分类标准:-开头,所有的Hotspot都支持非标准:-X开头,特定版本HotSpot支持特定命令不稳定:-XX开头,下个版本可能取消-XX:+PrintConmandLineFlags-XX:+PrintFlagsFinal 最终参数值-XX:+PrintFlagsInitial 默认参数值
01 00 00 00(00000001 00000000 00000000 00000000)(1)
Old
……
padding
(object header)
操作压栈
8
2bit
unused
根据new的参数在常量池D中定位——符号引用
默认
car1
对齐padding
Object o,对象指针在没压缩的情况下占8个字节,在压缩的情况下占4个字节,参数为UseCompressedOops
TYPE DESCRIPTION
short
方法区
Old区
JVM
OldGen
变量压栈
方法出口
锁升级初步
是否偏向锁
操作数栈
锁升级过程
double
分代年龄
小
指向互斥量(重量级锁)的指针
end
实例数据instance data
Young Gen(-Xmn)
Epsilon
Virtual-伸缩区
Serial
new
62位
ParallelScavenger
程序计数器(A)
普通对象
类型指针class pointer
Serial Old(MSC)
GC清除
iload_1
Java栈本地变量表
OFFSET
元空间/方法区
逃逸分析:就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中。-XX:+DoEscapeAnalysis // 使用-XX:-DoEscapeAnalysis // 不用使用逃逸分析,编译器可以对代码做如下优化:一、同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。二、将堆分配转化为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。(栈上分配--Hotspot并没有实现真正意义上的栈上分配,实际上是标量替换)三、分离对象或标量替换,有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。在动态编译同步块的时候,JIT编译器可以借助逃逸分析来判断同步块的时候,JIT编译器可以借助逃逸分析来判断同步块所使用的锁对象是否只能够被一个线程访问而没有被分布到其他线程。如果同步块所使用的锁对象通过这种分析被证实只能够被一个线程访问,就是优化成锁消除。
MarkWord,记录hashCode、GC、锁信息
1.8之前叫永久代
2位
31位
hashCode(如果有调用)
1位
对象实例数据
将分配的内存初始化为零值
存活区-Survivor
对象的hashCode
1、指针碰撞:依靠连续的内存空间,靠指针的移动来分配内存。2、空间列表:由固定的列表记录内存分配的信息,每一线程指定一块空间。TLAB(Thread Local Allocation Buffer,即线程本地分配缓存区)
Full GC
Java堆
无锁态
1、GlobalEscape(全局逃逸):对象的引用复制给了一个类对象,或者包含在另一个全局逃逸的对象里。2、ArgEscape(参数级逃逸):方法调用过程中传递对象引用给另一个方法3、NoEscape(没有逃逸):一个可以进行标量替换的对象
Object
Klasspointer,类指针通过java -XX:+PrintCommandLineFlags -version查看是否启用类指针、普通对象指针压缩在64bit计算机上不启用的话为8个字节,启用的话为4个字节
匿名偏向
8字节对齐
元空间
Hotspot垃圾回收新生代:复制算法 (MinorGC)老年代:标记整理算法(MajorGC)
ParNew
2、直接访问对象reference直接指向了对象类型数据,那么java堆对象分布中就必须考虑如何放置访问类型数据的相关信息,reference存储的直接就是对象地址。好处就是,减少一次指针定位的时间开销。
两个种方式各有优势:1、使用句柄最大好处是reference中存放是稳定句柄地址,在对象被移动(垃圾收集时会产生)时只改变句柄中实例数据指针,reference本身不用改变。2、使用指针最大好处就是速度快,节省了一次指针定位的时间开销,由于对象访问在Java中非常频繁,所以积少成多也是一项可观的执行成本。3、HotSpot主要是用指针,进行对象访问(例外情况,如果使用Shenandoah收集器的话,也会有一次额外的转发)。
轻度竞争
Survivor0
CMS:15G1:6
新生代与老年代空间默认比例1:2-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3Eden空间和另外两个Survivor空间缺省所占的比例是:8:1:1-XX:SurvivorRatio调整这个空间比例
32位长度(4b)
methodB
大
getClassLoader()
car2
逃逸分析
markword分析 64位
00 00 00 00(00000000 00000000 00000000 00000000)(0)
Major GC
class文件
float
动态链接
B
Epoch
本地方法栈(C)
类加载的生命周期
CMS过程用到的标记信息
没有逃逸
新生代Young
开启逃逸分析的JVM参数:-XX:+DoEscapeAnalysis
1、程序计数器:指向当前线程正在执行的字节码的地址、行号。线程私有,无GC。2、虚拟机栈:存储当前线程运行方法所需要的数据、指令,返回地址。线程私有,无GC。3、本地方法栈:同虚拟机栈,不同的是,它存的是本地方法的数据。4、方法区:存储类信息(字段方法的字节码,部分方法的构造器),常量,静态变量,JIT(即时编译的信息)。线程共享,无GC,非堆区。(java.lang.OutOfMemoryError:PermGen space)5、堆-heap:存储类实例,一个jvm实例只有一个堆内存。线程共享,要GC。Jdk1.6及之前:有永久代,字符串常量池和运行时常量池在方法区Jdk1.7:有永久代,但已经逐步“去永久代”,字符串常量池移到堆中,运行时常量池还在方法区中(永久代)Jdk1.8及之后:无永久代,字符串常量池在堆中,运行时常量池在元空间
Minor GC
方法区是一种定义,概念。而所谓永久带或元空间是其一种实现机制
12
对象引用:就是通过栈帧中局部变量表所存储的对象引用来对堆内存中的对象实例进行访问或操作的!简单点理解就是栈帧中有个对象引用的指针,通过各种方法指向了堆内存中的对象实例。
-XX:MaxPermSize
偏向锁
加载1
句柄池
ordinary object pointerclass pointer
11
标记整理
25bit
1-加载类文件从class文件或者jar中,或只从二进制中。以类全名标识存入方法区,供之后的使用。2.1-验证类文件是否符合jvm的规范,验证一些类的基本信息。格式验证、语义分析、操作验证。2.2-准备为类的静态变量和常量分配空间和初始值,在堆中分配空间。2.3-就是把将常量池中的符号引用转为直接引用,可以认为是一些静态绑定的会被解析,动态绑定则只会在运行是进行解析;静态绑定包括一些final方法(不可以重写),static方法(只会属于当前类),构造器(不会被重写)。3-初始化将一个类中所有被static关键字标识的代码统一执行一遍,如果执行的是静态变量,那么就会使用用户指定的值覆盖之前在准备阶段设置的初始值;如果执行的是static代码块,那么在初始化阶段,JVM就会执行static代码块中定义的所有操作。所有类变量初始化语句和静态代码块都会在编译时被前端编译器放在收集器里头,存放到一个特殊的方法中,这个方法就是<clinit>方法,即类/接口初始化方法。该方法的作用就是初始化一个中的变量,使用用户指定的值覆盖之前在准备阶段里设定的初始值,但在<clinit>方法内部不会显示调用父类的<clinit>方法,由JVM负责保证一个类的<clinit>方法执行之前,它的父类<clinit>方法已经被执行。JVM必须确保一个类在初始化的过程中,如果是多线程需要同时初始化它,仅仅只能允许其中一个线程对其执行初始化操作,其余线程必须等待,只有在活动线程执行完对类的初始化操作之后,才会通知正在等待的其他线程。
轻量级锁:在线程交替执行同步块时提高性能。偏向锁:在只有一个线程执行同步块时进一步提高性能。重量级锁:存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。
数组长度length(4字节)
来回换
getClass()
锁标志位
当前线程指针JavaThread*
方法调用结束,栈pop
Parallel Old
Object Header(对象头)1、Mark Word(标记字段)自身运行时数据:哈希值,GC分代年龄,锁状态标志,线程持有锁,偏向线程ID,偏向时间戳2、Klass Pointer(类型指针)类的元数据的指针(D)3、数组长度(只有数组对象才有)
EdenSpace
MinorGC
虚拟机为对象分配内存E
-优先分配Eden区1、大对象直接分配到老年代-XX:PretenureSizeThreshold2、长期存活的对象分配老年代-XX:MaxTenuringThreshold=153、空间分配担保-XX:+HandlePromotionFailure检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小。4、动态对象年龄对象如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代-XX:TargetSurvivorRatio
解析2.3
调用对象的init方法
1、句柄java堆中划分出一块内存作为句柄池,reference中存储的是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。句柄访问最大的好处就是reference中存储的是稳定的句柄地址,在对象被移动时,只用修改句柄中的实例数据指针,而reference本身不需要修改!
CMS
Thread-local allocation buffer在EdenSpace区为每个线程划分一个区域,默认配置为1%,因为堆是对每个线程共享的,线程在堆上分配对象需要加锁操作
普通对象new XX()
10
垃圾回收
java -XX:+PrintCommandLineFlags -version-XX:InitialHeapSize=266582848 -XX:MaxHeapSize=4265325568 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGCjava version \"1.8.0_172\
1bit
new 类名()
VALUE
基本类型
S1ToSpace
实例池
本地方法接口
E 堆内存模型
指向线程栈中Lock Record的指针
轻量级锁 自旋锁 无锁
重度竞争(耗时过长 wait等)
Shenandoah
栈
若未找到引用,则执行类的加载、验证、初始化
老年代
1bit偏向锁位
e5 01 00 20(11100101 00000001 00000000 00100000)(536871397)
ClassLoader
Young都是复制回收
调用
使用4
发生逃逸
4bit
验证2.1
G1
一个方法一个栈帧
通过直接指针访问对象
1、线程是用来执行方法的,至于怎么执行,取决于虚拟机栈。2、我们定义的常量和静态变量是存在方法区的,就是用于线程之间共享的。
本地方法库,c/c++
对象头markword
SIZE
methodA
FGC:MinorGC+MajorGC
GC标记
标记-整理算法
指向栈中锁记录的指针
堆空间分代思想:优化GC性能内存分配策略1、如果对象再Eden出生并经过第一次MinorGC后仍然存活,并且能被Survivor区容纳,则被移动到Survivor空间中,并将对象年龄设置为1,对象再Survivor区每熬过一次MinorGC,年龄就+1,当年龄增加到一定程度(默认为15,不同Jvm,GC都所有不同)时,就会被晋升到老年代中 -XX:MaxTenuringThreshold=152、优先分配到Eden3、大对象直接分配到老年代(尽量避免程序中出现过多的大对象)4、长期存活的对象分配到老年代5、动态对象年龄分配(如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄)6、老年代的最大可用连续空间大于新生代对象总大小,或者历次晋升的平均大小,就会进行MinorGC,否则进行FullGC
AGE
对齐填充Padding(保证对象是8个字节的整数倍)
对象大小
1、垃圾对象的判断:引用计数器;可达性分析(虚拟机栈-局部变量,方法区类属性和常量所引用的对象,本地方法栈所引用的,都可作为GCroot)2、回收策略:标记清除(Mark-Sweep,效率差,存在内存碎片),复制(Copying,没有碎片,但浪费空间),标记整理(Mark-Compact,没有碎片,需要移动对象),分代收集(目前大部分JVM的垃圾收集器所采用的算法,把堆分成新生代和老年代)。3、垃圾回收期:Serial(单线程GC,会STW),ParNew(多线程的GC,会STW),Parallel-Scavenge(多线程同parNew,不过它关心的是吞吐量,用户代码time/(用户time + GC time)),CMS(三阶段a初始标记,STW,b并行标记,c重新标记并清除,STW-----占用cpu,空间碎片,并发异常),G1(并发和并行,分代,空间整合,可预测停顿),ZGC(jdk11)。
JVM运行时数据区
00
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.9</version></dependency>
Car实例
空
局部变量表
Car.class
对象内存分配
压栈
实例化
通过工具JOL分析new 对象的对象布局Java Object Layout
堆(E)
0 条评论
回复 删除
下一页