JVM内存模型及垃圾回收器

JVM

什么是JVM?

定义:

JVM - Java Virtual Machine (java程序的运行环境)

优点:

  • 一次编译,到处运行,跨平台
  • 自动内存管理,垃圾回收功能
  • 数组下标越界检查

JVM,JRE.JDK 三者之间的关系

  • JDK

JDK (Java Development Kit) 是 Java 语言的软件开发工具包(SDK)。 在JDK的安装目录下有一个jre目录,里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib合起来就称为jre。

  • JRE

JRE(Java Runtime Environment,Java运行环境),包含JVM标准实现及Java核心类库。JRE是Java运行环境,并不是一个开发环境,所以没有包含任何开发工具(如编译器和调试器)

  • JVM

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

四种引用

以下四种引用强度逐渐减弱

  1. 强引用

    类似于 Object obj = new Object(); 创建的,只要强引用在就不回收。
    测试代码:

    package com.zh.quote;
       
       import java.lang.ref.SoftReference;
       
       /**
        * @Description: 测试JVM 的四种引用
        * @ClassName QuoteDemo01
        * @date: 2021.05.19 10:47
        * @Author: zhanghang
        */
       public class QuoteDemo01 {
           // 测试强引用在内存不足时是否会被垃圾回收器回收
           // JVM参数 -Xms10m -Xmx10m -XX:+PrintGCDetails
           public static void main(String[] args) {
               // 直接new 代表了强引用
               Object obj = new Object();
               System.out.println("obj-------------->"+obj);
       
               try {
                   byte[] b = new byte[30 * 1024 * 1024];
               } catch (Exception e) {
                   // TODO: handle exception
               } finally {
                   System.out.println("obj-------------->"+obj);
               }
           }
       }
       
       /**
       Connected to the target VM, address: '127.0.0.1:0', transport: 'socket'
       [GC (Allocation Failure) [PSYoungGen: 2048K->496K(2560K)] 2048K->955K(9728K), 0.0010165 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
       obj-------------->java.lang.Object@16c0663d
       [GC (Allocation Failure) [PSYoungGen: 1891K->496K(2560K)] 2350K->1192K(9728K), 0.0017941 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
       [GC (Allocation Failure) [PSYoungGen: 496K->512K(2560K)] 1192K->1224K(9728K), 0.0008540 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
       [Full GC (Allocation Failure) [PSYoungGen: 512K->0K(2560K)] [ParOldGen: 712K->1072K(7168K)] 1224K->1072K(9728K), [Metaspace: 3173K->3173K(1056768K)], 0.0115302 secs] [Times: user=0.09 sys=0.00, real=0.01 secs] 
       [GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 1072K->1072K(9728K), 0.0003809 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
       [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 1072K->1057K(7168K)] 1072K->1057K(9728K), [Metaspace: 3173K->3173K(1056768K)], 0.0126000 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
       obj-------------->java.lang.Object@16c0663d
       Heap
        PSYoungGen      total 2560K, used 101K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
         eden space 2048K, 4% used [0x00000000ffd00000,0x00000000ffd19788,0x00000000fff00000)
         from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
         to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
        ParOldGen       total 7168K, used 1057K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
         object space 7168K, 14% used [0x00000000ff600000,0x00000000ff708450,0x00000000ffd00000)
        Metaspace       used 3205K, capacity 4556K, committed 4864K, reserved 1056768K
         class space    used 342K, capacity 392K, committed 512K, reserved 1048576K
       Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
       	at com.zh.quote.QuoteDemo01.main(QuoteDemo01.java:19)
       Disconnected from the target VM, address: '127.0.0.1:0', transport: 'socket'
       
       Process finished with exit code 1
       */

结论:强引用在发生内存溢出的情况也不会回收
2. 软引用
特点:内存不足时(自动触发GC),会被回收
SoftReference 类实现软引用。在系统要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行二次回收。
测试代码:

  package com.zh.quote;
       
       import java.lang.ref.SoftReference;
       
       /**
        * @Description: 测试JVM 的四种引用
        * @ClassName QuoteDemo01
        * @date: 2021.05.19 10:47
        * @Author: zhanghang
        */
       public class QuoteDemo02 {
           // 测试软引用在内存不足时是否会被垃圾回收器回收
           // JVM参数 -Xms10m -Xmx10m -XX:+PrintGCDetails
           public static void main(String[] args) {
               Object obj = new Object();
               SoftReference<Object> softRef = new SoftReference<Object>(obj);
               System.out.println("obj-------------->"+obj);
               System.out.println(softRef.get());
               // 对象要设置为null,否则不会被回收。原因:通过设置为null让对象失去引用,方便GC
               // 备注:因为在这个main方法中(主线程),方法未结束之前,不设置为null,对象是不会失去引用的。
               obj = null;
               // 当内存不足时,会自动触发GC操作,这里就无需手动GC
               try {
                   byte[] b = new byte[30 * 1024 * 1024];
               } catch (Exception e) {
                   // TODO: handle exception
               } finally {
                   System.out.println("obj-------------->"+obj);
                   System.out.println(softRef.get());
               }
           }
       }
       
       
       /**
       Connected to the target VM, address: '127.0.0.1:0', transport: 'socket'
       [GC (Allocation Failure) [PSYoungGen: 2048K->512K(2560K)] 2048K->952K(9728K), 0.0012077 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
       obj-------------->java.lang.Object@16c0663d
       java.lang.Object@16c0663d
       [GC (Allocation Failure) [PSYoungGen: 1906K->512K(2560K)] 2347K->1133K(9728K), 0.0018017 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
       [GC (Allocation Failure) [PSYoungGen: 512K->496K(2560K)] 1133K->1185K(9728K), 0.0009132 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
       [Full GC (Allocation Failure) [PSYoungGen: 496K->0K(2560K)] [ParOldGen: 689K->924K(7168K)] 1185K->924K(9728K), [Metaspace: 3174K->3174K(1056768K)], 0.0116758 secs] [Times: user=0.11 sys=0.00, real=0.01 secs] 
       [GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 924K->924K(9728K), 0.0004310 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
       [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 924K->908K(7168K)] 924K->908K(9728K), [Metaspace: 3174K->3174K(1056768K)], 0.0117938 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
       obj-------------->null
       null
       Heap
        PSYoungGen      total 2560K, used 112K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
         eden space 2048K, 5% used [0x00000000ffd00000,0x00000000ffd1c098,0x00000000fff00000)
         from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
         to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
        ParOldGen       total 7168K, used 908K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
         object space 7168K, 12% used [0x00000000ff600000,0x00000000ff6e31c8,0x00000000ffd00000)
        Metaspace       used 3206K, capacity 4556K, committed 4864K, reserved 1056768K
         class space    used 342K, capacity 392K, committed 512K, reserved 1048576K
       Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
       	at com.zh.quote.QuoteDemo02.main(QuoteDemo02.java:24)
       Disconnected from the target VM, address: '127.0.0.1:0', transport: 'socket'
       
       Process finished with exit code 1
       
       */

结论:软引用在内存发生内存溢出之前会被回收

  1. 弱引用
    特点:无论内存是否充足,只要进行GC,都会被回收
    WeakReference 类实现弱引用。对象只能生存到下一次垃圾收集之前。在垃圾收集器工作时,无论内存是否足够都会回收掉只被弱引用关联的对象
    package com.zh.quote;
       
       import java.lang.ref.WeakReference;
       
       /**
        * @Description: 测试JVM 的四种引用
        * @ClassName QuoteDemo01
        * @date: 2021.05.19 10:47
        * @Author: zhanghang
        */
       public class QuoteDemo03 {
           // 测试弱引用在内存不足时是否会被垃圾回收器回收
           public static void main(String[] args) {
               Object obj = new Object();
               WeakReference<Object> weakRef = new WeakReference<Object>(obj);
               System.out.println("obj-------------->"+obj);            // java.lang.Object@7852e922
               System.out.println(weakRef.get());    // java.lang.Object@7852e922
               // 对象要设置为null,否则不会被回收。原因:通过设置为null让对象失去引用,方便GC
               // 备注:因为在这个main方法中(主线程),方法未结束之前,不设置为null,对象是不会失去引用的。
               obj = null;
               // 这里通过手动触发GC操作。否则内存充足的情况下很难自动触发GC
               System.gc();
       
               System.out.println("obj-------------->"+obj);            // null
               System.out.println(weakRef.get());    // null
           }
       }
       
       /**
       Connected to the target VM, address: '127.0.0.1:0', transport: 'socket'
       obj-------------->java.lang.Object@66048bfd
       java.lang.Object@66048bfd
       obj-------------->null
       null
       Disconnected from the target VM, address: '127.0.0.1:0', transport: 'socket'
       */

结论:无论内存是否充足,只要进行GC,都会被回收
4. 虚引用
特点:如同虚设,和没有引用没什么区别
PhantomReference 类实现虚引用。无法通过虚引用获取一个对象的实例,为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知
虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。

   要注意的是,虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
 package com.zh.quote;
       
       import java.lang.ref.PhantomReference;
       import java.lang.ref.ReferenceQueue;
       import java.lang.ref.WeakReference;
       
       /**
        * @Description: 测试JVM 的四种引用
        * @ClassName QuoteDemo01
        * @date: 2021.05.19 10:47
        * @Author: zhanghang
        */
       public class QuoteDemo04 {
           // 测试虚引用在内存不足时是否会被垃圾回收器回收
           public static void main(String[] args) {
               String obj = "obj";
               ReferenceQueue<String> queue = new ReferenceQueue<String>();
               PhantomReference<String> pr = new PhantomReference<String>(obj, queue);
               System.out.println(pr.get());  // null
           }
       }
       
       /**
       Connected to the target VM, address: '127.0.0.1:0', transport: 'socket'
       null
       Disconnected from the target VM, address: '127.0.0.1:0', transport: 'socket'
       */

结论: 虚引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被finalize以后,做某些事情的机制。PhantomRefrence的get方法总是返回null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入finalization阶段,可以被GC回收,用来实现比finalization机制更灵活的回收操作。
换句话说,设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理。Java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除之前做必要的清理工作。

GC的基础知识

1,什么事垃圾
  1. 就是一个对象没有被任何地址引用
  2. 多个对象相互应用,但是没有被其他变量所引用
2,如何定义垃圾
  1. 引用计数,当一个对象被引用一次,则计数器加1,当一个对象的计数器为0时代表这个对象是垃圾对象
    注意: 但是当对象之间循环引用时,引用计数法并不会认定他们为垃圾对象
  2. GC Roots(根可达算法)
    从 线程栈变量(启动时main方法的栈),静态变量,常量池,JNI指针为根部,向下寻找,对于连不上的对象都是垃圾对象(JVM使用的算法)

在这里插入图片描述

3,常见的垃圾回收算法
  • Mark-Sweep (标记清除)

    • 位置不连续,产生碎片
      在这里插入图片描述
  • Copying (拷贝)

    • 没有碎片,效率高,但是可用空间为一半,浪费空间
      在这里插入图片描述
  • Mark-Compact (标记压缩)

    • 空间利用率较高,但是效率比较低

在这里插入图片描述

4,JVM 内存分代模型(用于分代垃圾回收算法)

在这里插入图片描述

  1. 部分垃圾回收期使用的模型

  2. 新生代 + 老年 + 永久代(1.7)/ 元数据区(1.8)Metaspce
    

    永久代和元数据区的区别:

    1. 永久代和元数据区 都是装 Class对象
    2. 永久代 必须指定大小限制,元数据区可以设置,也可以不设置,无上限(受限于物理内存)
    3. JDK1.7中 字符串常量 存在永久代,JDK1.8 中字符串常量存在 堆中
    4. MethodArea(方法区) 是一个逻辑概念,永久代和元数据是他的具体实现
    5. 永久代时在堆中开辟一块内存实现方法区,而元数据这是在本地内存中开辟一块内存实现方法区
  3. 新生代 = Eden + 2个suvivor区 ,

    1. 新生代的GC 为YGC,使用的是Copying回收算法
    2. YGC回收之后,大多数对象会被回收,活着的进入 s0
    3. 再次YGC ,活着的对象 Eden + s0 会进入 s1 区,对象的年龄加一
    4. 再次YGC,活着的对象 Eden + s1 会进入 s0区,对象的年龄加一
    5. 年龄足够的对象 会进入 老年代(15岁, CMS(6岁))
    6. s区装不下,会将装不下的对象直接进入老年代
  4. 老年代

    1. 老年代的GC 为 FGC (Full GC)使用的是Mark-Compact 标记压缩算法
  5. GC Tuning(Generation) : GC 调优

    1. GC 调优 就是尽量减少 Full GC
    2. MinorGC = YGC
    3. MajorGC = FullGC

    堆内存逻辑分区
    在这里插入图片描述

5,常见的垃圾回收器

在这里插入图片描述

  1. Serial: 运用在年轻代,串行回收,
  • 是一个单线程的,当发生YGC的时候,所有用户线程停止,YGC完成后,用户线程继续执行
    在这里插入图片描述
  1. Parallel Scavenge: 运用在年轻代,并行回收

    • 多个线程同时回收
      在这里插入图片描述
  2. ParNew: 运用在年轻代,配合CMS 的并行回收
    在这里插入图片描述

  3. SerialOld: 运用在老年代,和Serial是单线程串行执行

  4. ParallelOld:运用在老年代,和ParallelOld一样是多线程并行执行

  5. Concurrent mark sweep: 运用在老年代,他是多线程并行执行,并且垃圾回收和应用程序同时运行,降低STW的事件(200ms)

    • CMS 既然是Mark Sweep,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代 分配对象分配不下的时候,使用 SerialOld 进行老年代回收
    • 并发标记使用的算法: 三色标记 + Incremental Update
    • Incremental Update会出现的问题是当A已经标记过C,在标记B的时候,应用程序也在同时并发,C引用了D,B不引用了D,A标记完B以为标记完了所有子类,所以A变成了黑色,导致了D被漏标
      在这里插入图片描述
  6. G1(100ms以内): 不区分年轻代和老年代,都是用这一个垃圾回收器

    • 算法:三色标记 + SATB
  7. ZGC(10ms 以内):不区分年轻代和老年代,都是用这一个垃圾回收器

  8. Shenandoah:

  9. Eplison:
    JDK1.8默认的垃圾回收器使用的是:Parallel Scavenge + ParallelOld

  • 三色标记算法
    在这里插入图片描述

    • 就是并发标记算法
    • 会从根部搜索,,如图所示,当A 并且A 的所有子类或直接引用被标记,这A标记为黑色,当标记到本身,这本身为灰色,如B,还没有被标记到的则为白色,
  • STAB算法

    使用SATB算法(snapshot-at-the-beginning 快照)
    刚开始做一个快照,当 B 和 C 引用消失的时候要把这个引用推到 GC 的堆栈,保证 C 还能被 GC 扫描到,最重要的是要把这个引用推到 GC 的堆栈,是灰色对象指向白色的引用,如果一旦某一个引用消失掉了,我会把它放到栈(GC 方法运行时数据也是来自栈中),我其实还是能找到它的,我下回直接扫描他 就行了,那样白色就不会漏标。
    对应 G1 的垃圾回收过程中的:
    最终标记( Final Marking):对用户线程做另一个短暂的暂停,用于处理并发阶段结后仍遗留下来的最后那少量的 SATB 记录(漏标对象)。
    在这里插入图片描述

    注意:圈红的位置,触发1线程重新扫描的是B少了一个引用。即使用SATB关注的是引用关系的删除。
    小结:SATB通过快照记录引用关系,一旦发现有引用删除,通过查看快照记录的引用关系,重新标记
    Incremental Update算法和SATB算法对比
    SATB 算法是关注引用的删除。(B->C 的引用),而Incremental Update 算法关注引用的增加。(A->C 的引用)。
    G1 如果使用 Incremental Update 算法,因为变成灰色的成员还要重新扫,重新再来一遍,效率太低了。 所以 G1 在处理并发标记的过程比 CMS 效率要高,这个主要是解决漏标的算法决定的。

6,JVM 调优第一步,了解生成环境虾的垃圾回收期组合

Serial -> Serial Old

ParNew -> CMS

Parallel -> Parallel  Old

常用调优

常见设置参数
  • JVM 的命令行参数参考:

  • JVM 参数分类

    标准: - 开头,所有的HotSpot都支持
    非标准: -X 开头,特定版本HotSpot支持特定命令
    不稳定: -XX 开头, 下个版本可能取消

    JVM 常用设置参数命令

配置参数说明示例
-Xmx设置最大堆大小-Xmx3550m,设置JVM最大可用内存为3550 MB
-Xms设置JVM初始内存-Xms3550m,设置JVM初始内存为3550 MB。此值建议与-Xmx相同,避免每次垃圾回收完成后JVM重新分配内存。
-Xmn设置年轻代大小-Xmn2g,设置年轻代大小为2 GB。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64 MB,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss设置线程的堆栈大小-Xss128k,设置每个线程的堆栈大小为128 KB。说明 JDK5.0版本以后每个线程堆栈大小为1MB,JDK5.0以前版本每个线程堆栈大小为256 KB。请依据应用的线程所需内存大小进行调整。在相同物理内存下,减小该值可以生成更多的线程。但是操作系统对一个进程内的线程个数有一定的限制,无法无限生成,一般在3000个~5000个左右。
-XX:NewRatio=n设置年轻代和老年代的比值-XX:NewRatio=4,设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。如果设置为4,那么年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=n年轻代中Eden区与两个Survivor区的比值
-XX:MaxPermSize=n设置持久代大小
-XX:MaxPermSize=16m,设置持久代大小为16 MB。
-XX:MaxTenuringThreshold=n设置对象最大年龄
-XX:MaxTenuringThreshold=0,设置垃圾最大年龄。

调优回收器GC

配置参数说明示例
-XX:+UseParallelGC选择垃圾收集器为并行收集器。-Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20,-XX:+UseParallelGC此配置仅对年轻代有效,即在示例配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
-XX:ParallelGCThreads配置并行收集器的线程数,即同时多少个线程一起进行垃圾回收。说明 此值建议配置与处理器数目相等。-Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20,-XX:ParallelGCThreads=20表示配置并行收集器的线程数为20个。
-XX:+UseParallelOldGC配置年老代垃圾收集方式为并行收集。说明 JDK6.0支持对年老代并行收集。-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC,-XX:+UseParallelOldGC表示对年老代进行并行收集。
-XX:MaxGCPauseMillis设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100,-XX:MaxGCPauseMillis=100设置每次年轻代垃圾回收的最长时间为100 ms。
-XX:+UseAdaptiveSizePolicy设置此选项后,并行收集器自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低响应时该间或者收集频率,该值建议使用并行收集器时,并且一直打开。-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
-XX:+PrintCommandLineFlags打印出系统启动时的系统命令行参数
-XX:+PrintFlagsFinal打印出系统最终参数值
-XX:+PrintFlagsInitial打印出系统默认参数值
-XX:+HeapDumpOnOutOfMemoryError当堆栈溢出时dump出内存文件
-XX:+PrintGCDetails打印出系统GC的调用情况

在LInux中常见的命令

使用JDK自带的命令

jps

说明:列出系统中所有的Java进程,ID号 和 进程名

在这里插入图片描述

jinfo + ID号

==说明:列出指定进程的详细信息 ==
在这里插入图片描述

jstat -gc + ID号 [毫秒数]

说明: 列出指定进程的各个内存使用情况,如果加上毫秒数这代表每多少毫秒输出一次信息

在这里插入图片描述

top [-] [d delay] [q] [c] [S] [s] [i] [n]

这个命令是linux自带的命令,用来监控系统资源

主要参数:

d:指定更新的间隔,以秒计算。
q:没有任何延迟的更新。如果使用者有超级用户,则top命令将会以最高的优先序执行。
c:显示进程完整的路径与名称。
S:累积模式,会将己完成或消失的子行程的CPU时间累积起来。
s:安全模式。
i:不显示任何闲置(Idle)或无用(Zombie)的行程。
n:显示更新的次数,完成后将会退出top

top -Hp + 进程ID号

说明:列出指定进程中的线程消耗资源的情况

在这里插入图片描述

jstack + 进程ID号

说明:可以跟踪指定进程中的线程的信息打印出来,比如线程的名字,状态等
在这里插入图片描述

jmap -histo + 进程ID号

说明:会打印出指定Java进程中有哪些类,被创建了多少对象。占用了多少资源
在这里插入图片描述

jmap -dump:format=b,file=2021516.hprof + 进程ID号

说明:将堆栈信息以二进制的格式导出到2021516.hprof文件中,可以离线的分析问题

分析工具: Jvisualvm.exe
在这里插入图片描述

双击打开之后可以将刚导出的离线的文件导入工具中,可以图形化的看出

类加载器

作用

将Class文件加载到JVM 中

过程

类的生命周期如下图:
在这里插入图片描述

  1. 加载
    1. 通过全限定类名来获取定义此类的二进制字节流。
    2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
    3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。
  2. 验证
    验证是连接阶段的第一步,这一阶段的目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
    1. 文件格式验证:如是否以魔数 0xCAFEBABE 开头、主、次版本号是否在当前虚拟机处理范围之内、常量合理性验证等。
      此阶段保证输入的字节流能正确地解析并存储于方法区之内,格式上符合描述一个 Java类型信息的要求。
    2. 元数据验证:是否存在父类,父类的继承链是否正确,抽象类是否实现了其父类或接口之中要求实现的所有方法,字段、方法是否与父类产生矛盾等。
      第二阶段,保证不存在不符合 Java 语言规范的元数据信息。
    3. 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。例如保证跳转指令不会跳转到方法体以外的字节码指令上。
    4. 符号引用验证:在解析阶段中发生,保证可以将符号引用转化为直接引用。
      可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间
  3. 准备
    为类变量分配内存并设置类变量初始值,这些变量所使用的内存都将在方法区中进行分配
  4. 解析
    虚拟机将常量池内的符号引用替换为直接引用的过程。
    解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类符号引用进行
  5. 初始化
    到初始化阶段,才真正开始执行类中定义的 Java 程序代码,此阶段是执行 () 方法的过程。
    () 方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生的。(不包括构造器中的语句。构造器是初始化对象的,类加载完成后,创建对象时候将调用的 () 方法来初始化对象)

类加载的时机

对于初始化阶段,虚拟机规范规定了有且只有 5 种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之前开始):

  1. 遇到new、getstatic 和 putstatic 或 invokestatic 这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。对应场景是:使用 new 实例化对象、读取或设置一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)、以及调用一个类的静态方法。
  2. 对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
  3. 当初始化类的父类还没有进行过初始化,则需要先触发其父类的初始化。(而一个接口在初始化时,并不要求其父接口全部都完成了初始化)
  4. 虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),
    虚拟机会先初始化这个主类。
  • 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

第5种情况,我暂时看不懂。

以上这 5 种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用,例如:

  1. 通过子类引用父类的静态字段,不会导致子类初始化
  2. 通过数组定义来引用类,不会触发此类的初始化。MyClass[] cs = new MyClass[10];
  3. 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化

类加载器

  1. 虚拟机自带的加载器
  2. 启动类(根)加载器 (BOOT)
  3. 扩展类加载器 (EXC)
  4. 应用程序加载器(App)

双亲委派模型

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

在这里插入图片描述

在这里插入图片描述

程序计数器

内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成

如果线程正在执行一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器的值则为 (Undefined)。此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域.

Java 虚拟机栈

线程私有生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。

局部变量表:存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型)和 returnAddress 类型(指向了一条字节码指令的地址)

StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。
OutOfMemoryError:如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存。

本地方法栈

区别于 Java 虚拟机栈的是,Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。也会有 StackOverflowError 和 OutOfMemoryError 异常。

方法区

属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

Java 堆

对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续

OutOfMemoryError:如果堆中没有内存完成实例分配,并且堆也无法再扩展时,抛出该异常。

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值