4,jvm原理

4.1jvm介绍

4.1.1 如何理解java虚拟机,他的结构是如何设计的

JVM 全称是 Java 虚拟机,在聊什么是 JVM 之前,我们不妨看⼀下这张图。
在这里插入图片描述
从这张图中可以看出 JVM 所处的位置,同时也能看出它两个作用:
 运⾏并管理 Java 源码⽂件所⽣成的 Class⽂件,
 在不同的操作系统上安装不同的 JVM,从⽽实现了跨平台的保证。
⼀般情况下,对于开发者⽽⾔,即使不熟悉 JVM 的运⾏机制并不影响业务代码的开发,因为在安装完JDK 或者 JRE 之后,其中就已经内置了 JVM,所以只需要将 Class⽂件交给 JVM 运⾏即可。
但当程序运⾏的过程中出现了问题,⽽这个问题发生在 JVM 层⾯的,那我们就需要熟悉 JVM 的运⾏机制,才能迅速排查并解决 JVM 的性能问题。
我们先看下目前主流的 JVM HotSpot 的架构图,通过这张架构图,我们可以看出 JVM 的大致流程是把一个 class 文件通过类加载器加载进系统,然后放到不同的区域,通过编译器编译。
在这里插入图片描述
第一个部分 Class Files
在 Java 中,Class⽂件是由源码⽂件⽣成的,⾄于源码⽂件的内容,是每个 Java 开发者在JavaSE 阶段的必备知识,这⾥就不再赘述了,我们可以关注⼀下 Class⽂件的格式,⽐如其中的常量池、成员变量、⽅法等,这样就能知道 Java 源码内容在 Class⽂件中的表示⽅式
第二个部分 Class Loader Subsystem 即类加载机制
Class⽂件加载到内存中,需要借助 Java 中的类加载机制。类加载机制分为装载、链接和初始化,其主要就是对类进⾏查找、验证以及分配相关的内存空间和赋值
第三个部分 Runtime Data Areas 也就是通常所说的运⾏时数据区
其解决的问题就是 Class⽂件进入内存之后,该如何进⾏存储不同的数据以及数据该如何进⾏扭转。比如:Method Area 通常会储存由 Class⽂件常量池所对应的运⾏时常量池、字段和⽅法的元数据信息、类的模板信息等;Heap 是存储各种 Java 中的对象实例;Java Threads 通过线程以栈的⽅式运⾏加载各个⽅法;Native Internal Thread 可以理解为是加载运⾏native 类型的⽅法;PC Register 则是保存每个线程执⾏⽅法的实时地址。
这样通过运⾏时数据区的 5 个部分就能很好地把数据存储和运⾏起来了
第四个部分 Garbage Collector 也就是通常所说的垃圾回收
就是对运⾏时数据区中的数据进⾏管理和回收。回收机制可以基于不同的垃圾收集器,⽐如 Serial、Parallel、CMS、G1、ZGC 等,可以针对不同的业务场景选择不同的收集器,只需要通过 JVM 参数设置 即可。如果我们打开 hotspot 的源码,可以发现这些收集器其实就是对于不同垃圾收集算法的实现,核⼼的算法有 3 个:标记-清除、标记-整理、复制
第五个部分是 JIT Compiler 和 Interpreter
通俗理解就是翻译器,Class 的字节码指令通过 JIT Compiler 和 Interpreter 翻译成对应操作系统的
CPU 指令,只不过可以选择解释执⾏或者编译执⾏,在 HotSpot JVM 默认采用的是这两种⽅式的混合。
在这里插入图片描述
第六就是 JNI 的技术
如果我们想要找 Java 中的某个 native⽅法是如何通过 C 或者 C++实现的,那么可以通过 Native
Method Interface 来进⾏查找,也就是所谓的 JNI 技术。
通过官⽹上给出的 HotSpot 架构图,我们就能够知道 JVM 到底是如何运行的了,当然在实际操作的过程中我们可以借助⼀些 JVM 参数:例如:【MIC 停顿 2S,展示图片】
在这里插入图片描述
在这里插入图片描述

4.1.2 什么是双亲委派机制

我简单说一下类的加载机制(如图),就是我们自己写的 java 源文件到最终运行,必须经过编
译和类加载两个阶段。
编译的过程就是把.java 文件编译成.class 文件。
类加载的过程,就是把 class 文件装载到 JVM 内存中,装载完成以后就会得到一个 Class 对象,我们就可以使用 new 关键字来实例化这个对象。
在这里插入图片描述
(如图)而类的加载过程,需要涉及类加载器。
JVM 在运行的时候,会产生 3 个类加载器,这三个类加载器组成了一个层级关系
每个类加载器分别去加载不同作用范围的 jar 包,比如
 Bootstrap ClassLoader,主要是负责 Java 核心类库的加载,也就是 %{JDK_HOME}\lib 下的
rt.jar、resources.jar 等
 Extension ClassLoader,主要负责%{JDK_HOME}\lib\ext 目录下的 jar 包和 class 文件
 Application ClassLoader,主要负责当前应用里面的 classpath 下的所有 jar 包和类文件
除了系统自己提供的类加载器以外,还可以通过 ClassLoader 类实现自定义加载器,去满足一些特殊场景的需求。
在这里插入图片描述
(如图)所谓的父委托模型,就是按照类加载器的层级关系,逐层进行委派。
比如当需要加载一个 class 文件的时候,首先会把这个 class 的查询和加载委派给父加载器去执行,如果父加载器都无法加载,再尝试自己来加载这个 class。
在这里插入图片描述
这样设计的好处,我认为有几个。
1, 安全性,因为这种层级关系实际上代表的是一种优先级,也就是所有的类的加载,优先给Bootstrap
ClassLoader。那对于核心类库中的类,就没办法去破坏,比如自己写一个 java.lang.String,最
终还是会交给启动类加载器。再加上每个类加载器的作用范围,那么自己写的 java.lang.String 就
没办法去覆盖类库中类。
,2, 我认为这种层级关系的设计,可以避免重复加载导致程序混乱的问题,因为如果父加载器已经加载过了,那么子类就没必要去加载了。

4.2 内存管理

4.2.1 jvm如何判断一个对象可以被回收

在 JVM 里面,要判断一个对象是否可以被回收,最重要的是判断这个对象是否还在被使用,只有没被使用的对象才能回收。

  1. (如图)引用计数器,也就是为每一个对象添加一个引用计数器,用来统计指向当前对象的引用次数,
    如果当前对象存在应用的更新,那么就对这个引用计数器进行增加,一旦这个引用计数器变成 0,就意味着它可以被回收了。
    这种方法需要额外的空间来存储引用计数器,但是它的实现很简单,而且效率也比较高
    在这里插入图片描述
    (如图)不过主流的 JVM 都没有采用这种方式,因为引用计数器在处理一些复杂的循环引用或者相互依赖的情况时,可能会出现一些不再使用但是又无法回收的内存,造成内存泄漏的问题。
    在这里插入图片描述

  2. 可达性分析,它的主要思想是,首先确定一系列肯定不能回收的对象作为 GC root,
    比如虚拟机栈里面的引用对象、本地方法栈引用的对象等,然后以 GC ROOT 作为起始节点,
    从这些节点开始向下搜索,去寻找它的直接和间接引用的对象,当遍历完之后如果发现有一些对象不可到达,那么就认为这些对象已经没有用了,需要被回收。
    在垃圾回收的时候,JVM 会首先找到所有的 GC root,这个过程会暂停所有用户线程, 然后再从 GC Roots 这些根节点向下搜索,可达的对象保留,不可达的就会回收掉。
    可达性分析是目前主流 JVM 使用的算法。
    在这里插入图片描述

4.2.2 谈谈你对jvm中主要GC算法的理解

实际上,垃圾收集器(GC,Garbage Collector)是和具体 JVM 实现紧密相关的,不同厂商(IBM、Oracle),不同版本的 JVM,提供的选择也不同。接下来,我来谈谈最主流的 Oracle JDK。
Serial GC,它是最古老的垃圾收集器,“Serial”体现在其收集工作是单线程的,并且在进行垃圾收集过程中,会进入臭名昭著的“Stop-The-World”状态。当然,其单线程设计也意味着精简的 GC 实
现,无需维护复杂的数据结构,初始化也简单,所以一直是 Client 模式下 JVM 的默认选项。从年代的角度,通常将其老年代实现单独称作 Serial Old,它采用了标记 - 整理(MarkCompact)算法,区别于新生代的复制算法。Serial GC 的对应 JVM 参数是-XX:+UseSerialGC
ParNew GC,很明显是个新生代 GC 实现,它实际是 Serial GC 的多线程版本,最常见的应用场景
是配合老年代的 CMS GC 工作,下面是对应参数
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
CMS(Concurrent Mark Sweep) GC,基于标记 - 清除(Mark-Sweep)算法,设计目标是尽量
减少停顿时间,这一点对于 Web 等反应时间敏感的应用非常重要,一直到今天,仍然有很多系统使用CMS GC。但是,CMS 采用的标记 - 清除算法,存在着内存碎片化问题,所以难以避免在长时间运行等情况下发生 full GC,导致恶劣的停顿。另外,既然强调了并发(Concurrent),CMS 会占用更多 CPU 资源,并和用户线程争抢。Parrallel GC,在早期 JDK 8 等版本中,它是 server 模式 JVM的默认 GC 选择,也被称作是吞吐量优先的 GC。它的算法和 Serial GC 比较相似,尽管实现要复杂得多,其特点是新生代和老年代 GC 都是并行进行的,在常见的服务器环境中更加高效。开启选项是:
-XX:+UseParallelGC
另外,Parallel GC 引入了开发者友好的配置项,我们可以直接设置暂停时间或吞吐量等目标,JVM 会自动进行适应性调整,例如下面参数:
-XX:MaxGCPauseMillis=value
-XX:GCTimeRatio=N // GC 时间和用户时间比例=1 / (N+1)
G1 GC 这是一种兼顾吞吐量和停顿时间的 GC 实现,是 Oracle JDK 9 以后的默认 GC 选项。G1 可以直观地设定停药时间的目标,相比于 CMS GC,G1 未必能做到 CMS 在最好情况下的延时停顿,但是最差情况要好很多。G1 GC 仍然存在着年代的概念,但是其内存结构并不是简单的条带式划分,而是类似棋盘的一个个 region。Region 之间是复制算法,但整体上实际可看作是标记 - 整理(Mark-Compact)算法,可以有效地避免内存碎片,尤其是当 Java 堆非常大的时候,G1 的优势更加明显。G1 吞吐量和停顿表现都非常不错,并且仍然在不断地完善,与此同时 CMS 已经在 JDK9中被标记为废弃(deprecated),所以 G1 GC 值得你深入掌握。

4.2.3 jvm分代年龄为什么是15次

首先,在 JVM 的 heap 内存里面,分为 Eden Space、Survivor Space、Old Generation(如图)。
当我们在 Java 里面使用 new 关键字创建一个对象的时候,JVM 会在 Eden Space 分配一块内存空间来存储这个对象。
当 Eden Space 的内存空间不足的时候,会触发 Young GC 进行对象回收。
那些因为存在引用关系而无法回收的对象,JVM 会把它们转移到 Survivor Space。
在这里插入图片描述
(如图)Survivor Space 内部又分为 From 区和 To 区,刚从 Eden 区转移过来的对象会分配到 From区,
每经历一次 Young GC,这些没有办法被回收的对象就会在 From 区和 To 区来回移动,每移动一次,这个对象的GC 年龄就加 1。默认情况下 GC 年龄达到 15 的时候,JVM 就会把这个对象移动到 Old Generation。
在这里插入图片描述
其次呢,一个对象的 GC 年龄,是存储在对象头里面的(如图),一个 Java 对象在 JVM 内存中的布局由三个部分组成,
分别是对象头、实例数据、对齐填充。而对象头里面有 4 个 bit 位来存储 GC 年龄。
在这里插入图片描述而 4 个 bit 位能够存储的最大数值是 15,所以从这个角度来说,JVM 分代年龄之所以设置成 15 次是因为它最大能够存储的数值就是 15。
虽然 JVM 提供了参数来设置分代年龄的大小,但是这个大小不能超过 15。
而从设计角度来看,当一个对象触发了最大值 15 次 gc,还没有办法被回收,就只能移动到 old
generation 了。
另外,设计者还引入了动态对象年龄判断的方式来决定把对象转移到 old generation,也就是说
不管这个对象的 gc 年龄是否达到了 15 岁,只要满足动态年龄判断的依据,也同样会转移到 old
generation。

4.2.4 jvm为什么使用元空间替换永久代

面试解析
在 Java7 里面,JVM 运行时数据区是这样的(如图)。
在 Hotspot 虚拟机中,方法区的实现是在永久栈里面,它里面主要存储运行时常量池、Klass 类元信息等。
永久代属于 JVM 运行时内存中的一块存储空间,我们可以通过-XX:PermSize 来设置永久代的大小。
当内存不够的时候,会触发垃圾回收。
在这里插入图片描述
在 JDK1.8 里面,JVM 运行时数据区是这样的(如图)
在 Hotspot 虚拟机中,取消了永久栈,由元空间来实现方法区的数据存储。
元空间不属于 JVM 内存,而是直接使用本地内存,因此不需要考虑 GC 问题。
默认情况下元空间是可以无限制地使用本地内存的,但是我们也可以使用 JVM 参数来限制内存使用大小。
在这里插入图片描述

回答
我认为有三个方面的原因:
 在 1.7 版本里面,永久内存是有上限的,虽然我们可以通过参数来设置,但是 JVM 加载的 class
总数、大小是很难确定的。
所以很容易出现 OOM 问题。
但是元空间是存储在本地内存里面,内存上限比较大,可以很好地避免这个问题。
 永久代的对象是通过 FullGC 进行垃圾收集,也就是和老年代同时实现垃圾收集。
替换成元空间以后,简化了 Full GC。可以在不进行暂停的情况下并发地释放类数据,同时也提升了GC 的性能
 Oracle 要合并 Hotspot 和 JRockit 的代码,而 JRockit 没有永久代表。

  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值