JVM内存模型
2024-05-16 17:16:28 22 举报
JVM全知识点梳理
作者其他创作
大纲/内容
ParNew
字节码生成器
栈帧1(main)
新生代,复制算法
复制算法(Copying)
GC Root Set
本机直接内存溢出:直接内存的容量可以通过 MaxDirectMemorySize 来设置(默认与堆内存最大值一样),所以也会出现 OOM 异常;由直接内存导致的内存溢出,一个比较明显的特征是在 HeapDump 文件中不会看见有什么明显的异常情况,如果发生了 OOM,同时 Dump 文件很小,可以考虑重点排查下直接内存方面的原因。
程序计数器
4
本身是 Server 级别多 CPU机器上的默认 GC 方式,主要适合后台运算不需要太多交互的任务;
解析Resolution
执行方法
动态链接
标记-整理(Mark-Compact)
注解抽象语法树
句柄:如果使用句柄访问的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。 使用句柄来访问的最大好处就是 reference 中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而 reference 本身不需要修改.直接指针:如果使用直接指针访问, reference 中存储的直接就是对象地址。这两种对象访问方式各有优势,使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在 Java 中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。对 Sun HotSpot 而言,它是使用直接指针访问方式进行对象访问的
本地方法栈变量
A
用户线程
safePoint
并行的多线程收集器
面向服务端应用的垃圾回收器,目标为取代 CMS
存活对象
RSet
回边计数器/方法调用计数器加1
X
向下尝试是否可加载
执行引擎Excution Engine
重置线程
三色标记
新生代
后台执行编译
类加载检查
HumongOus
7
并行与并发收集器
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半。 但是要注意:内存移动是必须实打实的移动(复制),所以对应的引用(直接指针)需要调整。 复制回收算法适合于新生代,因为大部分对象朝生夕死,那么复制过去的对象比较少,效率自然就高,另外一半的一次性清理是很快的。
不相等则重新获取
软引用 SoftReference系统将要发生内存溢出(OuyOfMemory)之前,这些对象就会被回收
CodeCache
标记-复制(Mark-Copying)
Class常量池:在 class 文件中除了有类的版本、字段、方法和接口等描述信息外,还有一项信息是常量池 (Constant Pool Table),用于存放编译期间生成的各种字面量和符号引用。字面量:给基本类型变量赋值的方式就叫做字面量或者字面值。符号引用 :符号引用以一组符号来描述所引用的目标。符号引用可以是任何形式的字面量,JAVA 在编译的时候一个每个 java 类都会被编译成一个 class文件,但在编译的时候虚拟机并不知道所引用类的地址(实际地址),就用符号引用来代替,而在类的解析阶段就是为了把这个符号引用转化成为真正的地址的阶段。
栈(Stack)
单线程
Parallel Old
C
MarkWord
读取当前内存位置
2
加载jre/lib/ext/*.jar,或者由-Djava.ext.dirs指定
类似 ParNew,更加关注吞吐量,达到一个可控制的吞吐量;
跨新生代和老年代;标记整理 + 化整为零
方法出口
操作数栈
3
Object
装载使用的类
Region
向上询问是否已经加载
8大基本数据类型
虚拟机内存优化技术
前后两个栈帧共享一部分区域来传递参数
交换复制
是
适用场景
三色标记漏标问题
筛选回收
并发标记
分为“标记”和“清除”两个阶段:首先扫描所有对象标记出需要回收的对象,在标记完成后扫描回收所有被标记的对象,所以需要扫描两遍。回收效率略低,如果大部分对象是朝生夕死,那么回收效率降低,因为需要大量标记对象和回收对象,对比复制回收效率要低。 它的主要问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作。 回收的时候如果需要回收的对象越多,需要做的标记和清除的工作越多, 所以标记清除算法适用于老年代。
8
F
概念:较小的内存空间,当前线程执行的字节码的行号指示器;各线程之间独立存储,互不影响。 程序计数器是一块很小的内存空间,当执行的线程数量超过 CPU 核数时,线程之间会根据时间片轮询争夺 CPU 资源。主要用来记录各个线程执行的字节码的地址,例如,分支、循环、跳转、异常、线程恢复等都依赖于计数器。如果是遇到本地方法(native 方法),这个方法不是 JVM 来具体执行,所以程序计数器不需要记录了,这个是因为在操作系统层面也有一个程序计数器,这个会记录本地代码的执行的地址,所以在执行 native 方法时,JVM 中程序计数器的值为空(Undefined)。 另外程序计数器也是 JVM 中唯一不会 OOM(OutOfMemory)的内存区域。
Serial Old
初始标记
堆(Heap)
解析:解析阶段是 JVM 将常量池内的符号引用替换为直接引用的过程。 符号引用是一种定义,可以是任何字面上的含义,而直接引用就是直接指向目标的指针、相对偏移量。
暂停所有用户线程
安全点与安全区域
方法表
设置
对齐填充8字节倍数(非必须)
自定义加载器CustomLoader
直接内存
✔
跨代引用
重新标记
标记-清除(Mark-Sweep)
Old
Javac编译器(源码级编译器)
调用方栈帧
局部变量表
×
句柄池
失败
读取当前值old
方法出口(返回地址):正常返回:(调用程序计数器中的地址作为返回) 三步曲: 1.恢复上层方法的局部变量表和操作数栈 2.把返回值(如果有的话)压入调用者栈帧的操作数栈中 3.调整程序计数器的值以指向方法调用指令后面的一条指令异常的返回:通过异常处理表<非栈帧中的>来确定
如果 Java 堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”。 选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。 如果是 Serial、ParNew 等带有压缩的整理的垃圾回收器的话,系统采用的是指针碰撞,既简单又高效。、 如果是使用 CMS 这种不带压缩(整理)的垃圾回收器的话,理论上只能采用较复杂的空闲列表。
Parallel Scavenge吞吐量优先收集器
card[2]
本地方法库Native Library
GCRoots
栈增长方向
重视服务的响应速度、系统停顿时间和用户体验的互联网网站或者 B/S 系统。互联网后端目前 cms 是主流的垃圾回收器;
本地方法栈:跟 Java 虚拟机栈的功能类似,Java 虚拟机栈用于管理 Java 函数的调用,而本地方法栈则用于管理本地方法的调用。但本地方法不是Java 实现的,而是由 C 语言实现的(比如 Object.hashcode 方法)。 本地方法栈是和虚拟机栈非常相似的一个区域,它服务的对象是 native 方法。你甚至可以认为虚拟机栈和本地方法栈是同一个区域。 虚拟机规范无强制规定,各版本虚拟机自由实现 ,HotSpot 直接把本地方法栈和虚拟机栈合二为一 。
方法区常量
老年代,标记整理算法
运行时常量池
b style=\"text-decoration-line: underline;\
栈帧信息
1)大对象直接进入老年代2)长期存活的对象age到MaxTenuringThreshold(可设 置最大为15)进入老年代3)survivor区中相同年龄所有对像大小总和大于survivor区的一半的时候,大于等于该年龄的所有对象直接进入老年代
可达性分析算法
验证Verification
并发安全
栈帧N(method)
reference
类变量
B
实例数据
TLAB(线程私有)
字符串常量池
Client 模式下虚拟机使用
虚拟机栈
Eden(8/10)
本地方法栈
并行:垃圾收集的多线程的同时进行。并发:垃圾收集的多线程和应用的多线程同时进行。注:吞吐量=运行用户代码时间/(运行用户代码时间+ 垃圾收集时间)垃圾收集时间= 垃圾回收频率 *
Y
老年代
字节码解释器
SurvivorTo(1/10)
堆内存:堆是 JVM 上最大的内存区域,我们申请的几乎所有的对象,都是在这里存储的。我们常说的垃圾回收,操作的对象就是堆。堆空间一般是程序启动时,就申请了,但是并不一定会全部使用。堆一般设置成可伸缩的。随着对象的频繁创建,堆空间占用的越来越多,就需要不定期的对不再使用的对象进行回收。这个在 Java 中,就叫作 GC(Garbage Collection)。那一个对象创建的时候,到底是在堆上分配,还是在栈上分配呢?这和两个方面有关:对象的类型和在 Java 类中存在的位置。Java 的对象可以分为基本数据类型和普通对象。对于普通对象来说,JVM 会首先在堆上创建对象,然后在其他地方使用的其实是它的引用。比如,把这个引用保存在虚拟机栈的局部变量表中。对于基本数据类型来说(byte、short、int、long、float、double、char),有两种情况。当你在方法体内声明了基本数据类型的对象,它就会在栈上直接分配。其他情况,都是在堆上分配。
1. JVM 第一步就是通过配置参数或者默认配置参数向操作系统申请内存空间。2. JVM 获得内存空间后,会根据配置参数分配堆、栈以及方法区的内存大小。3. JVM 首先会执行构造器,编译器会在.java 文件被编译成.class 文件时,收集所有类的初始化代码,包括静态变量赋值语句、静态代码块、静态方法,静态变量和常量放入方法区
1
即时编译结束
已编译
老年代Tenured(2/3MajorGC)
G1
标记-整理算法(Mark-Compact)
Survivor
运行时数据区的初始化
四种引用
收集类型
Class文件(JVM字节码)
数组
方法区(MetaSpace)
扩展类加载器ExtClassLoader
解释器(Interpreter),又译为直译器,是一种电脑程序,能够把高级编程语言一行一行直接转译运行。解释器不会一次把整个程序转译出来,只像一位“中间人”,每次运行程序时都要先转成另一种语言再作运行,因此解释器的程序运行速度比较缓慢。它每转译一行程序叙述就立刻运行,然后再转译下一行,再运行,如此不停地进行下去。
随着 JVM 中内存的增大,STW 的时间成为 JVM 急迫解决的问题,但是如果按照传统的分代模型,总跳不出 STW 时间不可预测这点。为了实现 STW 的时间可预测,首先要有一个思想上的改变。G1 将堆内存“化整为零”,将堆内存划分成多个大小相等独立区域(Region),每一个 Region都可以根据需要,扮演新生代的 Eden 空间、Survivor 空间,或者老年代空间。回收器能够对扮演不同角色的 Region 采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果 G1 的运作过程大致可划分为以下四个步骤: 1 初始标记( Initial Marking)仅仅只是标记一下 GC Roots 能直接关联到的对象,并且修改 TAMS 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的 Region 中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行 Minor GC 的时候同步完成的,所以 G1 收集器在这个阶段实际并没有额外的停顿。 2 并发标记( Concurrent Marking)从 GC Root 开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后 ,并发时有引用变动的对象 ,这些对象会漏标,漏 标 的 对 象会被SATB(snapshot-at-the-beginning)算法来解决 3 最终标记( Final Marking) 对用户线程做另一个短暂的暂停,用于处理并发阶段结后仍遗留下来的最后那少量的 SATB 记录(漏标对象)。 4 筛选回收( Live Data Counting and Evacuation)负责更新 Region 的统计数据,对各个 Region 的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个 Region 构成回收集,然后把决定回收的那一部分 Region 的存活对象复制到空的 Region 中,再清理掉整个旧 Region 的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。TAMS 是什么?要达到 GC 与用户线程并发运行,必须要解决回收过程中新对象的分配,所以 G1 为每一个 Region 区域设计了两个名为 TAMS(Top at Mark Start)的指针,从 Region区域划出一部分空间用于记录并发回收过程中的新对象。这样的对象认为它们是存活的,不纳入垃圾回收范围。特点:并行与并发:G1 能充分利用多 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿的时间,部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 Java 程序继续执行。分代收集:与其他收集器一样,分代概念在 G1 中依然得以保留。虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次 GC 的旧对象以获取更好的收集效果。空间整合:与 CMS 的“标记—清理”算法不同,G1 从整体来看是基于“标记—整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着 G1 运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次 GC。追求停顿时间:-XX:MaxGCPauseMillis 指定目标的最大停顿时间,G1 尝试调整新生代和老年代的比例,堆大小,晋升年龄来达到这个目标时间。适合的内存大小:该垃圾回收器适合回收堆空间上百 G。一般在 G1 和 CMS 中间选择的话平衡点在 6~8G,只有内存比较大 G1 才能发挥优势。
首先标记出所有需要回收的对象,在标记完成后,后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。标记整理算法虽然没有内存碎片,但是效率偏低。 我们看到标记整理与标记清除算法的区别主要在于对象的移动。对象移动不单单会加重系统负担,同时需要全程暂停用户线程才能进行,同时所有引用对象的地方都需要更新(直接指针需要调整)。 所以看到,老年代采用的标记整理算法与标记清除算法,各有优点,各有缺点。
操作数据栈:存放我们方法执行的操作数的,它就是一个栈,先进后出的栈结构,操作数栈,就是用来操作的,操作的的元素可以是任意的 java 数据类型,所以我们知道一个方法刚刚开始的时候,这个方法的操作数栈就是空的,操作数栈运行方法是会一直运行入栈/出栈的操作
JIT
内存溢出
JVM内部引用
如果 Java 堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”。
加载classpath指定内容
CardTable
D
方法区(Method Area):是可供各条线程共享的运行时内存区域。它存储了每一个类的结构信息,例如运行时常量池(Runtime Constant Pool) 字段和方法数据、构造函数和普通方法的字节码内容、还包括一些在类、实例、接口初始化时用到的特殊方法方法区是 JVM 对内存的“逻辑划分”,在 JDK1.7 及之前很多开发者都习惯将方法区称为“永久代”,是因为在HotSpot 虚拟机中,设计人员使用了永久代来实现了 JVM 规范的方法区。在 JDK1.8 及以后使用了元空间来实现方法区。
判断对象的存活
Garbage First(G1)
语法树/抽象语法树
内存分配方式
Eden
card[0]
G1:在 G1 中是每 一个 Region 都需要一个 RSet 的内存区域,导致有 G1 的 RSet 可能会占据整个堆容量的 20%乃至更多。CMS:CMS的 RSet 只需要一份,所以就内存占用来说,G1占用的内存需求更大。虽然G1的优点很多,但是不推荐在堆空间比较小的情况下使用G1,尤其小于6个G。
数组长度
栈中变量
功能: 1.以栈帧的方式存储方法调用的过程,并存储方法调用过程中基本数据类型的变量(int、short、long、byte、float、double、boolean、char 等)以及对象的引用变量,其内存分配在栈上,变量出了作用域就会自动释放; 2.而堆内存用来存储 Java 中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中;线程独享还是共享: 1.栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。 2.堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。 空间大小: 栈的内存要远远小于堆内存
Token流
5
本地接口Native Interface
C2编译器:C2 编译器是为长期运行的服务器端应用程序做性能调优的编译器,适用于执行时间较长或对峰值性能有要求的程序。C2 也被称为 Server Compiler
安全区域:当用户线程执行到安全区域里面的代码时,首先会标识自己已经进入了安全区域,这段时间里 JVM 要发起 GC 就不必去管这个线程了。 当线程要离开安全区域时,它要 JVM 是否已经完成了(根节点枚举,或者其他 GC 中需要暂停用户线程的阶段) 1、如果完成了,那线程就当作没事发生过,继续执行。 2、否则它就必须一直等待, 直到收到可以离开安全区域的信号为止。为什么需要安全区域? 要是业务线程都不执行(业务线程处于 Sleep 或者是 Blocked 状态),那么程序就没办法进入安全点,对于这种情况,就必须引入安全区域。 安全区域是指能够确保在某一段代码片段之中, 引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。我们也可以把安全区 城看作被扩展拉伸了的安全点。
栈溢出:HotSpot 版本中栈的大小是固定的,是不支持拓展的。java.lang.StackOverflowError 一般的方法调用是很难出现的,如果出现了可能会是无限递归。虚拟机栈带给我们的启示:方法的执行因为要打包成栈桢,所以天生要比实现同样功能的循环慢,所以树的遍历算法中:递归和非递归(循环来实现)都有存在的意义。递归代码简洁,非递归代码复杂但是速度较快。OutOfMemoryError:不断建立线程,JVM 申请栈内存,机器没有足够的内存。(一般演示不出,演示出来机器也死了)同时要注意,栈区的空间 JVM 没有办法去限制的,因为 JVM 在运行过程中会有线程不断的运行,没办法限制,所以只限制单个虚拟机栈的大小。
创建对象
G
线程A准备离开安全区
动态连接:Java 语言特性多态(需要类加载、运行时才能确定具体的方法)动态分派
E
收集对象和算法
JIT(即时编译)
指向class实例的引用
新生代(1/3MinorGC)
连接/linking
最终标记
C1编译器:C1编译器是一个简单快速的编译器,主要的关注点在于局部性的优化,适用于执行时间较短或对启动性能有要求的程序。C1 也被称为 Client Compiler
跨代引用:堆空间通常被划分为新生代和老年代。由于新生代的垃圾收集通常很频繁,如果老年代对象引用了新生代的对象,那么回收新生代的话,需要跟踪从老年代到新生代的所有引用,所以要避免每次YGC时扫描整个老年代,减少开销。CardTable:由于做font color=\"#ff0000\
继续运行
结束
对象初始化
初始化主要是对一个 class 中的 static{}语句进行操作(对应字节码就是 clinit 方法)。
CMS
执行编译后的机器码
线程A标识已进入安全区
垃圾回收
Java源文件
虚拟机栈的作用:在 JVM 运行过程中存储当前线程运行方法所需的数据,指 令、返回地址。栈的数据结构:先进后出(FILO)的数据结构虚拟机栈是基于线程的:哪怕你只有一个 main() 方法,也是以线程的方式运行的。在线程的生命周期中,参与计算的数据会频繁地入栈和出栈,栈的生命周期是和线程一样的。虚拟机栈的大小缺省为 1M,可用参数 –Xss 调整大小,例如-Xss256k。栈帧:在每个 Java 方法被调用的时候,都会创建一个栈帧,并入栈。一旦方法完成相应的调用,则出栈。
运行时常量池(Runtime Constant Pool):是每一个类或接口的常量池(Constant_Pool)的运行时表示形式,它包括了若干种不同的常量:从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。运行时常量池是方法区的一部分。运行时常量池相对于 Class 常量池的另外一个重要特征是具备动态性
被调用方栈帧
垃圾回收算法
标记-清除算法(Mark-Sweep)
语法分析器
引用不可达将被回收
在注重吞吐量以及 CPU 资源敏感的场合采用
字段信息
常驻异常对象
指针碰撞
局部变量表顾:名思义就是局部变量的表,用于存放我们的局部变量的。首先它是一个 32 位的长度,主要存放我们的 Java 的八大基础数据类型,一般 32位就可以存放下,如果是 64 位的就使用高低位占用两个也可以存放下,如果是局部的一些对象,比如我们的 Object 对象,我们只需要存放它的一个引用地址即可。(基本数据类型、对象引用、returnAddress 类型)
可回收对象
可达性分析
方法区溢出:(1) 运行时常量池溢出(2)方法区中保存的 Class 对象没有被及时回收掉或者 Class 信息占用的内存超过了我们配置。注意 Class 要被回收,条件比较苛刻(仅仅是可以,不代表必然,因为还有一些参数可以进行控制): 1、该类所有的实例都已经被回收,也就是堆中不存在该类的任何实例。 2、 加载该类的 ClassLoader 已经被回收。 3、 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
检查虚拟机是否完成根结点枚举
运行时数据区的定义:Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域
垃圾回收器
CAS机制:对分配内存空间的动作进行同步处理——实际上虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性
SurvivorFrom(1/10)
解决内存分配并发安全问题
Y引用
age=15
栈帧之间数据的共享:在一般的模型中,两个不同的栈帧的内存区域是独立的,但是大部分的 JVM 在实现中会进行一些优化,使得两个栈帧出现一部分重叠。(主要体现在方法中有参数传递的情况),让下面栈帧的操作数栈和上面栈帧的部分局部变量重叠在一起,这样做不但节约了一部分空间,更加重要的是在进行方法调用时就可以直接公用一部分数据,无需进行额外的参数复制传递了。
线程A准备进入安全区
方法信息
X引用
GC线程不再管已标识在安全区的线程
准备:准备阶段是正式为类中定义的变量(被 static 修饰的变量)分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。
CAS比较old值与当前值是否相等
运行时数据区
并发清理
深入辨析堆和栈
语义分析器
Serial/Serial Old
内存初始化
卸载unloading
类加载
安全区
提交编译请求
Java方法入口
方法区静态变量
直接指针
老年代,标记清除算法
系统类加载器
JVM 向操作系统申请内存
预处理
否:解释执行
应用类加载器AppClassLoader
线程私有内存区域
说明
尽可能的缩短垃圾收集时用户线程停止时间;缺点于:1.内存碎片2.需要更多 cpu 资源3.浮动垃圾问题,需要更大的堆空间
Serial
对象的内存布局
类型指针
准备Preparation
字节码校验
入栈
Concurrent Mark Sweep (CMS)
计数是否超过阈值
JDK1.7 才正式引入,采用分区回收的思维,基本不牺牲吞吐量的前提下完成低停顿的内存回收;可预测的停顿是其最大的优势;
JVM内存区域
黑色:根对象,或者该对象与它的子对象都被扫描过。灰色:对本身被扫描,但是还没扫描完该对象的子对象。白色:未被扫描对象,如果扫描完所有对象之后,最终为白色的为不可达对象,既垃圾对象。
弱引用 WeakReference用弱引用关联的对象,只能生存到下一次垃圾回收之前,GC 发生时,不管内存够不够,都会被回收。
解释执行
自定义的ClassLoader指定内容
收集器
card[1]
词法分析器
ParNew 垃 圾 收 集 器 是Serial 收集器的多线程版本
使用using
指针
虚引用 PhantomReference幽灵引用,最弱(随时会被回收掉)
同步锁持有者
初始化Initialization
指向类加载器的引用
相等则分配成功
强引用Object obj = new Object() ,就属于强引用。在任何情况下,只有有强引用关联(与根可达)还在,垃圾回收器就永远不会回收掉被引用的对象
6
Parallel Scavenge 收集器老年代版本,为了配合Parallel Scavenge 的面向吞吐量的特性而开发的对应组合;
栈帧2(method)
热点代码
类元信息
初始化/initialization
ParallelScavenge
JVM运行流程
多线程
启动类加载器Bootstrap ClassLoader
加载/loading(双亲委派模型)
类加载器系统(ClassLoader system)
类型的信息
线程共享内存区域
Class的常量池
验证:是连接阶段的第一步,这一阶段的目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。但从整体上看,验证阶段大致上会完成下面 4 个阶段的检验动作:文件格式验证、元数据验证、字节码验证、符号引用验证。
returnAddress
空闲列表
引用计数法
对象头
new()
出栈
分配内存
加载lib/*.jar(包括rt.jar、jce.jar等)
垃圾回收(GC)
对象创建过程
0 条评论
下一页