济南网站建设免费,个人简介ppt模板,网络推广平台排行榜,网站怎么添加百度商桥文章目录 内存布局直接内存执行引擎解释器JIT 即时编译器JIT 分类AOT 静态提前编译器#xff08;Ahead Of Time Compiler#xff09; GC什么是垃圾为什么要GC垃圾回收行为Java GC 主要关注的区域对象的 finalization 机制GC 相关算法引用计数算法#xff08;Reference Count… 文章目录 内存布局直接内存执行引擎解释器JIT 即时编译器JIT 分类AOT 静态提前编译器Ahead Of Time Compiler GC什么是垃圾为什么要GC垃圾回收行为Java GC 主要关注的区域对象的 finalization 机制GC 相关算法引用计数算法Reference Counting可达性分析算法GC Roots Stop The World标记-清除算法mark-sweep复制算法Copying标记-压缩标记-整理算法 分代垃圾回收增量收集算法分区算法 内存布局
对象头Header 运行时元数据Mark word哈希值、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳类型指针 指向类元数据确定对象所属类型。如果是数组还要记录数组的长度 实例数据Instance Data类中的各类型变量以及父类的相关类型数据。对齐填充Padding非必须也没有特殊含义仅起到占位符的作用
直接内存
直接内存不是JVM 运行时数据区的一部分是 Java 堆外的系统的内存区间。NIO 通过存在堆中的 DirectByteBuffer 操作 Native 内存。通常情况下直接内存的运行效率优于 Java 堆读写频繁的场合如果NIO 库就允许 Java 程序使用直接内存。 直接内存在 Java 堆外因此是不受 -Xmx 的限制的但操作系统内存是有限的 -XX:MaxDirectMemorySize1G表示设置 NIO 可以操作的直接内存最大大小为 1G默认为 0表示 JVM 自动选择 NIO 可以操作的直接内存大小。 直接内存也会 OOM。 执行引擎 执行引擎Execution Engine将字节码指令解释/编译注意与 Java 文件编译为 .class 文件的编译区分有的地方称之为后端编译为对应平台上的本地机器指令实际就是将高级编程语言翻译为机器指令。 解释器 当 Java 虚拟机启动时会根据预定义的规范对字节码采用逐行解释的方式执行将每条字节码文件内容“翻译”为对应平台的本地机器指令。此过程由解释器执行。 字节码解释器纯软件代码模拟字节码执行效率低下。模板解释器每一条字节码和一个模板函数相关联模板函数直接产生这条字节码执行时的机器码效率高。 基于解释器来执行还是相对低效的所以又有了 JIT 即时编译器。 JIT 即时编译器 JITJust In Time Compiler即时编译器就是 JVM 将源代码直接编译成和本地机器相关的机器语言。 根据代码被调用的执行频率来确定是否需要启动 JIT 来进行编译这些需要被 JIT 编译为本地指令的代码称为“热点代码”JIT 在运行时会针对这些热点代码做深度优化以提高 Java 程序的性能。栈上替换一个多次被调用的方法或者一个方法体内部的循环次数较多的循环体都可以称为“热点代码”他们都可以通过 JIT 编译为本地机器指令。由于此过程发生在方法执行过程中因此也称为栈上替换OSR 编译On Stack Replacement。热点探测功能HotSpot 基于计数器的方式来进行热点探测热点代码被调用的次数。Client 模式 1500次Server 模式 10000 次才是热点代码才切换为 JIT 编译。指令缓存JIT 编译为本机指令代码后会进行代码缓存以提高性能。 此外我们还可以通过 java 命令设置让程序使用纯解释器或纯 JIT 编译器执行或者两者的混合执行的模式。 -Xint 纯解释器模式-Xcomp 纯编译器模式-Xmixed 混合模式默认
JIT 分类
JIT 分为如下两类编译器
C1Client Compiler编译器运行在 -client 模式下C1 编译器会对字节码进行简单和可靠的优化耗时短。 方法内联去虚拟化冗余消除 C2Server Compiler编译器运行在 -server 模式下C2 进行耗时较长的优化以及激进优化。但优化后的代码执行效率高。 标量替换栈上分配同步消除 Graal 编译器JDK 10 才加入的全新的即时编译器。
AOT 静态提前编译器Ahead Of Time Compiler JIT 是程序运行中执行的AOT 编译是在程序运行之前执行将字节码转换为机器码的过程。好处预编译.class 文件为 .os 文件 GC
什么是垃圾 垃圾是指在运行程序中没有任何指针指向的对象。如果不及时堆垃圾进行清理这些垃圾会一直占用内存空间直到程序运行结束在这期间这些对象所占用的内存无法使用造成极大的资源浪费。 为什么要GC 如果不 GC 内存迟早会消耗完导致新的对象无法分配内存所以没有 GC 程序就可能无法正常执行。 垃圾回收行为
手动GCC / C 需要开发人员手动进行内存的申请和回收这样做的好处是灵活但坏处是操作太频繁而且对开发人员的要求较高相对增加了开发人员的负担。自动GCJava 采用的是自动 GC 的机制。自动管理内存无需开发人员手动分配和回收。坏处是弱化了开发人员对内存的管理只能通过监控和调节相关参数来优化。
Java GC 主要关注的区域
方法区注方法区对应永久代或元空间很多 JVM 没有方法区的 GC堆区 Java GC 的主要特点为频繁收集 Young 区年轻代较少收集 Old 区老年代基本不收集 Perm 区老年代、元空间 对象的 finalization 机制 当垃圾回收器对垃圾对象进行垃圾回收之前会先调用该对象的 finalize 方法该方法在 Object 类中定义可以被重写主要用于在对象被回收时进行资源释放。我们通常在此方法中进行一些资源释放的操作和清理的操作比如File 、IO 操作的关闭、Socket 操作的关闭、数据库连接的关闭等等。 注不要在程序中主动调用 finalize 方法该方法仅提供给垃圾回收器调用
在 finalize 时可能导致对象复活finalize 方法执行时间是没有保障的它有 GC 线程决定若不发生 GC则不会执行。GC 时调用 finalize 方法是由单独的优先级较低一点的线程Finalizer来执行。执行前放在执行队列中因为 GC 是有多个对象的 finalize 方法需要调用finalize 方法还可能导致 GC 失败所以在重写该方法时要注意执行效率等。
由于 finalize 方法对象可能会出现如下几个状态
可触及该对象可达。可达性算法分析可复活对象不可达但对象可能调用 finalize 方法后复活。在 finalize 方法中使当前对象跟引用链中任何一个对象建立联系就会导致对象复活。但之后再次不可用则不会调用 finalize 方法了。finalize 方法只调用一次不可触及finalize 方法被调用成功且对象没有复活。只有此状态的对象才可不垃圾回收。
GC 相关算法 GC 分为两个阶段标记阶段和清除阶段每个阶段都有对应的算法这些算法可以统称为垃圾回收算法。 标记阶段判断对象是否存活。其对应的算法有引用计数算法和可达性分析算法清除阶段在判断对象释放存活之后GC 接下来就会对死亡的对象进行垃圾回收释放内存空间。目前常用的算法有标记-清除算法mark-sweep、复制算法Copying、标记-压缩算法mark-Compact
引用计数算法Reference Counting 每个对象保存一个整型引用计数器记录该对象被引用的情况。只要有任何一个地方引用了该对象则该对象的计数器值 1 如果不再引用了则计数器值 -1只要该对象的引用计数器的值为 0则表示该对象死亡可以被 GC 回收。 优点实现简单垃圾对象判断简单判断效率高回收没有延迟性。缺点 需要独立的字段存储计数器增加内存开销任何引用的变动都需要更新计数器的值无法处理循环引用这是个致命的问题所以目前的 JVM 中都没有使用此算法了
可达性分析算法 可达性分析算法又叫根搜索算法或跟踪性垃圾收集相对引用计数算法而言可达性分析算法不仅同样具备实现简单和执行高效等特点而且可以有效的解决循环引用问题防止内存泄漏的问题发生。Java 就是选择的可达性分析算法。 可达性分析算法是以根对象集合GC Roots为起始点按照从上到下的方式搜索被根对象集合所连接的目标对象是否可达。使用可达性分析算法后内存中的存活对象都会被根对象集合直接或间接连接着搜索走过的路径称为引用链Reference Chain如果目标对象没有任何引用链相连则对象释不可达的可以标记为垃圾对象。只有直接或间接被根对象连接的对象才是存活对象。
GC Roots
GC Roots 包含如下几类元素
JVM 中引用的对象 如各个线程被调用的方法中使用的参数、局部变量等。 本地方法栈内 JNI 本地方法引用的对象方法区中静态属性引用的对象 如对象类型的静态变量 方法区中常量引用的对象 如字符串常量池中的引用对象 被同步锁 synchronized 持有的对象JVM 内部的引用 如系统类加载器、Class 对象等 反应 JVM 内部情况的 JMXBean 、JVMTI 中的注册回调、本地代码缓存等。
Stop The World 如果要使用可达性分析算法来判断内存是否可以回收那么分析工作必须在一个能保障一致性的快照中进行否则分析结果无法保证完全正确。所以在 JVM 在进行 GC 时必须 “Stop The World” 用户线程出现停顿。 标记-清除算法mark-sweep 标记-清除算法就分为标记和清除两个阶段 标记收集器从根节点开始遍历标记所有被引用的对象注意这里是标记的可用对象标记的内容标记在对象头 Header 中清除收集器从堆内存从头到尾的线性遍历如果发现某个对象没有标记为可用对象则将其回收。 标记-清除算法简单明了但在进行 GC 的时候需要停止整个应用程序而且清理出来的内存空间是不连续的容易产生内存碎。可能导致可用空间碎片化不能整体存放大对象。而且该算法需要经历标记-清除两步意味着需要两次遍历对象。 标记-清除算法的清除并不是真的清除只是将可回收的对象的地址进行记录放在空闲列表当有新对象来的时候进行覆盖。 复制算法Copying 将内存空间分为两块每次只使用其中一块在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中之后清除正在使用的内存块中的所有对象然后交换两个内存块的角色最后完成垃圾回收。这里是不是想起了什么我们的两个幸存者区就是使用的该方式 优点一次遍历更高效复制后内存连续没有碎片化问题。 缺点需要两份内存空间且任何时刻都有一个空间不使用而且对象需要复制当存活对象很多的情况下复制所占用的系统资源也不少。对于存活对象不多的情况比较适用。想想为什么在年轻代中的幸存者区使用该算法
标记-压缩标记-整理算法 在年轻代中一般只有少部分对象存活使用复制算法可以有效利用复制算法的优点但在老年代中存活对象更多采用复制算法就会放大其缺点所以 JVM 的开发者在标记-清除算法的基础上改进形成了标记-压缩算法。 标记-压缩算法也是分为两步
第一阶段和标记-清除算法一样标记所有被引用的对象。将所有存活的对象压缩到内存的一端按顺序排放然后清理空间。
优点解决了标记-清除算法的碎片化问题消除了复制算法的内存减半的代价。缺点效率上来说还是低于复制算法甚至低于标记-清除算法。对比标记-清除算法标记-压缩算法有对象的移动对应的引用地址就会涉及修改等。
标记-压缩算法的开销
标记mark阶段的开销与存活对象的数量成正比。清除sweep阶段的开销与所管理的区域大小成正比。压缩Compact阶段的开销与存活对象的数量成正比
分代垃圾回收
年轻代区域相对老年代较小对象生命周期短存活率低回收频繁。基于此特点采用了复制算法进行垃圾回收速度快效率高而且年轻代中两个幸存者区就是为了利用复制算法来划分的。老年代区域相对较大对象存活率高生命周期较长回收不及年轻代频繁。基于此特点采用的是标记-清除算法和标记-压缩算法混合使用的方式实现的垃圾回收。
增量收集算法 标记-清除、标记-压缩、复制算法都或多或少的会有 stop the world 的出现如果垃圾收集时间过长应用程序会被挂起时间过长影响用户体验。基于此情况又诞生了增量收集算法。 如果一次性进行垃圾收集将所有的垃圾进行处理可能导致时间过长所以增量收集算法就采用了垃圾收集线程和应用程序线程交替执行的思想垃圾收集线程每次执行只收集一小片区域的内存空间然后切换到应用程序线程执行反复执行直到垃圾收集完成。其本质上还是我们上面说到的垃圾收集算法只是将垃圾收集和应用程序线程交替执行以减少 stop the world 的时间 优点减少了 stop the world 的时间缺点交替执行线程因为线程和线程上下文的切换的消耗会使得垃圾回收的总成本上升造成系统吞吐量下降。
分区算法 分区算法将堆空间划分为更小的区间分代算法按照对象的生命周期长短划分成两个部分分区算法将整个堆空间划分成连续的不同区域。每一个区域都独立使用独立回收。这样一次回收多个小空间降低了 stop the World 的时间。