JVM 常见知识点总结

一、类加载篇

1.1 类加载过程

所谓类加载过程就是将我们的java原文件通过javac编译之后的产生的字节码文件。字节码文件(.class文件)加载到jvm内存的过程称为:类加载过程,(加载到内存的类称为运行时类,运行时类就成为Class的一个实例)该过程让jvm去执行字节码指令的过程。
jvm中类加载的过程包括:加载,链接(验证,解析,准备),初始化这五个阶段。

  1. 加载:是类加载的第一个阶段,他要完成三件事:
    1.通过一个类的全限定名来获取该类的二进制字节流。
    2.将这个字节流所代表的静态存储结构转化为方法区的运行时结构。
    3.内存中生存一个这个类的Class对象,作为方法区这个类的数据访问入口。
  2. 验证:验证时链接的第一个步骤,这个阶段目的是确保Class文件的字节流包含的信息符合java虚拟机规范,保证这些信息不会危害虚拟机自身的安全。其中包括:文件格式检验,元数据检验,字节码检验,符号引用检验。cafe baby魔术。
  3. 准备:该阶段是正式为类中定义的变量(静态变量被static 修饰的变量)分配内存并设置类变量的初始值。public static int a = 1;那么准备阶段他会把a赋值为0而不是1;
  4. 解析:该阶段时java虚拟机将常量池内的符号引用替换为直接引用的过程。
  5. 初始化阶段:是类加载过程的最后一个阶段,初始化阶段就是执行类构造器clinit方法的过程比如说刚才的a这个类变量就会复制为1。

1.2 类加载器分类

  • 启动类加载器(Bootstrap ClassLoader)是虚拟机自身的一部分,用来加载 Java_HOME/lib/目录中或者被 -Xbootclasspath 参数所指定的路径中并且被虚拟机识别的类库;
  • 扩展类加载器(Extension ClassLoader):负责加载\lib\ext目录或Java. ext. dirs系统变量指定的路径中的所有类库;
  • 应用程序类加载器(Application ClassLoader)。负责加载用户类路径 (classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

1.3 双亲委派机制

如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。

当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类由子类去完成类的加载。

二、JVM内存结构篇

2.1 JVM内存结构

jvm虚拟机在执行java程序的过程中他会把自己管里的内存,划分为5个不同的区域。jvm内存结构可以分为五个部分:

  1. 程序计数器: 是一块内存很小的单元,是当前线程所执行的字节码行号指示器。分支循环跳转,线程恢复异常处理都需要依赖这个计数器。是线程私有的。如果线程执行的是java方法,则计数器记录这在执行的虚拟机字节码指令的地址如果是执行本地(Native)方法则这个计数器为空undefined,jvm内存中唯一一个没有oom以及gc的区域。
  2. java虚拟机栈: 他也是线程私有的,生命周期和线程一样。每个方法的执行jvm都会与之对应创建一个栈帧用于存放局部表量表,操作数栈,动态链接,方法返回地址,以及附加信息。方法的执行对应的进栈操作,方法结束对应出栈操作。
  3. 本地方法栈: 与java虚拟机栈执行原理相同,不同的是他们执行的方法不同,java虚拟机执行的是java方法,本地方法栈执行的是本地方法。
  4. java堆: 它是jvm所管理的内存中最大的一块,是线程共享的区域,该区域的目的就是用于存放对象实例,几乎所有的对象都在堆上分配内存,逃逸分析技术日益强大,栈上分配,标量替换优化等手段导致一些微妙变换,在堆上分配内存存放对象不那么绝对。堆也是垃圾收集器管理的内存区域,基于分代收集理论,java堆被分为新生代,老年代(默认比例大小1:2),永久代(1.8元空间,脱离虚拟机管理,使用本地内存),新生代分为伊甸园区,from survivor to survivor默认比例8:1:1。堆内存可以实现固定大小也可以扩展(参数:-Xmx和-Xms)。
  5. 方法区: 与堆一样也是线程共享的内存区域,它用于存储已被jvm加载的类的信息,常量,静态变量,即时编译器编译后的代码缓存等数据,jvm规范中把方法区作为堆的一个逻辑部分。

2.2 对象的创建过程

  1. 当jvm遇到一条字节码new指令时,首先会检查这个指令的参数是否能在常量池重定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载,解析,初始化。如果没有则先进行类的加载过程。
  2. 类加载检查通过后,jvm为新生对象分配内存。对象所需的内存在类加载完成后可确定。为对象分配内存空间大小实际上是那一块确定大小的内存块从java堆划分出来。(1指针碰撞,2空闲列表 )两种方式选取合适的空间大小是根据java堆是否规整来决定的。java堆是否规整又由垃圾收集器是否带有压缩整理的能力决定。
    并发问题的处理:例如:用指针碰撞时,如果F和G同时需要分配内存,F和G的大小不同,他们同时获取到了指针位置,同时移动指针为对象分配内存空间这样就会出现并发问题。
    空闲列表也会有并发问题: jvm当然有方法解决并发问题,两种方法:
    1.cas+重试机制:就是通过cas操作移动指针,只有一个线程可以移动成功,移动失败的线程重试,直到成功为止。
    2.TLAB(thread local Allocation buffer)本地线程分配缓冲(默认)。
    本地线程分配缓冲:这种方式设计思想很简单,就是当线程开启时,就为每个线程分配一块较大的空间,然后线程内部创建对象的时候就从自己的空间分配,这样就不会有并发分配问题了。如果线程自己的空间用完了,那就要从堆中内存分配了,从而转为cas+重试机制解决并发问题。以上是对象想创建的第二步,内存分配。
  3. 初始化: 就是对分配的这一块内存初始化为零值,也就是给实例对象的成员变量赋值为零值,引用类型为null,int类型赋值为0等等操作。
    这样的话,对象就可以在没有赋值情况下使用了,只不过访问对象的成员变量都是零值。
  4. 设置对象头: 在说道对象头这一块,先来看看对象的结构。对象分为对象头,实例数据区,对齐填充位。设置对象头,就是设置对象头中对象的hashcode,分代年龄,锁状态,类元数据指针等信息。对象头设置好了之后就可以执行对象的一些初始化方法了。
  5. 执行初始化方法: 这一步,jvm会给对象的成员变量设置程序员指定值的初始值,并且会执行构造方法。

2.3 强软弱虚引用

JDK1.2之后java对引用这个概念进行扩充,将引用分为强软弱虚引用,引用强度依次减弱。

  1. 强引用:平时new出来的对象,只要有引用在即使发生GC也回收不了。
  2. 软引用:用来描述一些还有用但是非必须的对象。空间不够就回收,软引用适合做缓存,空间足够就放在那里,不够用就回收。
  3. 弱引用:用来描述那些非必须对象,gc开始工作无论内存是否足够,都会回收弱引用关联的对象 在jdk1.2之后提供WeakReference类来实现弱引用。
  4. 虚引用:也称幽灵引用和幻影引用是最弱的引用关系。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统的通知。

三、垃圾回收篇

3.1 如何判断对象是否死亡

堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即对象不存在任何的引用)。

1.引用计数法

给对象添加一个引用计数器,当对象被引用时,计数器就加1;当引用失效,计数器减1;当计数器为0时表明该对象是不存在任何引用的,即表明此对象死亡,可以被垃圾收集器回收。

这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间循环引用的问题。

他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。

2.可达性分析算法

算法思想:通过一系列的“GC Roots”的对象作为起点,向下搜索引用的对象。当一个对象到GC Roots没有任何引用相连接的话,则证明此对象是不可用的,即死亡对象,可以被回收。

3.2 哪些对象可以作为gcroots

  1. 虚拟机栈(栈帧中的本地变量表)中的引用对象,如各个线程被调用的方法堆栈中使用到的参数,局部变量,临时变量。
  2. 在方法区中类静态属性引用对象,如java类的引用类型静态变量。
  3. 方法区中常量引用的对象,如字符串常量池里的引用。
  4. 所有被同步锁持有的对象。

3.3 垃圾回收算法

  1. 标记清除算法(老年代):最基础最早出算的垃圾收集算法,和他的名字一样该算法分为两份阶段
    1.标记阶段:标记存活对象通过gcroots根节点出发标记所有从根节点开始的可达对象。未被标记的对象就是未被引用的垃圾对象。标记过程就是对象是否为垃圾对象的一个判定过程。
    2.清除阶段:回收未被标记的对象。
    主要缺点:
    1.执行的效率不稳定,当java堆中包含大量对象的时候,而且其中大部分对象是要被回收的。这是必须进行大量的标记和清除动作导致标记和清除这两个过程随着对象数量的增加而效率降低。
    2.内存会产生碎片化问题,标记清除之后java堆内存会产生大量的不连续的内存碎片,空间碎片太多导致程序在运行的过程中需要分配较大的对象时,无法找到足够连续的的内存空间而触发另一个gc。
    3.进行垃圾回收时需要停止用户线程Stop The World现象,导致用户体验差。
    优点:算法简单

  2. 复制算法(新生代):复制算法的诞生是基于标记清除算法面对大量对象回收时效率低的问题。它的思想就是:将可用的内存按容量大小划分为大小相同的两块,每次只使用一块,当另一块用完时,就将存活的对象复制到另一块内存,然后在清理已使用的那一块。
    主要缺点:
    1.将可用内存一分为二,每次只使用一块造成空间的浪费。
    2.在对象存活率较高(如老年代的对象)时复制算法需要进行大量复制操作,效率会降低。所以老年代不适用复制算法,使用标记整理算法
    主要优点:
    1.没有标记清除阶段,实现简单,运行高效。
    2.复制过去能保证内存空间的连续性,不会出现碎片化问题。

  3. 标记整理算法(老年代):标记阶段还是和标记清除算法的标记阶段一样,整理阶段让所有存活对象都向内存空间一端移动,然后直接清除边界以外的内存。标记清除算法与标记整理算法的本质区别:前者是非移动式的后再是移动式的回收算法
    优缺点: 是对标记清除算法和复制算法的一种折中方案。

  4. 增量收集算法:如果一次性将所有垃圾进行收集处理,那么将造成系统长时间的停顿。我们可以让垃圾收集线程和用户线程交替执行,每次进行一部分的gc,然后切换到用户线程反复执行,直到gc完成。
    优点: gc和用户线程并行,减少系统的停顿时间。
    缺点: 上下文切换消耗时间,使得gc的总成本上升,造成系统吞吐量下降。

  5. 分代算法:按照对象的存活时间即生命周期将堆内存分为新生代,老年代。采用分代收集理论进行垃圾对象的回收操作。而不用整个堆进行回收。

3.4 Minor GC和Full GC的区别

  1. 部分收集

    新生代收集(Minor GC/Young GC):只对新生代进行垃圾收集

    老年代收集(Major GC/Old GC):对老年代进行垃圾收集(需要注意Major GC在有的语境下也用于指代整堆收集)

  2. 整堆收集(Full GC):收集整个Java堆和方法区。

Minor GC 和 Full GC的区别如下:

  1. 作用范围不同:Minor GC又称为新生代GC,主要发生在Java的新生代中。而Full GC,涉及整个Java堆的回收,包括:年轻代、老年代以及方法区或元空间。

  2. 触发条件:Minor GC通常在新生代的Eden区满时触发。Full GC触发条件比如老年代不足以分配新的内存时。

  3. 回收算法:Minor GC一般采用复制算法进行垃圾回收,而Full GC在老年代中一般采用标记-整理算法进行垃圾回收。

  4. 执行速度:新生代中对象生命周期大多较短(朝生夕死),Minor GC相对频繁但回收速度较快。相反,Full GC因为涉及整个堆的回收,且老年代中存活对象较多,通常执行速度较慢。

  5. 应用影响:当Full GC发生时,通常会触发STW(Stop-The-World),即所有的应用线程都会被暂停,直到GC完成。而Minor GC虽然也可能会引起一定的应用停顿,但影响相对较小。

3.5 HotSpot为什么分为新生代和老年代

  1. 新创建的对象优先在新生代(Eden区)分配内存空间,长期存活的对象、大对象分配在老年代。

  2. 新生代中的对象特点是朝生夕死,而老年代中存活的对象时间是比较长的、存活的几率是比较高的。

  3. 新生代采用标记-复制算法进行垃圾收集,每次收集都会有大量对象死去;老年代采用标记-清除或标记-整理算法进行垃圾收集,对象的存活时间长。

3.6 常见的垃圾收集器

  • Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点 是简单高效;
  • ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程 版本,在多核CPU环境下有着比Serial更好的表现;
  • Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效 利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高 效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不 高的场景;
  • Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;
  • Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
  • CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集 器,以获取最短回收停顿时间为目标的收集器,具有高并发低停顿的特点,追求最 短GC回收停顿时间。
  • G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是 JDK1.7提供的一个新收集器,G1收集器基于标记-整理算法实现,也就是说不会 产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。

3.7 详细介绍CMS垃圾回收器

  1. CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“- XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。
  2. CMS 使用的是标记-清除的算法实现的,所以在gc的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。

四、JVM调优篇

Java虚拟机(JVM)在Java技术体系中占据着核心地位。JVM调优能够显著提升应用的响应速度、吞吐量以及资源利用效率,从而保障系统的稳定高效运行。本章为JVM调优前置知识,主要介绍:什么时候需要调优?JVM如何监控和诊断性能、如何设置参数、常用的JVM参数等。

4.1 什么时候需要调优

在进行JVM调优时,需要综合考虑JVM的内存管理、垃圾回收、线程管理等方面,通过合理的配置和参数调整,实现最佳的性能和资源利用效果。

通常出现以下情况就需要JVM调优:

  • 性能问题:例如应用程序的响应时间过长、吞吐量低、频繁发生垃圾回收等情况,可能需要进行JVM调优。
  • 内存问题:当应用程序经常发生内存溢出错误或持续占用过多的内存时,表明堆内存配置不合理或垃圾回收策略需要调整。
  • 并发问题:在高并发环境下,如果应用程序出现线程竞争、死锁或阻塞等问题,可以考虑通过调整线程池大小、线程栈大小等参数来改善并发性能。

4.2 JVM调优监控和诊断

当生产遇到上述类似的情况时,我们如何发现问题?一般会通过系统监控来诊断问题。通常诊断的工具有两类:一种是jdk自带命令行工具;一种是借助第三方性能分析工具。

基础配置

  • 主机参数:4核8G
  • jdk版本:1.8.0_74

4.2.1 Java命令行工具

1.jps:查看正在运行的Java进程

  • 通过jps命令获取Java进程的进程ID(PID)以及主类名称或JAR文件的完整路径名。

结果演示:

[igquery@boe-dev-BU01 vh-screen]$ jps -mlvV
24186 sun.tools.jps.Jps -mlvV -Denv.class.path=/data/igadm/app/jdk1.8.0_74/lib -Dapplication.home=/data/igadm/app/jdk1.8.0_74 -Xms8m

2.jstat:查看JVM统计信息

  • jstat 是 Java 虚拟机(JVM)自带的监控工具,用于查看 HotSpot JVM 的性能统计信息。

3.jmap:导出内存映像文件&内存使用情况

  • jmap 用于生成堆内存映射或堆转储文件(heap dump)。这些文件可以用于后续的堆分析,帮助开发者诊断内存泄漏、内存溢出等问题。

4.jinfo:实时查看和修改JVM配置参数

  • jinfo 用于实时查看和修改运行中的 Java 进程的 JVM 配置参数。

5.jstack:打印JVM中线程快照

  • jstack(JVM Stack Trace):用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。

  • 线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合。可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等问题。就可以用jstack显示各个线程调用的堆栈情况。

4.2.2 第三方JVM监控及诊断工具

使用命令行工具或组合能帮您获取目标Java应用性能相关的基础信息,但它们无法获取方法级别的分析数据,如方法间的调用关系、各方法的调用次数和调用时间等(这对定位应用性能瓶颈至关重要)。而且结果展示不够直观。

4.3 如何设置JVM调优参数?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值