我对JVM的认知总结[笔记]

JVM是可运行Java代码的假想计算机 ,包括一套字节码指令集、一组寄存器、一个栈、
一个垃圾回收,堆 和 一个存储方法域。
JVM 是运行在操作系统之上的,它与硬件没有直接
的交互

JVM的运行过程

JAVA源文件->编译器->class字节码->JVM->机器码

java虚拟机的多平台运行

每一种平台的解释器是不同的,但是实现的虚拟机是相同的,这也就是 Java 为什么能够
跨平台的原因了 ,当一个程序从开始运行,这时虚拟机就开始实例化了,多个程序启动就会
存在多个虚拟机实例。程序退出或者关闭,则虚拟机实例消亡,多个虚拟机实例之间数据不
能共享

线程是一个什么东东

线程指程序执行过程中的一个线程实体。JVM 允许一个应用并发执行多个线程。
Hotspot JVM 中的 Java 线程与原生操作系统线程有直接的映射关系。当线程本地存储、缓
冲区分配、同步对象、栈、程序计数器等准备好以后,就会创建一个操作系统原生线程

Java 线程结束,原生线程随之被回收。操作系统负责调度所有线程,并把它们分配到任何可
用的 CPU 上。当原生线程初始化完毕,就会调用 Java 线程的 run() 方法。当线程结束时,会释放原生线程和 Java 线程的所有资源。

java 类加载过程?

加载->验证->准备->解析->初始化->使用->卸载

  • 加载(加载时类加载的第一个过程,在这个阶段,将完成一下三件事情):
  1. 通过一个类的全限定名获取该类的二进制流。
  2. 将该二进制流中的静态存储结构转化为方法区运行时数据结构。
  3. 在内存中生成该类的 Class 对象,作为该类的数据访问入口。
  • 验证(验证的目的是为了确保 Class 文件的字节流中的信息不会危害到虚拟机;在该阶段主要完成以下四钟验证)
  1. 文件格式验证:验证字节流是否符合 Class 文件的规范,如主次版本号是否在当前虚拟
    机范围内,常量池中的常量是否有不被支持的类型.
  2. 元数据验证:对字节码描述的信息进行语义分析,如这个类是否有父类,是否集成了不
    被继承的类等。
  3. 字节码验证:是整个验证过程中最复杂的一个阶段,通过验证数据流和控制流的分析,
    确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正确,跳转
    指令是否正确等。
  4. 符号引用验证:这个动作在后面的解析过程中发生,主要是为了确保解析动作能正确执
    行。
  • 准备(准备阶段是为类的静态变量(类变量)分配内存并将其初始化为默认值(初始化阶段进行赋相应的值),这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量(就是普通的成员变量)的内存,实例变量将会在对象实例化时(初始化阶段)随着对象一起分配在 Java 堆中),如果是静态常量则会在准备阶段为其分配内存,并赋用户自定义的值;
  1. public static int value=123; //在准备阶段 value 初始值为 0 。在初始化阶段才会变为 123 。
  2. public static final int number = 3;//直接在准备阶段赋值为3,因为其不可变
  • 解析(该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。)[JVM 针对类或接口、字段、类方法、接口方法、方法类型方法句柄和调用点限定符 7 类引用进行解析。这个阶段的主要任务是将其在常量池中的符号引用替换成直接其在内存中的直接引用]
  • 初始化(初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的 Java 程序代码。)

    遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:

    • 使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
    • 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
    • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
    • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
    • 当使用 JDK1.7 动态语言支持时,如果一个 java.lang.invoke.MethodHandle实例最后的解析结果 REF_getstatic,REF_putstatic,REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化。
  • 使用(当 JVM 完成初始化阶段之后,JVM 便开始从入口方法开始执行用户的程序代码)
  • 卸载(当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存)

什么是java垃圾回收机制?

在 java 中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在
JVM 中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚
拟机空闲或者当前堆内存不足时,才会触发执行,扫描那些没有被任何引用的对象,并将
它们添加到要回收的集合中,进行回收;

JVM内存分哪几个区,每个区的作用是什么?

  • 方法区(永久代)[线程共享]:
  1. 有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生 GC,在这里
    进行的 GC 主要是对方法区里的常量池和对类型的卸载
  2. 方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。
  3. 该区域是被线程共享的。
  4. 方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池
    具有动态性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量
    池中。
  • 虚拟机栈[线程数据隔离]
  1. 虚拟机栈也就是我们平常所称的栈内存,它为 java 方法服务,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。
  2. 虚拟机栈是线程私有的,它的生命周期与线程相同。
  3. 局部变量表里存储的是基本数据类型、returnAddress 类型(指向一条字节码指令的地
    址)和对象引用,这个对象引用有可能是指向对象起始地址的一个指针,也有可能是代表
    对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译器间确定
  4. 操作数栈的作用主要用来存储运算结果以及运算的操作数,它不同于局部变量表通过索
    引来访问,而是压栈和出栈的方式
  5. 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了
    支持方法调用过程中的动态连接.动态链接就是将常量池中的符号引用在运行期转化为直接
    引用。
  • 本地方法栈[线程数据隔离]
  1. 本地方法栈和虚拟机栈类似,只不过本地方法栈为 Native 方法服务;
  • 堆[线程共享]
  1. java 堆是所有线程所共享的一块内存,在虚拟机启动时创建,几乎所有的对象实例都在这
    里创建,因此该区域经常发生垃圾回收操作。
  • 程序计数器
  1. 内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码
    指令,分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个 java 虚拟机规范没有规定任何 OOM 情况的区域。

Hotspot JVM 后台运行的系统线程主要有下面几个:

线程描述
虚拟机线程(VM thread)这个线程等待 JVM 到达安全点操作出现。这些操作必须要在独立的线程里执行,因为当堆修改无法进行时,线程都需要 JVM 位于安全点。这些操作的类型有:stop-the-world 垃圾回收、线程栈 dump、线程暂停、线程偏向锁(biased locking)解除。
GC 线程这些线程支持 JVM 中不同的垃圾回收活动
编译器线程这些线程在运行时将字节码动态编译成本地平台相关的机器码。
信号分发线程这个线程接收发送到 JVM 的信号并调用适当的 JVM 方法处理。

JVM的基础总结

在这里插入图片描述

JVM的内存区域

在这里插入图片描述

  • JVM 内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区
    域【JAVA 堆、方法区】、直接内存
  • 线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束 而 创建/销毁(在 Hotspot
    VM 内, 每个线程都与操作系统的本地线程直接映射, 因此这部分内存区域的存/否跟随本地线程的
    生/死对应)
  • 线程共享区域随虚拟机的启动/关闭而创建/销毁
  • 直接内存并不是 JVM 运行时数据区的一部分, 但也会被频繁的使用: 在 JDK 1.4 引入的 NIO 提
    供了基于 Channel 与 Buffer 的 IO 方式, 它可以使用 Native 函数库直接分配堆外内存, 然后使用
    DirectByteBuffer 对象作为这块内存的引用进行操作(详见: Java I/O 扩展), 这样就避免了在 Java
    堆和 Native 堆中来回复制数据, 因此在一些场景中可以显著提高性能

JVM的模型

在这里插入图片描述

jvm堆运行时内存

在这里插入图片描述

  • 新生代

是用来存放新生的对象。一般占据堆的1/3空间。由于频繁创建对象,所以新生代会频繁触发
MinorGC 进行垃圾回收。新生代又分为 Eden 区、ServivorFrom、ServivorTo 三个区

  1. Eden区

    Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老
    年代)。当Eden区内存不够的时候就会触发MinorGC,对新生代区进行
    一次垃圾回收

  2. ServivorFrom

    上一次 GC 的幸存者,作为这一次 GC 的被扫描者

  3. ServivorTo

    保留了一次 MinorGC 过程中的幸存者(上一次 GC 的幸存者,作为这一次 GC 的被扫描者)

  • 老年代

主要存放应用程序中生命周期长的内存对象

老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行
了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足
够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间

  • 永久代

指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被
放入永久区域,它和和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。所以这
也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常;

  1. 新特性1.8

    在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间
    的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用
    本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native
    memory, 字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由
    MaxPermSize 控制, 而由系统的实际可用空间来控制


  • 延伸
  1. MinorGC【 采用复制算法】(新生代内存不够用时候发生 MGC 也叫 YGC)

    MinorGC 的过程(复制->清空->互换)
    from区与to区的角色互换(上一次 GC 的幸存者,作为这一次 GC 的被扫描者)
    1 : eden 、 servicorFrom 复制到 ServicorTo,年龄+1
    首先,把 Eden和 ServivorFrom区域中存活的对象复制到 ServicorTo区域(如果有对象的年
    龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果 ServicorTo 不
    够位置了就放到老年区);

    2 : 清空 eden 、 servicorFrom
    然后,清空 Eden 和 ServicorFrom 中的对象;

    3 : ServicorTo 和 ServicorFrom 互换
    最后,ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom区

  2. MajorGC【采用标记清除算法】(清理老年代)

    首先扫描一次所有老年代,标记出存活的对象,然后回收没
    有标记的对象。MajorGC 的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减
    少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的
    时候,就会抛出 OOM(Out of Memory)异常

  3. Full GC 是清理整个堆空间—包括年轻代和老年代(JVM 内存不够的时候发生 FGC)


JVM垃圾回收机制的演变

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

如和判断一个对象是否存活?(或者 GC 对象的判定方法)

  • 引用计数法

对象被引用就加一,引用失效就减一,当为零时,也被称为死对象,将会被回收

所谓引用计数法就是给每一个对象设置一个引用计数器,每当有一个地方引用这个对象
时,就将计数器加一,引用失效时,计数器就减一。当一个对象的引用计数器为零时,说
明此对象没有被引用,也就是“死对象”,将会被垃圾回收.

引用计数法有一个缺陷就是无法解决循环引用问题,也就是说当对象 A 引用对象 B,对象
B 又引用者对象 A,那么此时 A,B 对象的引用计数器都不为零,也就造成无法完成垃圾回
收,所以主流的虚拟机都没有采用这种算法。
  • 可达性算法(引用链法)[也就是上面提到的根搜索算法]

根据GCRoots的对象向下搜索,没有任何引用链时,认为死对象.

从一个被称为 GC Roots 的对象开始向下搜索,如果一个对象到 GC
Roots 没有任何引用链相连时,则说明此对象不可用
在 java 中可以作为 GC Roots 的对象有以下几种:

  • 虚拟机栈中引用的对象
  • 方法区类静态属性引用的对象
  • 方法区常量池引用的对象
  • 本地方法栈 JNI 引用的对象

虽然这些算法可以判定一个对象是否能被回收,但是当满足上述条件时,这个对象不一
定会被回收
。当一个对象不可达 GC Root 时,这个对象并
不会立马被回收,而是出于一个死缓的阶段,若要被真正的回收需要经历两次标记
如果对象在可达性分析中没有与 GC Root 的引用链,那么此时就会被第一次标记并且进行
一次筛选,筛选的条件是是否有必要执行 finalize()方法。当对象没有覆盖 finalize()方法
或者已被虚拟机调用过,那么就认为是没必要的。
如果该对象有必要执行 finalize()方法,那么这个对象将会放在一个称为 F-Queue 的对队
列中,虚拟机会触发一个 Finalize()线程去执行,此线程是低优先级的,并且虚拟机不会承
诺一直等待它运行完,这是因为如果 finalize()执行缓慢或者发生了死锁,那么就会造成 F-
Queue 队列一直等待,造成了内存回收系统的崩溃。GC 对处于 F-Queue 中的对象进行
第二次被标记,这时,该对象将被移除”即将回收”集合,等待回收。

GC垃圾收集器

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

JVM的内存模型演变

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值