这些不可不知的JVM知识,我都用思维导图整理好了

Posted 三分恶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了这些不可不知的JVM知识,我都用思维导图整理好了相关的知识,希望对你有一定的参考价值。

空闲列表 两种,选择那种分配⽅式由 Java 堆是否规整决定,⽽Java堆是否规整⼜由所采⽤的垃圾收集器是否带有压缩整理功能决定。

⽅法:在上⾯⼯作都完成之后,从虚拟机的视⻆来看,⼀个新的对象已经产⽣了,但从Java 程序的视⻆来看,对象创建才刚开始,  ⽅法还没有执⾏,所有的字段都还为零。所以⼀般来说,执⾏ new 指令之后会接着执⾏  ⽅法,把对象按照程序员的意愿进⾏初始化,这样⼀个真正可⽤的对象才算完全产⽣出来。

3.2、对象的内存布局

在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

img

HotSpot虚拟机对象的对象头部分包括两类信息。第一类是用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32个比特和64个比特,官方称它为“Mark Word”。

3.3、对象的访问定位

建⽴对象就是为了使⽤对象,我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问⽅式有虚拟机实现⽽定,⽬前主流的访问⽅式有使⽤句柄和直接指针两种:

  • 句柄:如果使⽤句柄的话,那么Java堆中将会划分出⼀块内存来作为句柄池,reference 中存储的就是对象的句柄地址,⽽句柄中包含了对象实例数据与类型数据各⾃的具体地址信息。
  • image-20210214120115895
  • 直接指针:如果使⽤直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,⽽reference 中存储的直接就是对象的地址。
  • image-20210214120227426

    4、GC垃圾回收

    对于垃圾回收,主要考虑的就是完成三件事:

  • 哪些内存需要回收?

  • 什么时候回收?

  • 如何回收?

  • 4.1、如何判断对象需要回收?4.1.1、引用计数法

    引用计数法的算法:

  • 在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;
  • 当引用失效时,计数器值就减一;
  • 任何时刻计数器为零的对象就是不可 能再被使用的。
  • 客观地说,引用计数算法(Reference Counting)虽然占用了一些额外的内存空间来进行计数,但它的原理简单,判定效率也很高,在大多数情况下它都是一个不错的算法。也有一些比较著名的应用案例,例如微软COM(Component Object Model)技术、使用ActionScript 3的FlashPlayer、Python语言以及在游戏脚本领域得到许多应用的Squirrel中都使用了引用计数算法进行内存管理。

    但是,在Java 领域,至少主流的Java虚拟机里面都没有选用引用计数算法来管理内存,主要原因是,这个看似简单的算法有很多例外情况要考虑,例如在处理处理一些相互依赖、循环引用时非常复杂。

    4.1.2、可达性分析算法

    当前主流的商用程序语言(Java、C#,上溯至前面提到的古老的Lisp)的内存管理子系统,都是通过可达性分析(Reachability Analysis)算法来判定对象是否存活的。这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连, 或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。


    image-20210214121217760

    GC Roots 包括;

  • 全局性引用,对方法区的静态对象、常量对象的引用

  • 执行上下文,对 Java 方法栈帧中的局部对象引用、对 JNI handles 对象引用

  • 已启动且未停止的 Java 线程

  • 4.1.3、引用

    无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否引用链可达,判定对象是否存活都和“引用”离不开关系。

    Java的引用分为四种:强引用(Strongly Re-ference)软引用(Soft Reference)弱引用(Weak Reference)虚引用(Phantom Reference)

  • 强引用是最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似“Object obj=new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。

  • 软引用是用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存, 才会抛出内存溢出异常。Java提供提供了SoftReference类来实现软引用。

  • 弱引用也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。Java提供了WeakReference类来实现弱引用。

  • 虚引用也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。一个对象是否有虚引用的 存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。Java提供了PhantomReference类来实现虚引用。

  • 4.2、垃圾收集算法4.2.1、标记-清除算法

    最早出现也是最基础的垃圾收集算法是“标记-清除”(Mark-Sweep)算法,

    算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回

    收所有未被标记的对象。标记过程就是对象是否属于垃圾的判定过程。

    后续的收集算法大多都是以标记-清除算法为基础,对其缺点进行改进而得到的。

    它的主要缺点有两个:

  • 第一个是执行效率不稳定,如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过
  • 程的执行效率都随对象数量增长而降低;

  • 第二个是内存空间的碎片化问题,标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
  • 标记-清除算法的执行过程如图:

    image-20210213205500815
    4.2.2、标记-复制算法

    标记-复制算法常被简称为复制算法。为了解决标记-清除算法面对大量可回收对象时执行效率低的问题。

    它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

    这样实现简单,运行高效,不过其缺陷也显而易见,这种复制回收算法的代价是将可用内存缩小为了原来的一半,空间浪费较多。

    标记-复制算法的执行过程如图所示。

    image-20210213205852314
    4.2.3、标记-整理算法

    标记-整理算法的标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。

    “标记-整理”算法的示意图如图:

    image-20210213210117656
    4.3、分代收集理论

    当前商业虚拟机的垃圾收集器,大多数都遵循了“分代收集”(Generational Collection)的理论进行设计,分代收集名为理论,实质是一套符合大多数程序运行实际情况的经验法则,它建立在两个分代假说之上:

  • 1)弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。

  • 2)强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。

  • 基于这两个假说,收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。

    设计者一般至少会把Java堆划分为新生代(Young Generation)和老年代(Old Generation)两个区域。顾名思义,在新生代中,每次垃圾收集

    时都发现有大批对象死去,而每次回收后存活的少量对象,将会逐步晋升到老年代中存放。

    基于这种分代,老年代和新生代具备不同的特点,可以采用不同的垃圾收集算法。

  • ⽐如在新⽣代中,每次收集都会有⼤量对象死去,所以可以选择标记-复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。
  • ⽽⽼年代的对象存活⼏率是⽐较⾼的,⽽且没有额外的空间对它进⾏分配担保,所以必须选择标记-清除标记-整理算法进⾏垃圾收集。
  • 因为有了分代收集理论,所以就有了了“Minor GC(新⽣代GC)”、“Major GC(⽼年代GC)”、“Full GC(全局GC)”这样的回收类型的划分

    4.4、垃圾收集器4.4.1、Serial收集器

    Serial收集器是最基础、历史最悠久的收集器,曾经(在JDK 1.3.1之前)是HotSpot虚拟机新生代收集器的唯一选择。这个收集器是一个单线程工作的收集器,但它的“单线 程”的意义并不仅仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作,更重要的是强调在它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。

    Serial/Serial Old收 集器的运行过程如下:

    image-20210213213230637
    4.4.2、ParNew收集器

    ParNew收集器实质上是Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其余的行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一致,在实现上这两种收集器也共用了相当多的代码。

    ParNew收集器的工作过程如图所示:

    image-20210213213606136
    4.4.3、Parallel Scavenge收集器

    Parallel Scavenge收集器也是一款新生代收集器,它同样是基于标记-复制算法实现的收集器,也是能够并行收集的多线程收集器

    Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。由于与吞吐量关系密切,Parallel Scavenge收集器也经常被称作“吞吐量优先收集器”。

    Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。

    4.4.4、Serial Old收集器

    Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。这个收集器的主要意义也是供客户端模式下的HotSpot虚拟机使用。如果在服务端模式下,它也可能有两种用途:一种是在JDK 5以及之前的版本中与Parallel Scavenge收集器搭配使用,另外一种就是作为CMS 收集器发生失败时的后备预案,在并发收集发生Concurrent Mode Failure时使用。这两点都将在后面的内容中继续讲解。

    Serial Old收集器的工作过程如图所示。

    image-20210213214008232
    4.4.5、Parallel Old收集器

    Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现。这个收集器是直到JDK 6时才开始提供的。Parallel Old收集器的工作过程如图所示。

    image-20210213214130222
    4.4.6、CMS收集器

    CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网网站或者基于浏览器的B/S系统的服务端上,这类应用通常都会较为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验。CMS收集器就非常符合这类应用的需求。

    Concurrent Mark Sweep收集器运行过程如图:

    image-20210213214323197
    4.4.7、Garbage First收集器

    G1是一款主要面向服务端应用的垃圾收集器,是目前垃圾回收器的前沿成果。HotSpot开发团队最初赋予它的期望是(在比较长期的)未来可以替换掉JDK 5中发布的CMS收集器。现在这个期望目标已经实现过半了,JDK 9发布之日,G1宣告取代Parallel Scavenge加Parallel Old组合,成为服务端模式下的默认垃圾收集器。

    G1收集器运行过程如图:

    image-20210213214532807

    5、JVM类加载

    Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。

    5.1、类加载过程

    一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载 (Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化 (Initialization)、使用(Using)和卸载(Unloading)七个阶段,其中验证、准备、解析三个部分统称为连接(Linking)。

    过程如下图:

    image-20210214122059420

    **加载 **:

    “加载”(Loading)阶段是整个“类加载”(Class Loading)过程中的一个阶段,在加载阶段,Java虚拟机需要完成以下三件事情:

  • 1)通过一个类的全限定名来获取定义此类的二进制字节流。

  • 2)将这个字节流所代表的静态存储结构转化为堆的运行时数据结构。

  • 3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

  • 验证:

    验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。

    验证阶段大致上会完成四个阶段的检验动作:文件格式验证元数据验证字节码验证符号引用验证

    准备:

    准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。

    解析

    解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程。

    5.2、类加载器

    Java虚拟机设计团队有意把类加载阶段中的“通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类。实现这个动作的代码被称为“类加载器”(Class Loader)。

    5.2.1、类与类加载器

    类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远超类加载阶段。对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。

    5.2.2、双亲委派模型

    JVM 中内置了三个重要的 ClassLoader,启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分,其他所有

    的类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。

  • 启动类加载器(Bootstrap Class Loader): 这个类加载器负责加载存放在 <JAVA_HOME>\\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够识别的(按照文件名识别,如rt.jar、tools.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机的内存中。
  • 扩展类加载器(Extension Class Loader):这个类加载器是在类sun.misc.Launcher$ExtClassLoader 中以Java代码的形式实现的。它负责加载<JAVA_HOME>\\lib\\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库。
  • 应用程序类加载器(Application Class Loader):这个类加载器由 sun.misc.Launcher$AppClassLoader来实现。由于应用程序类加载器是ClassLoader类中的getSystem-ClassLoader()方法的返回值,所以有些场合中也称它为“系统类加载器”。它负责加载用户类路径 (ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。
  • 双亲委派模型: 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。

    image-20210214124729757

    **为什么要使用双亲委派模型来组织类加载器之间的关系呢?**一个显而易见的好处就是Java中的类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都能够保证是同一个类。反之,如果没有使用双亲委派模型,都由各个类加载器自行去加载的话,如果用户自己也编写了一个名为java.lang.Object的类,并放在程序的 ClassPath中,那系统中就会出现多个不同的Object类,Java类型体系中最基础的行为也就无从保证,应用程序将会变得一片混乱。

    5.2.3、破坏双亲委派模型

    过双亲委派模型并不是一个具有强制性约束的模型,而是Java设计者推荐给开发者们的类加载器实现方式。在Java的世界中大部分的类加载器都遵循这个模型,但也有例外的情况,直到Java模块化出现为止,双亲委派模型主要出现过3次较大规模“被破坏”的情况。

  • 第一次破坏:向前兼容

    JDK1.2发布之前,兼容之前的代码。

  • 第二次破坏:加载SPI接口实现类

  • 第二次被破坏是这个模型自身的缺陷导致的。双亲委派模型很好的解决了各个类加载器的基础类的统一问题(越基础的类由越上层的加载器进行加载),基础类之所以称为“基础”,是因为它们总是作为被用户代码调用的API, 但没有绝对,如果基础类调用会用户的代码怎么办呢?

    这不是没有可能的。一个典型的例子就是JNDI服务,JNDI现在已经是Java的标准服务,它的代码由启动类加载器去加载(在JDK1.3时就放进去的rt.jar),但它需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者(SPI, Service Provider Interface)的代码,但启动类加载器不可能“认识“这些代码啊。因为这些类不在rt.jar中,但是启动类加载器又需要加载。怎么办呢?

    为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader方法进行设置。如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过多的话,那这个类加载器默认即使应用程序类加载器。

    有了线程上下文加载器,JNDI服务使用这个线程上下文加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则。但这无可奈何,Java中所有涉及SPI的加载动作基本胜都采用这种方式。例如JNDI,JDBC,JCE,JAXB,JBI等。

  • 第三次破坏:热部署
  • 双亲委派模型的第三次“被破坏”是由于用户对程序的动态性的追求导致的。为了实现热插拔,热部署,模块化,意思是添加一个功能或减去一个功能不用重启,只需要把这模块连同类加载器一起换掉就实现了代码的热替换。例如OSGi的出现。在OSGi环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为网状结构。

    如果我们自己想定义一个类加载器,破坏双亲委派模型,只需要重写重写其中的loadClass方法,使其不进行双亲委派即可。

    5.2.4、Tomcat类加载器架构

    Tomcat是主流的Java Web服务器之一,为了实现一些特殊的功能需求,自定义了一些类加载器。

    Tomcat类加载器如下:

    image-20210214131305597

    Tomcat实际上也是破坏了双亲委派模型的。

    Tomact是web容器,可能需要部署多个应用程序。不同的应用程序可能会依赖同一个第三方类库的不同版本,但是不同版本的类库中某一个类的全路径名可能是一样的。如多个应用都要依赖hollis.jar,但是A应用需要依赖1.0.0版本,但是B应用需要依赖1.0.1版本。这两个版本中都有一个类是com.hollis.Test.class。如果采用默认的双亲委派类加载机制,那么无法加载多个相同的类。

    所以,Tomcat破坏双亲委派原则,提供隔离的机制,为每个web容器单独提供一个WebAppClassLoader加载器。

    Tomcat的类加载机制:为了实现隔离性,优先加载 Web 应用自己定义的类,所以没有遵照双亲委派的约定,每一个应用自己的类加载器——WebAppClassLoader负责加载本身的目录下的class文件,加载不到时再交给CommonClassLoader加载,这和双亲委派刚好相反。

    6、JVM故障处理

    6.1、基础故障处理工具6.1.1、jps:虚拟机进程状况工具

    jps(JVM Process Status Tool),它的功能与 ps 命令类似,可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class,main()函数所在的类)

    名称以及这些进程的本地虚拟机唯一 ID ( Local Virtual Machine Identifier,LVMID),类似于 ps -ef | grep java 的功能。

    命令格式

    jps [ options ] [ hostid ]

     options:选项、参数,不同的参数可以输出需要的信息

     hostid:远程查看

    选项列表描述
    -q只输出进程 ID,忽略主类信息
    -l输出主类全名,或者执行 JAR 包则输出路径
    -m输出虚拟机进程启动时传递给主类 main()函数的参数
    -v输出虚拟机进程启动时的 JVM 参数
    6.1.2、jstat:虚拟机统计信息监视工具

    jstat(JVM Statistics Monitoring Tool),用于监视虚拟机各种运行状态信息。它可以查看本地或者远程虚拟机进程中,类加载、内存、垃圾收集、即时编译等运行时数据。

    命令格式

    jstat -

  • vmid:如果是查看远程机器,需要按照此格式:
  • [protocol:][//]lvmid[@hostname[:port]/servername]

  • interval 和 count,表示查询间隔和次数,比如每隔 1000 毫秒查询一次进程 ID 的gc 收集情况,每次查询 5 次。jstat -gc 111552 1000 5
  • 选项列表:

    选项列表描述
    -class监视类加载、卸载数量、总空间以及类装载所耗费时长
    -gc监视 Java 堆情况,包括 Eden 区、2 个 Survivor 区、老年代、永久代或者 jdk1.8 元空间等,容量、已用空间、垃圾收集时间合计等信息
    -gccapacity监视内容与-gc 基本一致,但输出主要关注 Java 堆各个区域使用到的最大、最小空间
    -gcutil监视内容与-gc 基本相同,但输出主要关注已使用空间占总空间的百分比
    -gccause与 -gcutil 功能一样,但是会额外输出导致上一次垃圾收集产生的原因
    -gcnew监视新生代垃圾收集情况
    -gcnewcapacity监视内容与 -gcnew 基本相同,输出主要关注使用到的最大、最小空间
    -gcold监视老年代垃圾收集情况
    -gcoldcapacity监视内容与 -gcold 基本相同,输出主要关注使用到的最大、最小空间
    -compiler输出即时编译器编译过的方法、耗时等信息
    -printcompilation输出已经被即时编译的方法
    6.1.3、jinfo:Java配置信息工具

    jinfo(Configuration Info for Java),实时查看和调整 JVM 的各项参数。在上面讲到 jps -v 指令时,可以看到它把虚拟机启动时显式的参数列表都打印

    出来了,但如果想更加清晰的看具体的一个参数或者想知道未被显式指定的参数时,就可以通过 jinfo -flag 来查询了。

    命令格式

    jinfo [ option ] pid

    6.1.4、jmap:Java内存映像工具

    jmap(Memory Map for Java),用于生成堆转储快照(heapdump 文件)。

    jmap 的作用除了获取堆转储快照,还可以查询 finalize 执行队列、Java 堆和

    方法区的详细信息。

    命令格式

    jmap [ option ] pid

     option:选项参数

     pid:需要打印配置信息的进程 ID

     executable:产生核心 dump 的 Java 可执行文件

     core:需要打印配置信息的核心文件

     server-id:可选的唯一 id,如果相同的远程主机上运行了多台调试服务器,用此选

    项参数标识服务器

     remote server IP or hostname:远程调试服务器的 IP 地址或主机名

    选项描述
    -dump生成 Java 堆转储快照。
    -finalizerinfo显示在 F-Queue 中等待 Finalizer 线程执行 finalize 方法的对象。Linux平台
    -heap显示 Java 堆详细信息,比如:用了哪种回收器、参数配置、分代情况。Linux 平台
    -histo显示堆中对象统计信息,包括类、实例数量、合计容量
    -permstat显示永久代内存状态,jdk1.7,永久代
    -F当虚拟机进程对 -dump 选项没有响应式,可以强制生成快照。Linux平台
    6.1.5、jhat:虚拟机堆转储快照分析工具

    jhat(JVM Heap Analysis Tool),与 jmap 配合使用,用于分析 jmap 生成的堆转储快照。

    jhat 内置了一个小型的 http/web 服务器,可以把堆转储快照分析的结果,展示在浏览器中查看。不过用途不大,基本大家都会使用其他第三方工具。

    命令格式

    jhat [-stack ] [-refs ] [-port ] [-baseline ] [-

    debug ] [-version] [-h|-help]

    6.1.6、jstack:Java堆栈跟踪工具

    jstack(Stack Trace for Java),用于生成虚拟机当前时刻的线程快照(threaddump、javacore)。

    线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的目的通常是定位线程出现长时间停顿的原因,如:线程死锁、死循环、请求

    外部资源耗时较长导致挂起等。

    线程出现听顿时通过 jstack 来查看各个线程的调用堆栈,就可以获得没有响应的线程在搞什么鬼。

    命令格式

    jstack [ option ] vmid

    选项参数:

    选项描述
    -F当正常输出的请求不被响应时,强制输出线程堆栈
    -l除了堆栈外,显示关于锁的附加信息
    -m如果调用的是本地方法的话,可以显示 c/c++的堆栈
    6.2、可视化故障处理工具

    JDK中除了附带大量的命令行工具外,还提供了几个功能集成度更高的可视化工具,用户可以使用这些可视化工具以更加便捷的方式进行进程故障诊断和调试工作。这类工具主要包括JConsole、 JHSDB、VisualVM和JMC四个。

    6.2.1、JHSDB:基于服务性代理的调试工具

    JDK中提供了JCMD和JHSDB两个集成式的多功能工具箱,它们不仅整合了所有 基础工具所能提供的专项功能,而且由于有着“后发优势”,能够做得往往比之前的老工具们更好、更强大。

    JHSDB是一款基于服务性代理(Serviceability Agent,SA)实现的进程外调试工具。

    使用以下命令进入JHSDB的图形化模式,并使其附加进程11180:

    jhsdb hsdb --pid 11180

    命令打开的JHSDB的界面:

    image-20210214140518407
    6.2.2、JConsole:Java监视与管理控制台

    JConsole(Java Monitoring and Management Console)是一款基于JMX(Java Manage-ment Extensions)的可视化监视、管理工具。它的主要功能是通过JMX的MBean(Managed Bean)对系统进 行信息收集和参数动态调整。

    JConsole连接页面 :

    image-20210214140713141

    通过JDK/bin目录下的jconsole.exe启动JCon-sole后,会自动搜索出本机运行的所有虚拟机进程

    image-20210214140905009
    6.2.3、VisualVM:多合-故障处理工具

    VisualVM(All-in-One Java Troubleshooting Tool)是功能最强大的运行监视和故障处理程序之一, 曾经在很长一段时间内是Oracle官方主力发展的虚拟机故障处理工具。

    它除了常规的运行监视、故障处理外,还可以做性能分析等工作。因为它的通用性很强,对应用程序影响较小,所以可以直接接入到生产环境中。

    VisualVM的插件可以手工进行安装,在网站上下载nbm包后,点击“工具->插件->已下载”菜单,然后在弹出对话框中指定nbm包路径便可完成安装。

    VisualVM插件页签:

    image-20210214141116343
    6.2.4、Java Mission Control:可持续在线的监控工具

    JMC最初是BEA公司的产品,因此并没有像VisualVM那样一开始就基于自家的Net-Beans平台来开发,而是选择了由IBM捐赠的Eclipse RCP作为基础框架,现在的JMC不仅可以下载到独立程序,更常见的是作为Eclipse的插件来使用。JMC与虚拟机之间同样采取JMX协议进行通信,JMC一方面作为 JMX控制台,显示来自虚拟机MBean提供的数据;另一方面作为JFR的分析工具,展示来自JFR的数据。

    JMC的主界面如图:

    image-20210214141410375

    本文是作者结合一些常见面试题学习周志朋老师《深入理解Java虚拟机:JVM高级特性与最佳实践》的整理。这本书是非常经典的JVM书籍,也是一部七百多页的大部头,强烈建议有空仔细研读这本书籍,来学习更多JVM的特性和细节。



    参考:

    【1】:周志朋编著 《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版》

    【2】:JavaGuide 搞定大厂jvm面试

    【3】:小傅哥编著 《Java面经手册》

    【4】:Java内存管理-JVM内存模型以及JDK7和JDK8内存模型对比总结(三)          


    进入JVM的世界:《深入理解JVM虚拟机》-- 思维导图

    进入JVM的世界:《深入理解JVM虚拟机》-- 思维导图

    在工作的时候,其实很少会需要使用到JVM的时候,因而一直都是零零散散的看了些JVM的知识。于是便抽空看了一下这本神书,阅罢,醍醐灌顶、豁然开朗。真正的是知其然,更知其所以然。当然,看完了书,知识还不是自己的,只有留在自己的脑袋里面的,才是自己的。因此我整理了一份思维导图,希望自己有时间的时候,就多看看,多想想。巩固记忆。

    大图地址:https://img2018.cnblogs.com/blog/785907/201901/785907-20190106130914421-1214758103.png

    码云项目(含思维导图源文件)地址:https://gitee.com/cjh95/jvm-mindmap
    喜欢请star~

    技术分享图片
    思维导图



    以上是关于这些不可不知的JVM知识,我都用思维导图整理好了的主要内容,如果未能解决你的问题,请参考以下文章

    进入JVM的世界:《深入理解JVM虚拟机》-- 思维导图

    当初为了有机会进大厂,帅地狠心复习了这9门核心知识,熬夜整理成思维导图送给大家

    思维导图之JVM运行时数据区域

    轻松认识JVM运行时数据区域(使用思维导图)

    Scala思维导图

    善用思维导图来整理发散的思维