JVM虚拟机

JVM虚拟机

Java在各个平台上是如何跑起来的?

我们都知道 Java 源文件,通过编译器,能够生产相应的.Class 文件,也就是字节码文件, 而字节码文件又通过 Java 虚拟机中的解释器,编译成特定机器上的机器码

不同平台上的解释器不同,但是虚拟机环境是相同的,这就是java为什么能在各个平台上运行的原因

JVM允许一个应用并发执行多个线程

JVM 内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区 域【JAVA 堆、方法区】、直接内存

JVM的多线程处理提高了程序的并行处理效率

JVM介绍

程序计数器(线程私有)

一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为线程私有的内存

正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址),如 果还是 Native 方法,则为空

这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域

程序计数器的作用是用于存储下一条指令(即java代码编译完成的字节码指令)的地址,方便虚拟机解释器找到这些指令并且翻译成机器码交给CPU执行

虚拟机栈(线程私有)

是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame) 用于存储局部变量表、操作数栈、动态链接、方法出口等信息

每一个方法从调用直至执行完成 的过程,就对应着一个栈帧在虚拟机栈入栈到出栈的过程

  • 栈帧随着方法调用而创建,随着方法结束而销毁

一个方法域都对应一个栈帧,每次方法执行结束,栈帧内存放的变量引用都会被GC机制回收,回收结束后,对应的强引用也会消失(不可达),便于GC机制继续回收那些不可达的对象占用的堆内存

JVM内存分布

本地方法区(线程私有)

本地方法栈则为 Native 方法服务,调用本地其它语言的方法,例如C++,C等

Java提供了本地调用需要实现的接口(JNI)Java Native Interface ,它允许Java代码和其他语言写的代码进行交互

堆(Heap-线程共享)-运行时数据区

是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行 垃圾收集的最重要的内存区域

堆内存通常存放new关键字创建的对象数据

堆内存分为 新生代 (Eden 区、From Survivor 区和 To Survivor 区)和 老年代

在了解堆内存内的分代回收机制前,先要了解下垃圾回收机制如何判断一个对象是否存活

  • 宣告一个对象的死亡一般要经历两次标记

第一次标记

如果对象在进行可达性分析后发现没有与GC Roots(对象引用关系链,相当于一个树结构)相连接的引用链, 那它将会被第一次标记,随后进行一次筛选

GCroot原理:通过对枚举GCroot对象做引用可达性分析

可以作为GC root对象的有:

  • 局部变量(Object obj = new Object() 中的obj)引用的对象
  • 静态属性引用的对象 (static修饰的属性)
  • 方法区常量引用的对象(static+final修饰的属性)
  • 本地方法栈中引用的对象
  • 新生代(8:1:1)
    • Eden区(伊甸区)
      • Java 新对象的出生地(new关键字创建的,如果新创建的对象占用内存很大,则直接分配到老年代),当 Eden 区内存不够的时候就会触发 MinorGC(复制算法),对新生代区进行 一次垃圾回收
    • ServivorFrom区(幸存者1区)
      • 存放上一次 GC 的幸存者,作为这一次 GC 的被扫描者
    • ServivorTo区(幸存者2区)
      • 保留了一次 MinorGC(复制算法) 过程中的幸存者

GC垃圾回收机制

GC机制在新生代中的运行过程

1、将Eden区和ServivorFrom区中的数据复制到ServivorTo区,对象年龄+1,如果有达到老年区对象标准的移至老年区(默认15次扫描后)

2、清空Eden区和ServivorFrom区数据

3、替换ServivorFrom区和ServivorTo区数据内容

在第一次标记的同时我们要明白GC机制还做了一件事情:

判断是否要执行finalize()方法

finalize()方法

finalize()是Object的protected方法

finalize()与C++中的析构函数不是对应的。C++中的析构函数调用的时机是确定的(对象离开作用域或delete掉),但Java中的finalize的调用具有不确定性

  • 如果需要执行,那么对象需要执行完finalize()方法才能被回收

  • 如果对象重写了finalize()方法,那么该对象将会被放置在一个F-Queue队列中,稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它

  • 对象如果没有重写finalize()方法,或者finalize()方法已经被虚拟机调用过,此时会被认
    定为没必要执行finalize()方法

对象可由两种状态,涉及到两类状态空间

一是终结状态空间 F = {unfinalized, finalizable, finalized}

二是可达状态空间 R = {reachable, finalizer-reachable, unreachable}

各状态含义如下:

unfinalized: 新建对象会先进入此状态,GC并未准备执行其finalize方法,因为该对象是可达的
finalizable: 表示GC可对该对象执行finalize方法,GC已检测到该对象不可达。正如前面所述,GC通过F-Queue队列和一专用线程完成finalize的执行
finalized: 表示GC已经对该对象执行过finalize方法
reachable: 表示GC Roots引用可达
finalizer-reachable(f-reachable):表示不是reachable,但可通过某个finalizable对象可达
unreachable:对象不可通过上面两种途径可达

第二次标记

第二次标记会为执行完finalize()方法的对象进行最后一次标记,之后销毁该对象内存

  • 若JVM检测到finalized状态的对象变成unreachable,回收其内存
  • 若对象并未覆盖finalize方法,JVM会进行优化,直接回收对象

老年代

新时代与老年代的内存比例为1:2

老年代的对象比较稳定,所以 MajorGC 不会频繁执行

MajorGC(采用标记复制回收策略)触发条件

  • 至少触发一次MinorGC(不是绝对,有策略可以选择),使得有新生代的对象晋身入老年代,导致老年代内存空间不够用时才触发MajorGC

频繁触发MajorGC的原因一般为:新生代内存不足以存放过大内存占用率的对象,或者是没有合理分配好新生代的对象内存

参考博客

方法区/永久代(线程共享)

  • 指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,类在被加载的时候被放入永久区域,它和和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理
  • 在 Java8 中,永久代已经被移除,被一个称为元数据区(方法区)的区域所取代

元空间并不在虚拟机中,而是使用本地内存,其大小仅受本地内存限制

G1 收集器

G1 收集器 Garbage first 垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与 CMS 收集器,G1 收 集器两个最突出的改进是:

  1. 基于标记-整理算法,不产生内存碎片
  2. 可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收

G1 收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域 的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾最多的区域,区域划分和优先级区域回收机制,确保 G1 收集器可以在有限时间获得最高的垃圾收集效率


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以邮件至 1300452403@qq.com

文章标题:JVM虚拟机

字数:2.2k

本文作者:Os467

发布时间:2022-08-24, 18:28:03

最后更新:2022-09-05, 00:08:45

原始链接:https://os467.github.io/2022/08/24/JVM%E8%99%9A%E6%8B%9F%E6%9C%BA/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

×

喜欢就点赞,疼爱就打赏