JVM概览
2022-03-27 12:26:42 0 举报
JVM
作者其他创作
大纲/内容
空闲内存
元数据空间 | 方法区 | 永久代 (Meta Space)
是否编译
成功
1.获取class字节码文件二进制字节流2.将磁盘文件静态结构载入内存方法区转换为运行时数据结构<类信息>3.将载入后的类信息进行组装,在堆空间中生成类对象(class),作为数据入口
在程序执行过程中能够随着程序的需要生成并执行新的代码及称为即时编译。在目前的商用虚拟机里面实现了JIT即时编译这种技术。虽然Java虚拟机规范中没有明确指出必须要实现它,但是JIT编译性能的好坏、代码优化程度的高低确实衡量一款商用虚拟机最关键的指标之一,它也是虚拟机中最核心且最能体现虚拟机技术水平的部分在HotSpot中Java程序通常情况下都是通过解释器(Interpreter)一边解释一边执行代码的,当虚拟机检测到某个代码块或者方法体执行非常频繁时,会将这些检测的代码判断为热点代码。而如果这些热点代码都通过解释器的模式执行,那么每次执行都需要用到解释器解释,为了提升热点代码执行效率,在运行时虚拟机会将这些代码编译成与当前本地平台+硬件相关的机器码并进行各种层面的优化,同时也会将方法的调用地址指向编译后的机器码并且也会将编译后的机器码保存道磁盘,而这个编译热点代码的编译器被称为JIT(即时编译)器PS:当热点代码被检测到时,不管是代码块还是方法体,虚拟机都是以方法为最小单位进行编译。HotSpot中实现的两种JIT编译器:ClientCompiler:局部优化,追求编译速度/ServerCompiler(C2):充分优化,追求编译质量。会根据JVM运行模式来决定选择谁。
加载
否
用于存放int、long、short、float、double、char、byte、boolean八种基本数据类型,存放以变量槽(slot)为最小单位(32bit/位span style=\"font-size: inherit;\
根可达算法
reference
不分代回收GC器
重度竞争、耗时过长、自选过多等
词法分析
Java对象创建过程
class文件
当出现大量线程(CPU核数一半+)同时竞争轻量级锁或者一个线程CAS10次(默认时十次,可以通过-XX:PerBlockSpin调整)以上是轻量级锁发生锁膨胀,升级到重量级锁
父类构造方法
新生代(New) 1/3
轻量级锁
偏向锁未启动
虚引用:虚引用是指使用PhantomReference类型修饰的对象,不过再使用虚引用的时候是需要配合ReferenceQueue引用队列才能联合使用。与其他几种引用不同的就是虚引用不会决定GC机制堆一个对象的回收权,如果一个对象仅仅存在虚引用,那么GC机制将会把它当成一个没有任何引用类型的对象,随时随刻都可以回收它,不过他有一个额外的用途:跟踪垃圾回收过程,也正是由于虚引用可以跟踪对象的回收时间,所以可以将一些资源释放操作放置再虚引用中执行和记录
老年代分配
记录方法结束时的出栈地址(正常执行结束时的返回地址或者由于报错结束时的异常地址)
栈上替换
解析
内存完整:①复制②整理③清除内存利用:①整理②清除③复制
javac源代码编译器
初始化
......
引用计数法
复制算法:将原有的内存区域一分为二,在同一时间只会使用其中一块内存区域用来分配对象。在发生GC时,首先会通过根可达算法判断存活对象,并且将所有的存活对象移动到另一块未使用的内存区域,最后对于前面的这块使用内存区域中的对象进行统一回收。缺点:①不适用于存活对象多的情况,移动对象对于空间/时间开销太大②内存利用率太低,这种算法下无论何时永远有一半内存区域的浪费优点:内存完整
Parellel Old(并行)整理
尝试TLAB分配
动态链接
是
STW(Stop-The-World):在GC发生时,所有用户线程都需要挂起停止,对于用户而言就是整个程序卡住不能动了,直到等到GC完成后用户线程才能重新恢复执行。至于为什么需要停止用户线程也很好理解,GC发生时,GC线程和用户线程操作的时同一块内存区域,如果用户线程不停止,GC线程前脚刚标记完一块区域,用户线程就在这快区域创建了一个对象,当回收的时候,可能这个对象还时存活的,那么会导致该对象被清除了,对于业务而言会出现很大的影响,存在线程安全的问题。
对象头
引用级别:强引用→软引用→弱引用→虚引用
多标-浮动垃圾:在并发标记过程中,如果由于方法运行结束导致部分局部变量(gcroot)被销毁,这个gcroot引用的对象之前又被扫描过(被标记为非垃圾对象),那么本轮GC不会回收这部分内存。这部分本应该回收但是没有回收到的内存,被称之为“浮动垃圾”。浮动垃圾并不会影响垃圾回收的正确性,只是需要等到下一轮垃圾回收中才被清除。另外,针对并发标记(还有并发清理)开始后产生的新对象,通常的做法是直接全部当成黑色,本轮不会进行清除。这部分对象期间可能也会变为垃圾,这也算是浮动垃圾的一部分。
对于每个对象都自带一个计数器,有对象引用自己时+1,取消引用时-1。当计数器为0时该对象标记为垃圾可以被回收(但是会存在a,b相互引用导致循环问题造成内存泄漏)
字面量:文本字符串final常量值基本类型数值.....
标记复制算法
Serial(串行)复制
验证文件格式/元数据/字节码/符号引用等
①处于并列优先级的按照Java程序编写时代码的先后顺序加载。②前面两步为静态过程,程序运行过程中只会执行一次,之后再次创建该类或者该子类的对象时并不会再次执行
array length
类加载子系统
字节码效验器
尝试栈上分配
偏向锁已启动
子类构造方法
卸载
JVM启动前四秒new出来的对象并未启动匿名偏向锁
类加载检测
本地方法接口(Native Interface)
值初始化
TLAB(线程本地内存)1
young
-client: 代表以client模式运行虚拟机-XX:ThreadStackSize: 设置线程栈默认大小-XX:-UseTLAB: 开启TLAB线程本地空间-Xnoclassgc: 关闭垃圾回收机制-XX:+CollectGen0First: FullGC是否先Young GCXX:+UseAdaptiveSizePolicy: 自适应Survivor和Eden大小-XX:GCTimeRatio:设置GC时间占程序运行时间的百分比-XX:NewSize 设置年轻代的初始大小-XX:MaxNewSize 设置年轻代的最大值-XX:+AggressiveOpts 加快编译-XX:+PrintGCApplicationStoppedTime 打印垃圾回收期间程序暂停的时间-XX:+DisableExplicitGC 关闭System.gc()-XX:+UseBiasedLocking 改善锁机制的性能
如果第一个线程试图获取锁时判断匿名偏向发现匿名偏向锁未开启时会直接膨胀未轻量级锁,先将markword中除开锁标志位的信息全部copy到自己栈内存中锁信息的LockRecord中,然后再尝试CAS将对象头内的mrakword中的指针指向自己栈内锁信息,能够替换成功代表获取到锁,替换失败继续自旋
四、完成初始化操作后接着会对于对象的对象头进行设置:①markword:储存对象自身的运行时数据,如:hashcode、GC分代年龄、锁标志、锁信息等;②klasspointer:类型指针,指向它对应的类元数据,VM用这个确定其属于哪个类的实例。
栈帧2
标记整理算法
匿名偏向锁未开启
如果是句柄引用reference则存储的是堆中句柄池的句柄,如果是直接引用reference则存储的是堆中对象的内存地址
双亲委派模型:当前类加载器需要加载XX类时不会自己直接去加载,而是尝试将加载这个类的任务向上传递给父加载器去完成,如果父类加载器能加载则由父类加载器来完成而自己不会再去加载。(好处:外部想要替换JDK的系统类,篡改它的实现时,因为父类加载器已经加载过JDK的系统类子类加载器不会再加载,从而能够再一定程度上防止危险代码的植入)
父类静态变量/静态代码块
指针碰撞:指针碰撞是Java在为对象分配对内存时的一种内存分配方式,一般适用于Serial和ParNew等不会产生内存碎片、堆内存完整的垃圾收集器。分配过程:堆中已用分配内存和为分配的空闲分别用处于不同的一侧,通过一份指针指向分界点区分,当JVM要为一个新的对象分配内存时,只需要把指针往空闲的一端移动与对象大小相等的距离即可。
轻度竞争
强引用:通常是通过new指令创建的对象都属于强引用类型,堆中的对象和栈中变量存在直接引用,对于这类存在强引用的对象。众所周知,当堆内存不足时,GC机制会被强制触发,但是如果GC器大仙堆内存对象都存在强引用时,GC器不会强制回收,而是触发OOM。所以一般情况下再编码的时候,如果不确定一个对象不在使用之后可以显式的将对象的引用清空:object=null;这样能够方便再查找垃圾的直接发现它。
GC器 连线表示可搭配使用
注解抽象语法树
匿名偏向
语义分析
局部变量表
G1(并发)整理+复制
Shenandoah(并发)整理+复制
标记-压缩(整理)算法:标记阶段通过可达性分析算法标记出所有存活对象,整理/压缩阶段将所有存活得对象挪动到内存得一段,然后对于边界之外得所有对象进行统一回收缺点:需要移动对象,所以效率也不是特别高优点:内存完整
连接
标记-清除算法:标记阶段通过可达性算法标记出所有存活对象,然后在清除阶段对于所有未标记对象进行统一回收。缺点:①两个阶段的效率都不高②会产生大量的内存碎片优点:简单
方法/回边计数器+1/n
同步消除:通过逃逸分析对一个对象进行逃逸判断之后,如果该对象为线程级作用域不可逃逸时,则代表当前这个对象只会有一个线程可访问。如果当前这个对象时被作为同步锁对象的,那么JVM会在编译时消除加锁和解锁的代码。决定能否同步消除(满足一个即可):①当前对象被分配在栈上②当前对象无法逃逸出线程作用域。
markword
新生代分配
Extension类加载器
.java源文件
两个计数器相加是否超过阈值
栈上分配:一般而言Java对象创建出来都是会在堆上进行内存分配,但其实并不是所有的对象都会在堆中分配,有时候也能够在栈上进行分配。不过想要将对象在栈上进行分配,需要先开启变量替换以及逃逸分析决定一个对象能否在栈上分配的因素:①对象能够通过标量替换分解成一个个标量。②对象在栈帧级作用域不可逃逸。如果一个对象能够满足如上两点则代表该对象可以在栈上进行分配
ParNew(并行)复制
ZGC(并发)整理+复制
方法返回
对象存活判断算法
编译结束
JIT(Just In Time Compiler)即时编译
五、最后执行<init>函数,也就是类的构造函数,主要是堆属性赋值
执行编译后的机器码
执行栈上替换
进行编译
当第二个线程试图获取锁资源时发现偏向锁已开启,那么会进行上一步操作,尝试将自己的线程ID设置到markword中,但是之前第一个线程已经赋值,对象头存储的时第一个线程的ID,那么此时会判断是否是安全点(第一个线程是否已执行完毕),如果第一个线程已经执行完毕,那么会发生偏向锁撤销(将锁对象清除锁记录并回归无锁状态),然后第二个线程重新CAS自旋将自己的线程ID设置到markword中,重新偏向
Eden 8/10
本地方法库(Native Libraries)
不管当前对象是处于何种锁状态,只要你调用了Object.wait()方法,那么该锁会直接膨胀成重量级锁,因为wait方法实则还是依赖于momitor实现,所以不管什么时候调用wait都会使得对象头种markword内出现指向monitor的指针,而markword的改变对于用户线程是不可逆的,一旦出现monitor指针,就无法回到之前的锁状态
子类静态变量/静态代码块
本地方法栈(Native Method Stack)
运行时常量池
当第一个线程试图获取锁资源时会先判断匿名偏向是否已开启,如果开启会先将markword内除标志位的信息全部copy到自己栈内存中锁信息的LockRecord中并尝试利用自旋将自己的线程ID设置到markword中,以后每次该线程执行用该对象作为锁资源的代码无需加锁和放锁操作,只是再LockRecord中加一个空的markword
磁盘载入内存
操作数栈
已用内存
基本数据类型
类加载器
wait
Klasspointer
方法执行
一、调优指标:①降低回收STW延迟②提升吞吐量③减少内存占用量二、调优原则:①MinorGC回收原则:每次MinorGC都要尽可能回收更多垃圾对象,以减少应用程序发生Full GC的频率②GC内存最大化原则:处理吞吐量和低延迟问题的时候,垃圾处理器能使用的内存越大,垃圾收集的效果越好,应用程序也会越来越流畅③GC调优3选2原则:在性能属性里,吞吐量、延迟度、内存占用率,我们只能选择其中两个进行调优,不可三者兼得三、JVM调优常用参数-Xms 堆最小内存-Xmx 堆最大内存-Xss 虚拟机栈大小-XX:NewRatio:Old/New的比例-Xmn: 年轻代大小,调整会影响老年代大小,官方建议为堆大小的3/8-XX:SurvivorRatio: 调整Survivor区和Eden区的大小比例-XX:MetaspaceSize: 元空间初始化大小,JVM默认20.75M-XX:MaxMetaspaceSize: 元空间最大大小,逻辑限制为物理内存上限-XX:PretenureSizeThreshold: 大对象直接进入老年代的阈值(字节为单位)-XX:TargetSurvivorRatio: 动态年龄判断比例设置-verbose:gc: 输出JVM的GC情况-XX:+PrintGCDatails: 输出GC详细情况-XX:+PrintGCTimeStamps: 输出GC的时间戳(以基准时间的形式)-XX:+PrintTenuringDistribution: 输出对象GC年龄信息-XX:+PrintHeapAtGC: 在进行GC的前后打印出堆的信息-Xloggc:路径: 日志文件的输出路径-XX:+UseG1GC:用G1垃圾收集器-XX:+PrintCommandLineFlags -version: 输出默认的垃圾回收器-XX:+PrintGC: 在控制台输出GC信息-XX:+PrintTLAB: 可以查看TLAB的使用情况-XX:+UseSpining: 开启自旋锁-XX:MaxGCPauseMillis: 设置每次年轻代垃圾回收最长时间-XX:EliminaAllocations: 开启标量替换-XX:+DoEscapeAnalysis: 开启逃逸分析+XX:+EliminateLocks: 开启同步消除-XX:+PrintCliminateAllocations: 查看标量替换情况-server: 代表以server模式运行虚拟机
当第二个线程尝试获取锁,但是发现第一个线程还在执行,那么会将对象回归无锁态并将markword中除开锁标志位的信息全部copy到自己栈内存中锁信息的LockRecord中,然后再尝试CAS将对象头内的mrakword中的指针指向自己栈内锁信息,能够替换成功代表获取到锁,替换失败继续自旋(小细节:如果此时之前持有偏向锁的线程来获取锁,优先CAS)
标量替换:使用标量替换聚合量(对象),对于一个对象进行分解,将其拆解成一个个小的标量的过程(需要先进行逃逸分析,不可逃逸的对象才能进行标量替换)。标量:不可分割的量,指基本数据类型和reference类型好处:①能够节省堆内存,因为进行标量替换之后的对象可以在栈上进行内存分配。②相对运行而言省去了去堆中查找对象引用的过程,速度会更快一些。③因为时分配在栈上,所以会随着方法的结束和线程栈的弹出自动销毁,不需要GC的介入
直接内存
TLAB(线程本地内存)2
弱引用:弱引用是指使用WeakReference类型修饰的对象,与软引用的区别在于:弱引用类型对象的生命周期更短,因为弱引用类型的对象只要被GC发现,不管当前的堆内存是否紧张,都会被GC机制回收。不过因为GC线程的优先级比用户线程更低,所以一般不会立马发现弱引用类型对象,因此一般弱引用类型的对象也会有一段不短的存活周期
符号引用:类和结构全限定名字段名称和描述符方法名称和描述符
线程私有区
通过GCRoots(栈中变量/方法区静态变量/Monitor持有者对象/常驻异常对象/JVM内部引用/方法区常量)对象作为起始点向下搜索,搜索走过的路径被称做引用链,当一个对象到GCRoots没有任何引用链时即根不可达时该对象则判断为不可用可以被回收
二、创建一个对象锁需要的内存在类加载完成时就能确定,内存分配是指在堆中划出一块和对象大小的对应内存出来,具体的分配方式根据堆内存的整齐性确定,而堆内存的整齐性则由当前程序采用GC机制决定。分配方式:①指针碰撞(堆整齐)②空闲列表(堆不整齐) 分配出现并发情况解决方案:①CAS自旋②TLAB本地内存
栈帧1
三、接着JVM会初始化分配好的内存,将其设为零值(不包括对象头,如果使用了TLAB,这一步会提前到内存分配阶段进行)
解释执行
基于计数器实现的热点探测JVM给每个方法绑定了两个计数器:1.方法调用计数器:方法被调用执行一次时计数器+12.回边计数器:统计一个方法体内循环体循环的次数.
指针压缩:在64bit的虚拟机中为了提升内存的利用率,所以出现了指针压缩这一技术,指针压缩的技术会将Java程序中的所有引用指针(类型指针、堆引用指针、栈帧内变量引用指针等)都会压缩一半,而Java中一个指针的大小是栈一个字宽单位的,在64bit的虚拟机中一个字宽的大小未64bit,所以也就意味着在64位的虚拟机中,指针会从原本的64bit压缩位32bit的大小,而指针压缩这一技术在JDK1.7之后是默认开启的。指针压缩失效:指针压缩带来的好处无可厚非,几乎能够为Java程序节省很大的内存空间,一般而言,在不开启压缩的情况下对象需要14GB,在开启指针压缩之后几乎能够在10GB内存内分配这些对象。但是压缩的技术带来好处的同时,也存在非常大的弊端,因为指针通过压缩技术后被压缩到32bit,而32bit的指针最大寻址为32GB,也就代表着如果你的堆内存为32G时出现了OOM问题,你此时将内存扩充到48GB时仍有可能会出现OOM,因为内存超出32GB后,32bit的指针无法寻址,所有压缩的指针将会失效,发生指针膨胀
编译器以方法为最小单元对于热点代码进行编译之后,原本的方法调用地址被替换为编译后的方法地址
JVM启动后四秒new出来的对象默认启动匿名偏向锁
父类变量/代码块
与虚拟栈相似,但是本地方法栈是为虚拟机的Native方法提供服务;HotSpot将虚拟机栈与本地方法栈合二为一
解释器解释
Survivor1 1/10(To/s1)
old
解释器字节码→解释器执行→结果
由于OOP思想中存在多态的概念使得编译器在编译源代码时无法确定对象类型,只有在运行时才能确定对象,指向常量池中方法的引用
后台线程执行编译
软引用:软引用是指使用SoftReference类型修饰的对象,当一个对象只存在软引用时,再堆内存不足的时候,该引用级别的对象将被GC机制回收。当然再发生GC时如果堆内存还充足,那么是不会回收该引用级别的对象(可以用来实现缓存)。
尝试站上分配
SerialOld(串行)整理
根据验证后的类信息初始化类结构变量
TLAB分配
虚拟机栈(Stack)
常量池符号引用替换为直接引用<因为多态>
逃逸分析:判断变量作用域是否存在于其他栈帧或者线程中。当一个对象的指针被多个方法或者线程引用,我们称这个指针发生了逃逸,判断逃逸的方法被称为逃逸分析。逃逸的作用域:①栈帧逃逸:当前方法内定义了一个局部变量逃出了当前方法/栈帧。②线程逃逸:当前方法内定义了一个局部变量逃出了当前线程能够被其他线程访问。全局变量赋值逃逸:当前对象被赋值给类属性、静态属性参数赋值逃逸:当前对象被当作参数传递给另一个方法方法返回值逃逸:当前对象被当作返回值return
提交OSR编译请求
.......
老年代(Old) 2/3在新生代达到一定条件的对象会进入老年代:1.s0和s1区域交换一次年龄+1当对象年龄达到15岁进入(cms默认为6岁)2.大对象直接进入(可设置大小判断标准)3.s区相同年龄的对象大小总和超过s区空间一半,大于等于该年龄的所有对象直接进入
失败
方法出口
来回交换 同一时间内永远有一个为空
CMS(并发)清除
实例数据
设置对象头
.class字节码文件
对齐填充
本地内存
普通对象
抽象语法树
字节码生成器
User类加载器
Mojor GC
一、当遇到new指令时,VM首先会进行类加载检测:①检测new指令的参数是否能在常量池中定位类的符号引用。②检测这个符号引用是否进行过加载解析和初始化,没有则先对该类进行类加载。
堆空间(Heap) --Full GC
子类变量/代码块
可以理解为PC寄存器,作为用于储存计算的临时数据存储区,当CPU执行load指令时能将数据加入到操作数栈
标记清除算法
重量级锁
垃圾回收 GC
偏向锁
方法入口
new
字符串常量池
Minor GC
JIT动态编译
程序计数器(Program Counter Register)
漏标-读写屏障:漏标会导致被引用的对象被当成垃圾误删除,这是严重bug,必须解决,有两种解决方案: 增量更新(Incremental Update) 和原始快照(Snapshot At The Beginning,SATB) 。增量更新就是当黑色对象插入新的指向白色对象的引用关系时, 就将这个新插入的引用记录下来, 等并发扫描结束之后, 再将这些记录过的引用关系中的黑色对象为根, 重新扫描一次。 这可以简化理解为, 黑色对象一旦新插入了指向白色对象的引用之后, 它就变回灰色对象了。原始快照就是当灰色对象要删除指向白色对象的引用关系时, 就将这个要删除的引用记录下来, 在并发扫描结束之后, 再将这些记录过的引用关系中的灰色对象为根, 重新扫描一次,这样就能扫描到白色的对象,将白色对象直接标记为黑色(目的就是让这种对象在本轮gc清理中能存活下来,待下一轮gc的时候重新扫描,这个对象也有可能是浮动垃圾)以上无论是对引用关系记录的插入还是删除, 虚拟机的记录操作都是通过写屏障实现的。
Survivor0 1/10(From/s0)
运行时数据区
对象内存分配
Bootstrap类加载器
进行加载
类(class)信息属性方法类元信息(类型信息/类型的常量池/方法信息/字段信息/类加载器的引用信息/Class对象实例引用信息/方法表)
准备
不管能不能分配下都是在Eden区进行分配,因为TLAB指的是HotSpot对于new对象的优化,因为会出现同时多个线程在创建(new)对象,那么假设如果两个线程都看上同一块内存,就会在这里浪费很多争抢的时间,所以HotSpot为了每一个线程都能在Eden区中分配了一块专享的空间供线程使用
执行类构造器<clinit>()生成class对象放入堆中
分代回收GC器
分配担保:当发生GC时,一个S区空间无法储存Eden区和另外一个S区存活对象时,这些对象直接被转移到老年代,这个过程就是空间分配担保。JDK8以后,在进行Minor GC前,如果老年代的连续空间大于新生代对象大小总和或者历次晋升的平均大小,则进行Minor GC,否则进行Full GC
热点代码缓存(hotCodeCache)JIT即时编译产物
验证
ParallelScavenge(并行)复制
直接引用:①直接指向内存地址的指针。②相对的偏移量(方法指针/指向实例变量)③句柄池中间接指向内存地址的句柄符号引用:符号引用以一组符号来描述所引用的目标。符号引用可以时任何形式的字面量,只要使用时能无歧义地定位到目标即可。在类加载的解析阶段,会有一步操作就是将class文件中的符号引用替换成直接引用,简单来说就是:在编译的时候每个Java类都会被编译成一个class文件,但在编译的时候因为类还没有被装载进内存,所以虚拟机并不知道所引用类的内存地址,所以就用符号引用来代替,而这个解析阶段就是为了把这个符号引用转化成为真正的地址
是否满足old代分配条件
Epsilon:无作用GC器,当你的程序不需要GC时可以选择使用该GC器
使用
安全点:无论时在GC中还是线程按权重都会出现安全点(Safepoint)这个概念,当我们需要阻塞一个线程时都需要在安全点停止,简单说安全点就是指当前线程运行到这类位置时,堆对象状态时确定一致的,当前线程停止后,JVM可以安全地进行操作,如GC、偏向锁撤离等。安全点定义:①循环结束的末尾段②方法调用之后③抛出异常的位置④方法返回之前当JVM需要发生GC、偏向锁撤离等操作时如何让所有线程达到安全点阻塞?①主动式中断(JVM采用的中断方式):不中断线程,而是设置一个标志然后让每个线程执行时主动轮询这个标志,当一个线程到达安全点后,发现中单标志位true时就自己中断挂起②抢断式中断:先中断所有线程,如果发现线程未执行到安全点则恢复线程让其运行到安全点位置。安全区域(SafeRegion):当一个线程处于中断或者休眠状态时就不能响应JVM的中断请求走到安全点区域挂起了,所以出现了安全区域的概念。按段区域是指一个线程执行到一段代码时,该区域的代码不会改变堆内存对象的引用,在这个区域内JVM可以安全地进行操作。当线程进入到该区域时需要先标识自己进入了,这样GC线程则不会管这些已标识的线程,当线程要离开这个区域时需要先判断GCRoots是否完成,如果完成了则往下执行,如果没有则需要原地等待到GC线程发出安全离开信息为止
G1承上启下:逻辑分代物理不分代
当前线程执行解释器代码
returnAddress
TLAB:Thread Local Allocation Buffer在Eden区为每个线程开辟的一个缓冲空间,线程在创建对象时如果该缓冲区的大小能够承载对象大小则直接在该区域分配对象,能够避免多个线程同时创建对象时由于竞争同一块堆内存时产生的资源消耗
线程共享区
GC算法
空闲列表:与指针碰撞一样,空闲列表同样时Java在为新对象分配堆内存时的一种内存分配方式,一般适用于CMS等一些会产生内存碎片、堆内存不完整的垃圾收集器分配过程:堆中的已用内存和空闲内存相互交错,JVM通过维护一张内存列表记录可用的空闲内存块信息,当创建新对象需要分配内存时,从列表中找到一个足够大的内存块分配给对象实例,并同步更新列表上的记录,当GC收集器发生GC时,也会将已回收的内存更新到内存列表.
热点探测
实例对象
垃圾回收算法
执行引擎(Execurtion engine)
执行<init>
JIT即时编译器字节码→JIT即时编译器(代码体积增大)→执行→结果
class对象
Application类加载器
0 条评论
下一页