JVM基础知识

参考

https://snailclimb.gitee.io/javaguide/#/?id=jvm-%e5%bf%85%e7%9c%8b-1

类加载
类加载的几个过程⭐️

img

总结:
类加载到内存中主要有5个阶段,分别为①加载:将Class文件读取到方法区内,在堆中创建Class对象②验证:确保Class文件符合当前虚拟机的要求③准备:在方法区中为类变量分配内存空间并设置类中变量的初始值。④解析:将常量池中的符号引用替换为直接引用。⑤初始化:这一步主要就是执行静态变量的初始化,包括静态变量的赋值和静态初始化块的执行,只有在父类的方法都执行成功后,子类的方法才可以被执行。在一个类中既没有静态变量赋值操作也没有静态语句块时,编译器不会为该类生成方法。

有哪些类加载器⭐️

JVM 中内置了三个重要的 ClassLoader,除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader:

BootstrapClassLoader(启动类加载器) :最顶层的加载类,由C++实现,负责加载JAVA_HOME/lib中的类库
ExtensionClassLoader(扩展类加载器) :负责加载JAVA_HOME/lib/ext中的类库
AppClassLoader(应用程序类加载器) :也称系统类加载器,负责加载用户类路径上指定的类库;也可以自定义类加载器。

类加载器的加载模型是什么,有什么好处?⭐️

双亲委派模型:当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。
安全性:API 不被篡改
唯一性:比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类

什么情况下类不会初始化

子类引用父类的静态字段,子类不会初始化
通过数组定义来引用类,不会触发此类的初始化
常量在编译阶段存入调用类的常量池中,不会触发定义常量的类的初始化

对象的创建⭐️

Java创建对象的过程

Step1 类加载检查:虚拟机遇到一条 new 指令时,会先去常量池检查这个类,看是否被加载、解析和初始化过

Step2 分配内存:把确定大小的的内存从Java堆中划分出来

Step3 初始化零值
虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头,这一步操作保证了对象的实例字段在 Java 代码中可以不赋

Step4 设置对象头
对象是否启用偏向锁、对象的哈希码、GC、分代年龄等信息

Step5 执行init方法
执行 方法,把对象按照程序员的意愿进行初始化

内存区域⭐️
简述JVM的内存区域⭐️

线程私有区域(程序计数器、虚拟机栈、本地方法区)
线程共享区域(堆、方法区)和直接内存。
程序计数器:用于存储当前线程执行字节码文件的行号指示器(唯一一个不会出现OutOfMemoryError的区域)

虚拟机栈:方法执行过程的内存模型,栈帧中存储了局部变量表,操作数栈,动态链接,方法出口等信息

本地方法栈:和虚拟机栈作用类似,区别是虚拟机栈为Java方法服务,本地方法栈为Native方法服务

堆:JVM运行过程中创建的对象和生成的数据都存储在堆中,是垃圾回收最主要的内存区域

方法区:用来存储常量,静态变量、类信息、即时编译器编译后的机器码、运行时常量池等数据

img

方法区和永久代的关系

方法区也被称为永久代,永久代就是方法区的一种实现方式,方法区是一种规范,而永久代是一种具体实现

为什么要将永久代(PermGen)替换为元空间(MetaSpace)呢?⭐️

永久代有一个 JVM 本身设置固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,比原来溢出的概率要小

字符串常量池、运行时常量池

JDK1.7 字符串常量池从方法区单独拿到了堆
JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)

如何判断对象是否死亡(两种方法)

引用计数法:如果某个地方引用了这个对象就+1,如果失效了就-1,当为 0 就会回收
缺点:因为无法判定相互循环引用(A 引用 B,B 引用 A)的情况

可达性分析算法(引用链法):“GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。

可达性分析算法

简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用作用、使用软引用能带来的好处)⭐️

①强引用,最常见的引用类型,把一个对象指向一个引用变量时就是强引用。不会被垃圾回收,是内存泄漏的主要原因
②软引用,当内存空间不足时将被回收
③弱引用,在垃圾回收过程中一定会被回收
④虚引用,虚引用和引用队列联合使用,主要用来跟踪对象的垃圾回收过程。

使用软引用的情况较多:防止内存溢出

如何判断一个类是无用的类

加载该类的 ClassLoader 已经被回收
该类所有的实例都已经被回收
这个类的.class对象没有在任何地方被引用

JVM垃圾回收区域(堆的内存布局)和对象晋升过程

JDK 8 版本之前,堆内存被通常被分为下面三部分:

  1. 新生代内存(Young Generation)
  2. 老生代(Old Generation)
  3. 永生代(Permanent Generation)

JDK 8 版本之后元空间替换永久代

堆中的 OutOfMemoryError 错误⭐️

OutOfMemoryError: GC Overhead Limit Exceeded : 当JVM花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。
java.lang.OutOfMemoryError: Java heap space :假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发

堆空间的基本结构:

img

JVM的运行时内存

其中新生代:老年代=1:2,永久代占非常少的堆空间。
新生代又分为ServivorFrom:ServivorTo:Eden=1:1:8

对象的晋升

堆内存常见分配策略
上图所示的 Eden 区、两个 Survivor 区都属于新生代(为了区分,这两个 Survivor 区域按照顺序被命名为 from 和 to),中间一层属于老年代。

大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。(当累积的某个年龄大小超过了survivor区的一半时,取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阈值”。)

不管怎样,都会保证名为 To 的 Survivor 区域是空的。Minor GC 会一直重复这样的过程,直到“To”区被填满,"To"区被填满之后,会将所有对象移动到老年代中。

HotSpot 为什么要分为新生代和老年代?⭐️

可以根据各个年代的特点选择合适的垃圾收集算法。

在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。

垃圾收集算法⭐️

垃圾收集算法分类

标记-清除算法:

标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。

复制算法:

为了解决效率问题
可以将内存分为大小相同的两块,每次把存活的对象复制到另一块

标记-整理算法

让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。

分代收集算法:

根据对象存活周期的不同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。

垃圾收集器⭐️

垃圾收集器分类
在这里插入图片描述

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。根据具体应用场景选择适合自己的垃圾收集器。

Serial收集器:

串行收集器

新生代采用复制算法,老年代采用标记-整理算法。

ParNew收集器:

ParNew 收集器其实就是 Serial 收集器的多线程版本
新生代采用复制算法,老年代采用标记-整理算法。

Parallel Scavenge收集器:

Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU),新生代采用复制算法,老年代采用标记-整理算法。

CMS收集器:⭐️

CMS是老年代垃圾收集器,在收集过程中可以与用户线程并发操作。它可以与Serial收集器和Parallel New收集器搭配使用。CMS牺牲了系统的吞吐量来追求收集速度,适合追求垃圾收集速度的服务器上
七个步骤

  • 初始标记(CMS-initial-mark) ,会导致stw(全局暂停);
  • 并发标记(CMS-concurrent-mark),与用户线程同时运行;
  • 预清理(CMS-concurrent-preclean),与用户线程同时运行;
  • 可被终止的预清理(CMS-concurrent-abortable-preclean) 与用户线程同时运行;
  • 重新标记(CMS-remark) ,会导致swt;
  • 并发清除(CMS-concurrent-sweep),与用户线程同时运行;
  • 并发重置状态等待下次CMS的触发(CMS-concurrent-reset),与用户线程同时运行; 其运行流程图如下所示:
    在这里插入图片描述
    优点:并发收集、低停顿
    缺点:
    对 CPU 资源敏感
    无法处理浮动垃圾(并发清理阶段用户线程还在运行,这段时间就可能产生新的垃圾,新的垃圾在此次GC无法清除,只能等到下次清理)
    它使用的回收算法是“标记-清除”算法会导致收集结束时产生大量空间碎片产生
G1收集器:⭐️

G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.
四个步骤:
初始标记
并发标记
最终标记
清理
G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来)。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。

GC收集器有哪些?CMS收集器与G1收集器的特点⭐️

并行收集器:串行收集器使用一个单独的线程进行收集,GC 时服务有停顿时间
串行收集器:次要回收中使用多线程来执行
CMS 收集器是基于“标记—清除”算法实现的,经过多次标记才会被清除
G1 从整体来看是基于“标记—整理”算法实现的收集器,从局部(两个 Region 之间)
上来看是基于“复制”算法实现的

CMS怎样进行垃圾回收,哪些过程是stw

基于“标记—清除”算法实现的,初始标记(标记存活的对象:老年代中所有的GC Roots对象和年轻代中活着的对象引用到的老年代的对象)、重新标记(该阶段的任务是完成标记整个老年代的所有的存活对象)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值