JVM--Java虚拟机

java虚拟机——java代码的执行过程 在Java虚拟机(JVM)中,Java代码的执行过程是一个复杂但有序的过程。 阅读详情

0. java代码的执行过程

了解Java虚拟机(JVM)首先需要了解一下一段Java代码的具体执行过程。

  • Java代码的具体执行过程如下:
    • 执行 javac 命令编译源代码为字节码
    • 执行 java 命令,二进制字节码通过解释器翻译为机器码
      1. 创建 JVM,调用类加载子系统加载 class,将类的信息存入方法区
      2. 创建 main 线程,使用的内存区域是 JVM 虚拟机栈,开始执行 main 方法代码
      3. 如果遇到了未见过的类,会继续触发类加载过程,同样会存入方法区
      4. 需要创建对象,会使用堆内存来存储对象
      5. 不再使用的对象,会由垃圾回收器在内存不足时回收其内存
      6. 调用方法时,方法内的局部变量、方法参数所使用的是 JVM 虚拟机栈中的栈帧内存
      7. 调用方法时,先要到方法区获得到该方法的字节码指令,由解释器将字节码指令解释为机器码执行
      8. 调用方法时,会将要执行的指令行号读到程序计数器,这样当发生了线程切换,恢复时就可以从中断的位置继续
      9. 对于非 java 实现的方法调用,使用内存称为本地方法栈(见说明)
      10. 对于热点方法调用,或者频繁的循环代码,由 JIT 即时编译器将这些代码编译成机器码缓存,提高执行性能
    • 将翻译的机器码交给cpu去运行

其中加粗的字体就是JVM的组件。

1. JVM组件

在这里插入图片描述

1.1.程序计数器

  • 作用:记住下一条jvm指令的执行地址
  • 特点:
    • 线程私有
    • 不会存在内存溢出

1.2. Java Virtual Machine Stacks (Java虚拟机栈)

  • 每个线程运行时所需要的内存,称为虚拟机栈。
  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
  • 栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的。8种基本类型的变量+对象的引用变量+实例方法都是在函数的栈内存中分配。
  • 常见问题栈溢出:Exception in thread “main” java.lang.StackOverflowError

1.2.1 栈帧

  • 栈中的数据都是以**栈帧(Stack Frame)**的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集。栈帧是每个方法运行时需要的内存
    • 每执行一个方法都会产生一个栈帧,保存到栈(后进先出)的顶部,顶部栈就是当前的方法,该方法执行完毕后会自动将此栈帧出栈。
    • 栈帧中主要保存3 类数据:
      • 本地变量(Local Variables):输入参数和输出参数以及方法内的变量。
      • 栈操作(Operand Stack):记录出栈、入栈的操作。
      • 栈帧数据(Frame Data):包括类文件、方法等等。

1.2.2本地方法栈(Native Method Stacks):

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。

  • 凡是带了native 关键字的,说明java的作用范围达不到了,回去调用底层c语言的库!
    private native void starte(); //thread.start()的底层实现
    • 进入本地方法栈,然后调用JNI本地方法接口
      • JNI作用:扩展Java的使用,融合不同的编程语言为Java所用。

1.3. 堆(Heap)

Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

  • 堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间、From Survivor空间、To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配;

1.3.1 新生代

  • 新生区是对象的诞生、成长、消亡的区域,一个对象在这里产生,应用,最后被垃圾回收器收集,结束生命。一般占据堆的 1/3 空间。
  • 新生代又分为 Eden 区、SurvivorFrom 、SurvivorTo三个区。
    • **Eden 区(伊甸园区):**Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。

    • **SurvivorFrom (幸存0区):**上一次 GC 的幸存者,作为这一次 GC 的被扫描者。

    • **SurvivorTo(幸存1区):**保留了一次 MinorGC 过程中的幸存者

  • 由于频繁创建对象,所以新生代会频繁触发 MinorGC 进行垃圾回收。
    • 即当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收**(Minor GC(轻GC))**,将伊甸园区中的不再被其他对象所引用的对象进行销毁。然后将伊甸园中的剩余对象移动到幸存 From区。

1.3.2 老年代

老年代:主要存放应用程序中生命周期长的内存对象。老年代的对象比较稳定,所以**MajorGC(重GC)**不会频繁执行。在进行 MajorGC 前一般都先进行 了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。

  • MajorGC 采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC 的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出 OOM(Out of Memory)异常

1.4.方法区(元空间)

方法区存储类信息、常量、静态变量等数据,是线程共享的区域,为与Java堆区分,**方法区还有一个别名Non-Heap(非堆);**方法区是一种定义,不同的Java虚拟机的实现方式不同。

  • HotSpot VM把 GC 分代收集扩展至方法区,即使用 Java 堆的永久代来实现方法区,这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存,而不必为方法区开发专门的内存管理器(永久代的内存回收的主要目标是针对常量池的回收和类型的卸载,因此收益一般很小)。
  • 永久代是 Hotspot 虚拟机特有的概念,指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被放入永久区域,它和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。
    • 永久代是jdk1.8之前,Hotspot对方法区的具体实现。
  • 在 Java8 中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。
    • 元空间是jdk1.8以后,Hotspot对方法区的具体实现。
    • 元空间的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。
    • 类的元数据放入 native memory,字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由 MaxPermSize 控制, 而由系统的实际可用空间来控制。

2. JVM调优

2.1. 常用工具

2.1.1. 命令工具

2.1.1.1. JPS:JVM Process Status Tool,虚拟机进程状况工具
  • 显示指定系统内所有的HotSpot虚拟机进程。
  • 命令格式:jps [options] [hostid]
  • option参数
    • -l : 输出主类全名或jar路径

    • -q : 只输出LVMID

    • -m : 输出JVM启动时传递给main()的参数

    • -v : 输出JVM启动时显示指定的JVM参数

2.1.1.2. jstat 虚拟机统计信息监视工具

jstat(JVM statistics Monitoring)是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。

  • 命令格式:jstat [option] LVMID [interval] [count]

  • 参数

    • [option] : 操作参数

      • -class 监视类装载、卸载数量、总空间以及耗费的时间
        在这里插入图片描述
    • -compiler 输出JIT编译过的方法数量耗时等
      在这里插入图片描述

    • -gc 垃圾回收堆的行为统计,常用命令
      在这里插入图片描述

    • -gccapacity 同-gc,不过还会输出Java堆各区域使用到的最大、最小空间
      在这里插入图片描述

    • -gcutil 同-gc,不过输出的是已使用空间占总空间的百分比
      在这里插入图片描述

    • -gccause 垃圾收集统计概述(同-gcutil),附加最近两次垃圾回收事件的原因

    • -gcnew 统计新生代的行为

    • -gcnewcapacity 新生代与其相应的内存空间的统计

    • -gcold 统计旧生代的行为

    • -gcoldcapacity 统计旧生代的大小和空间

    • -gcpermcapacity 永生代行为统计

    • -printcompilation hotspot编译方法统计

  • LVMID : 本地虚拟机进程ID

  • [interval] : 连续输出的时间间隔

  • [count] : 连续输出的次数

2.1.1.3. jmap Java内存映像工具
  • jmap(JVM Memory Map)命令用于生成heap dump文件,如果不使用这个命令,还可以使用**-XX:+HeapDumpOnOutOfMemoryError参数来让虚拟机出现OOM的时候自动生成dump文件**。 jmap不仅能生成dump文件,还可以查询finalize执行队列、Java堆和永久代的详细信息,如当前使用率、当前使用的是哪种收集器等。
  • 命令格式:jmap [option] LVMID
  • option参数
    • dump : 生成堆转储快照

    • finalizerinfo : 显示在F-Queue队列等待Finalizer线程执行finalizer方法的对象

    • heap : 显示Java堆详细信息

    • histo : 显示堆中对象的统计信息

    • permstat : 打印Java堆内存的永久保存区域的类加载器的智能统计信息

    • F : 当-dump没有响应时,强制生成dump快照

  • 查看某个java进程所有参数:jinfo 进程号
  • 查看某个java进程总结性垃圾回收统计:jstat -gc 20292
2.1.1.4. jhat 虚拟机堆转储快照分析工具

jhat(JVM Heap Analysis Tool)命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看。在此要注意,一般不会直接在服务器上进行分析,因为jhat是一个耗时并且耗费硬件资源的过程,一般把服务器生成的dump文件复制到本地或其他机器上进行分析。

  • 命令格式:jhat [dumpfile]
  • 参数
    • -stack false|true 关闭对象分配调用栈跟踪(tracking object allocation call stack)。

      • 如果分配位置信息在堆转储中不可用. 则必须将此标志设置为 false. 默认值为 true.>
    • -refs false|true 关闭对象引用跟踪(tracking of references to objects)。

      • 默认值为 true. 默认情况下, 返回的指针是指向其他特定对象的对象,如反向链接或输入引用(referrers or incoming references), 会统计/计算堆中的所有对象。
    • -port port-number 设置 jhat HTTP server 的端口号. 默认值 7000

    • -exclude exclude-file 指定对象查询时需要排除的数据成员列表文件。

      • 例如, 如果文件列列出了 java.lang.String.value , 那么当从某个特定对象 Object o 计算可达的对象列表时, 引用路径涉及 java.lang.String.value 的都会被排除。>
    • -baseline exclude-file 指定一个基准堆转储(baseline heap dump)。

      • 在两个 heap dumps 中有相同 object ID 的对象会被标记为不是新的(marked as not being new). 其他对象被标记为新的(new). 在比较两个不同的堆转储时很有用.
    • -debug int 设置 debug 级别. 0 表示不输出调试信息。

      • 值越大则表示输出更详细的 debug 信息.
    • -version 启动后只显示版本信息就退出>

    • -J< flag > 因为 jhat 命令实际上会启动一个JVM来执行, 通过 -J 可以在启动JVM时传入一些启动参数.

      • 例如, -J-Xmx512m 则指定运行 jhat 的Java虚拟机使用的最大堆内存为 512 MB. 如果需要使用多个JVM启动参数,则传入多个 -Jxxxxxx.
2.1.1.5. jstack Java堆栈跟踪工具
  • jstack用于生成java虚拟机当前时刻的线程快照。
    • 线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。
    • 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。 如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题。另外,jstack工具还可以附属到正在运行的java程序中,看到当时运行的java程序的java stack和native stack的信息, 如果现在运行的java程序呈现hung的状态,jstack是非常有用的。
  • 命令格式:jstack [option] LVMID
  • option参数
    • -F : 当正常输出请求不被响应时,强制输出线程堆栈

    • -l : 除堆栈外,显示关于锁的附加信息

    • -m : 如果调用到本地方法的话,可以显示C/C++的堆栈

2.1.1.6. jinfo Java配置信息工具

jinfo(JVM Configuration info)这个命令作用是实时查看和调整虚拟机运行参数。 之前的jps -v口令只能查看到显示指定的参数,如果想要查看未被显示指定的参数的值就要使用jinfo口令

  • 命令格式:jinfo [option] [args] LVMID
  • option参数
    • -flag : 输出指定args参数的值

    • -flags : 不需要args参数,输出所有JVM参数的值

    • -sysprops : 输出系统属性,等同于System.getProperties()

2.1.2. 可视化工具

2.1.2.1. jconsole Java监视与管理控制台
  • JConsole(Java Monitoring and Management Console)是一种基于JMX的可视化监视、管理工具。

  • 通过JDK/bin目录下的jconsole.exe启动
    在这里插入图片描述
    在这里插入图片描述

2.1.2.2. jvisual VM工具:多合一故障处理工具

VisualVM(All-in-One Java Troubleshooting Tool)是目前为止JDK发布的功能最强大的运行监视和故障处理程序。

  • 通过JDK/bin目录下的jvisualvm.exe启动
    在这里插入图片描述
    在这里插入图片描述

  • 使用idea分析dump工具

    • 配置jvm参数
      在这里插入图片描述

    • 把上例中运行参数改成:

       -Xmx50m -Xms10m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\tmp 
      

      -XX:HeapDumpPath:生成dump文件路径。

    • 再次执行:生成C:\tmp\java_pid20328.hprof文件
      在这里插入图片描述

    • 使用jvisualvm工具打开hprof文件
      在这里插入图片描述

    • 文件–>装入–>选择要打开的文件即可
      在这里插入图片描述
      在这里插入图片描述

2.2. JVM常用参数

  • 标准参数(-),所有JVM都必须支持这些参数的功能,而且向后兼容;
  • 非标准参数(-X),默认JVM实现这些参数的功能,但是并不保证所有JVM实现都满足,且不保证向后兼容;
  • 非稳定参数(-XX),此类参数各个JVM实现会有所不同,将来可能会不被支持,需要慎重使用;

在这里插入图片描述

-Xss设置每个线程的堆栈大小。
-Xmx设置堆的最大空间大小。默认是内存的1/4
-Xms设置堆的初始空间大小,默认是内存的1/64
-XX:NewSize设置新生代最小空间大小。
-XX:MaxNewSize设置新生代最大空间大小。
-Xmn设置新生代堆大小,最大=最小
-XX:PermSize设置永久代最小空间大小。
-XX:MaxPermSize设置永久代最大空间大小。
-Xx:SurvivorRatio=ratio幸存区比例(伊甸区:survivor from区)
-XX:MaxTenuringThreshold=threshold新生代到老年代的晋升阈值
-XX:NewRatio=ratio老年代和新生代的比值(老年代:新生代)
-XX:+PrintTenuringDistribution晋升详情
-XX:+PrintGCDetails输出详细的GC处理日志
-XX:+PrintGCDetails -verbose:gcGc详情
-XX:+ScavengeBeforeFullGCFullGC前MinorGC

没有直接设置老年代的参数,但是可以设置堆空间大小和新生代空间大小两个参数来间接控制。
- 老年代空间大小=堆空间大小-年轻代大空间大小

2.3. jvm参数设置

java代码查看jvm堆的默认值大小:

Runtime.getRuntime().maxMemory()   // 堆的最大值,默认是内存的1/4
Runtime.getRuntime().totalMemory()  // 堆的当前总大小,默认是内存的1/64

如果是命令行运行:

 java -Xmx50m -Xms10m HeapDemo

在这里插入图片描述

2.4 测试/调优案例

  • 在终端中输入指令
    • 查看虚拟机运行参数
      • "D:\Program Files\Java\jdk1.8.0_144\bin\java”-XX:+PrintFlagsFinal -version | findstr “Gc”

2.4.1. 线程诊断

  • 线程CPU高占用查询
    • 用top命令定位那个进程对cpu的占用过高
    • ps H -e0 pid,tid,%cpu | grep 进程id,进一步定位那个线程对cpu占用过高
    • jstack 进程id =⇒列出当前进程的所有线程,对应线程id(10进制,,jstack列出的线程的id是十六进制)
  • 排查线程死锁问题
    • jstack 进程id,查看是否存在死锁,若存在查看导致死锁的具体线程和具体问题。

2.4.2. 查看堆内存详情

public class Demo2 {
   public static void main(String[] args) {

       System.out.print("最大堆大小:");
       System.out.println(Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");
       System.out.print("当前堆大小:");
       System.out.println(Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");
       System.out.println("==================================================");

       byte[] b = null;
       for (int i = 0; i < 10; i++) {
           b = new byte[1 * 1024 * 1024];
       }
   }
}
  • 执行前配置参数:-Xmx50m -Xms30m -XX:+PrintGCDetails
    在这里插入图片描述

    2.4.4. GC演示

public class HeapDemo {

   public static void main(String args[]) {

       System.out.println("=====================Begin=========================");
       System.out.print("最大堆大小:Xmx=");
       System.out.println(Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");

       System.out.print("剩余堆大小:free mem=");
       System.out.println(Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");

       System.out.print("当前堆大小:total mem=");
       System.out.println(Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");

       System.out.println("==================First Allocated===================");
       byte[] b1 = new byte[5 * 1024 * 1024];
       System.out.println("5MB array allocated");

       System.out.print("剩余堆大小:free mem=");
       System.out.println(Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");

       System.out.print("当前堆大小:total mem=");
       System.out.println(Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");

       System.out.println("=================Second Allocated===================");
       byte[] b2 = new byte[10 * 1024 * 1024];
       System.out.println("10MB array allocated");

       System.out.print("剩余堆大小:free mem=");
       System.out.println(Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");

       System.out.print("当前堆大小:total mem=");
       System.out.println(Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");

       System.out.println("=====================OOM=========================");
       System.out.println("OOM!!!");
       System.gc();
       byte[] b3 = new byte[40 * 1024 * 1024];
   }
}
  • jvm参数设置成最大堆内存100M,当前堆内存10M:-Xmx100m -Xms10m -XX:+PrintGCDetails,再次运行,可以看到minor GC和full GC日志:
    在这里插入图片描述

  • 把上面案例中的jvm参数改成最大堆内存设置成50M,当前堆内存设置成10M,执行测试: -Xmx50m -Xms10m

    =====================Begin=========================
      剩余堆大小:free mem=8.186859130859375M
    当前堆大小:total mem=9.5M
    =================First Allocated=====================
    5MB array allocated
    剩余堆大小:free mem=3.1868438720703125M
    当前堆大小:total mem=9.5M
    ================Second Allocated====================
    10MB array allocated
    剩余堆大小:free mem=3.68682861328125M
    当前堆大小:total mem=20.0M
    =====================OOM=========================
    OOM!!!
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
     at com.atguigu.demo.HeapDemo.main(HeapDemo.java:40)
    

3.垃圾回收(GC)

  • GC回收的是堆内存,不包含虚拟机栈,在方法调用结束后会自动释放方法的占用的内存
  • GC 的目的在于实现无用对象内存自动释放,减少内存碎片、加快分配速度
  • GC的特点:
    • 次数上频繁收集Young区
    • 次数上较少收集Old区
    • 基本不动Perm区

3.1. 垃圾判定

判断无用对象,使用可达性分析算法,三色标记法标记存活对象,回收未标记对象

3.1.1. 引用计数法(Reference-Counting)

  • 引用计数算法是通过判断对象的引用数量来决定对象是否可以被回收。 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。

优点:

  • 简单,高效,现在的objective-c、python等用的就是这种算法。

缺点:

  • 引用和去引用伴随着加减算法,影响性能
  • 很难处理循环引用,相互引用的两个对象则无法释放。
  • 因此目前主流的Java虚拟机都摒弃掉了这种算法。

3.1.2. 可达性分析算法(根搜索算法)

这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。
在这里插入图片描述

在Java语言中,可以作为GC Roots的对象包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中的引用对象。
  • 方法区中的类静态属性引用的对象。
  • 方法区中的常量引用的对象。
  • 本地方法栈中JNI(Native方法)的引用对象
    在这里插入图片描述
    真正标记以为对象为可回收状态至少要标记两次。
    • 第一次标记:不在 GC Roots 链中,标记为可回收对象。
    • 第二次标记:判断当前对象是否实现了finalize() 方法,如果没有实现则直接判定这个对象可以回收,如果实现了就会先放入一个队列中。并由虚拟机建立一个低优先级的程序去执行它,随后就会进行第二次小规模标记,在这次被标记的对象就会真正被回收了!

3.1.3. 三色标记和并发漏标

3.1.3.1. 三色标记

即用三种颜色记录对象的标记状态

  • 黑色 – 已标记

  • 灰色 – 标记中

  • 白色 – 还未标记

  • 具体过程:

    1. 起始的三个对象还未处理完成,用灰色表示
      在这里插入图片描述

    2. 该对象的引用已经处理完成,用黑色表示,黑色引用的对象变为灰色
      在这里插入图片描述

    3. 依次类推
      在这里插入图片描述

    4. 沿着引用链都标记了一遍
      在这里插入图片描述

    5. 最后为标记的白色对象,即为垃圾
      在这里插入图片描述

3.1.3.2. 并发漏标问题
  • 比较先进的垃圾回收器都支持并发标记,即在标记过程中,用户线程仍然能工作。但这样带来一个新的问题,如果用户线程修改了对象引用,那么就存在漏标问题。例如:
    1. 如图所示标记工作尚未完成
      在这里插入图片描述

    2. 用户线程同时在工作,断开了第一层 3、4 两个对象之间的引用,这时对于正在处理 3 号对象的垃圾回收线程来讲,它会将 4 号对象当做是白色垃圾
      在这里插入图片描述

    3. 但如果其他用户线程又建立了 2、4 两个对象的引用,这时因为 2 号对象是黑色已处理对象了,因此垃圾回收线程不会察觉到这个引用关系的变化,从而产生了漏标
      在这里插入图片描述

    4. 如果用户线程让黑色对象引用了一个新增对象,一样会存在漏标问题
      在这里插入图片描述

因此对于并发标记而言,必须解决漏标问题,也就是要记录标记过程中的变化。有两种解决方法:

  1. Incremental Update 增量更新法,CMS 垃圾回收器采用
    • 思路是拦截每次赋值动作,只要赋值发生,被赋值的对象就会被记录下来,在重新标记阶段再确认一遍
  2. Snapshot At The Beginning,SATB 原始快照法,G1 垃圾回收器采用
    • 思路也是拦截每次赋值动作,不过记录的对象不同,也需要在重新标记阶段对这些对象二次处理
    • 新加对象会被记录
    • 被删除引用关系的对象也被记录

3.1.4. 四种引用

平时只会用到强引用和软引用。

强引用:

在这里插入图片描述

  • 类似于 Object obj = new Object();
  • 只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
**软引用:**SoftReference 类实现软引用。用来描述一些还有用但并非必需的对象。

在这里插入图片描述

  • 在系统要发生内存溢出异常之前,才会将这些对象列进回收范围之中进行二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常
  • 软引用可用来实现内存敏感的高速缓存。
  • 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果这个软引用所引用的对象被垃圾回收器回收,Java虚拟机就会将这个软引用加入到关联的引用队列中。
  • 使用场景: 适用于网页缓存、图片缓存,防止内存溢出,在内存充足的时候,缓存对象会一直存在,在内存不足的时候,缓存对象占用的内存会被垃圾收集器回收。
**弱引用:**WeakReference 类实现弱引用。

在这里插入图片描述

  • 对象只能生存到下一次垃圾收集之前。在垃圾收集器工作时,无论内存是否足够都会回收掉只被弱引用关联的对象。
  • 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果这个弱引用所引用的对象被垃圾回收器回收,Java虚拟机就会将这个弱引用加入到关联的引用队列中。
  • 使用场景: 弱引用用于生命周期更短的,对内存更敏感的场景中,比如占用内存很大的Map,java api中就提供了WeakHashMap使用,就会使得大Map被及时清理掉。
**虚引用:**PhantomReference 类实现虚引用。

在这里插入图片描述

  • 无法通过虚引用获取一个对象的实例,为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
  • 作用:虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。你声明虚引用的时候是要传入一个queue的。当你的虚引用所引用的对象已经执行完finalize函数的时候,就会把对象加到queue里面。你可以通过判断queue里面是不是有对象来判断你的对象是不是要被回收了
  • 使用场景: 判断一个对象是否被垃圾回收了,跟踪对象被垃圾回收回收的活动。一般可以通过虚引用达到回收一些非java内的一些资源比如堆外内存的行为。
    • 例如:在 DirectByteBuffer 中,会创建一个 PhantomReference 的子类Cleaner的虚引用实例用来引用该 DirectByteBuffer 实例,Cleaner 创建时会添加一个 Runnable 实例,当被引用的 DirectByteBuffer 对象不可达被垃圾回收时,将会执行 Cleaner 实例内部的 Runnable 实例的 run 方法,用来回收堆外资源。

3.1.5. 内存泄漏

内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。

导致内存泄漏的情况
  • 资源未关闭造成的内存泄漏
    • 各种连接,如数据库连接、网络连接和IO连接等,文件读写等,可以使用 try-with-resources 读取完文件,自动资源释放
  • 全局缓存持有的对象不使用的时候没有及时移除,导致一直在内存中无法移除
  • 静态集合类
    • 如HashMap、LinkedList等等。如果这些容器为静态的,那么它们的生命周期与程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。生命周期长的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。
  • 堆外内存无法回收
    • 堆外内存不受gc的管理,可能因为第三方的bug出现内存泄漏
内存泄漏的解决方法
  1. 尽量减少使用静态变量,或者使用完及时 赋值为 null。
  2. 明确内存对象的有效作用域,尽量缩小对象的作用域,能用局部变量处理的不用成员变量,因为局部变量弹栈会自动回收;
  3. 减少长生命周期的对象持有短生命周期的引用;
  4. 使用StringBuilder和StringBuffer进行字符串连接,Sting和StringBuilder以及StringBuffer等都可以代表字符串,其中String字符串代表的是不可变的字符串,后两者表示可变的字符串。如果使用多个String对象进行字符串连接运算,在运行时可能产生大量临时字符串,这些字符串会保存在内存中从而导致程序性能下降。
  5. 对于不需要使用的对象手动设置null值,不管GC何时会开始清理,我们都应及时的将无用的对象标记为可被清理的对象;
  6. 各种连接(数据库连接,网络连接,IO连接)操作,务必显示调用close关闭。
内存泄漏的排查方法
  • 查看内存中对象的数量和大小,判断是否在合理的范围,如果在合理的范围内,增大内存配置,调整内存比例就可以了。
    • 命令:jmap -heap pid
  • 分析gc是否正常执行
    • 命令:jstat -gcutil 1000
  • 确认下版本新增代码的改动,尽快从代码上找出问题。
  • 开启各种命令行和 导出 dump 各种工具分析

3.2. 垃圾回收算法

在介绍JVM垃圾回收算法前,先介绍一个概念:Stop-the-World

Stop-the-world意味着 JVM由于要执行GC而停止了应用程序的执行,并且这种情形会在任何一种GC算法中发生。当Stop-the-world发生时,除了GC所需的线程以外,所有线程都处于等待状态直到GC任务完成。事实上,GC优化很多时候就是指减少Stop-the-world发生的时间,从而使系统具有高吞吐 、低停顿的特点。

  • minor gc 会触发Stop-the-World
  • Minor GC 发生在新生代的垃圾回收,暂停时间短
  • Mixed GC 新生代 + 老年代部分区域的垃圾回收,G1 收集器特有
  • Full GC 新生代 + 老年代完整垃圾回收,暂停时间长,应尽力避免

3.2.1. 复制算法(Copying)

  • 该算法将内存平均分成两部分,然后每次只使用其中的一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另一个内存中,然后将之前的内存清空,只使用这部分内存,循环下去。

优点:

  • 实现简单
  • 不产生内存碎片

缺点:

  • 代价太高,将内存缩小为原来的一半,浪费了一半的内存空间;如果不想浪费一半的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。
  • 对象存活率高的时候,**复制对象和对象的引用地址时间花费高。**如果对象的存活率很高,假设是100%存活,那么我们需要将所有对象都复制一遍,并将所有引用地址重置一遍。复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变的不可忽视。复制算法要想使用,最起码对象的存活率要非常低才行。

3.2.2. 标记清除(Mark-Sweep)

  • “标记-清除”(Mark Sweep)算法是几种GC算法中最基础的算法,是因为后续的收集算法都是基于这种思路并对其不足进行改进而得到的。
  • 算法分为2个阶段:
    1. 标记出需要回收的对象,使用的标记算法均为可达性分析算法。
    2. 回收被标记的对象。
      在这里插入图片描述

缺点:

  • 效率问题(两次遍历)
  • 空间问题(标记清除后会产生大量不连续的碎片。JVM就不得不维持一个内存的空闲列表,这又是一种开销。而且在分配数组对象的时候,寻找连续的内存空间会不太好找。)

3.2.3. 标记整理(Mark-Compact)

标记-整理法是标记-清除法的一个改进版。分为标记和整理两个阶段

  1. 在标记阶段,该算法也将所有对象标记为存活和死亡两种状态;
  2. 在整理阶段,该算法并没有直接对死亡的对象进行清理,而是通过所有存活对像都向一端移动,然后直接清除边界以外的内存。

在这里插入图片描述

  • 优点:标记/整理算法不仅可以弥补标记/清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价
  • 缺点:如果存活的对象过多,整理阶段将会执行较多复制操作,导致算法效率降低。

老年代一般是由标记清除或者是标记清除与标记整理的混合实现。

3.2.4. 分代收集算法(Generational-Collection)

  • 内存效率:复制算法>标记清除算法>标记整理算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此)。

  • 内存整齐度:复制算法=标记整理算法>标记清除算法。

  • 内存利用率:标记整理算法=标记清除算法>复制算法。

分代回收算法实际上是把复制算法和标记整理法的结合,老年代就是很少垃圾需要进行回收的,新生代就是有很多的内存空间需要回收,所以不同代就采用不同的回收算法,以此来达到高效的回收算法。

  • 年轻代(Young Gen):年轻代特点是区域相对老年代较小,对像存活率低。

    • 年轻代空间不足时,使用Minor GC(复制算法)回收整理,速度最快。

      复制算法的效率只和当前存活对像大小有关,因而很适用于年轻代的回收。而复制算法内存利用率不高的问题,通过hotspot中的两个survivor的设计得到缓解。
      在这里插入图片描述

    MinorGC 的过程(复制 --> 清空 --> 互换)
    1. eden、From 复制到 To,年龄+1
      首先,当Eden区满的时候会触发第一次GC,把还活着的对象拷贝到Survivor From区,当Eden区再次触发GC的时候会扫描Eden区和From区域,对这两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域(如果有对象的年龄已经达到了老年的标准(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置),则赋值到老年代区),同时把这些对象的年龄+1
    2. 清空 eden、Survivor From
      然后,清空Eden和From中的对象
    3. To和 From 互换
      最后,To和From互换,原To成为下一次GC时的From区。部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代
    4. 大对象特殊情况
      如果分配的新对象比较大Eden区放不下,但Old区可以放下时,对象会被直接分配到Old区(即没有晋升这一过程,直接到老年代了)
  • 老年代(Tenure Gen):老年代的特点是区域较大,对像存活率高。

    • 老年代空间不足,会先尝试触发Minor GC,如果空间仍不足,则触发Full GC,一般是由标记清除或者是标记清除与标记整理的混合实现。

3.3. 垃圾收集器(了解)

如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现

3.3**.1. Serial/Serial Old收集器**

串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-整理;垃圾收集的过程中会Stop The World(服务暂停)。它还有对应老年代的版本:Serial Old

  • 特点
    • 单线程
    • 堆内存较小,适合个人电脑
  • 参数控制: -XX:+UseSerialGC 串行收集器
    在这里插入图片描述

3.3.2. ParNew 收集器

ParNew收集器收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数、收集算法、Stop The world、对象分配规则、回收策略等都与Serial收集器完全一样,实现上这两种收集器也共用了相当多的代码。ParNew收集器的工作过程如下图所示。

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

  • 参数控制:

    -XX:+UseParNewGC ParNew收集器

    -XX:ParallelGCThreads 限制线程数量

在这里插入图片描述

3.3.3. Parallel Scavenge/Parallel Old 收集器

Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制GC的时间不大于多少毫秒或者比例;

  • 特点

    • 多线程
    • 堆内存较大,适合多核cpu
    • 目标是让单位时间内,STW时间最短
  • 垃圾回收时采用的算法

    • eden 内存不足发生 Minor GC,采用标记复制算法,需要暂停用户线程
    • old 内存不足发生** Full GC**,采用标记整理算法,需要暂停用户线程

    Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供

  • 参数控制:

    -XX:+UseParallelGC 使用Parallel收集器+ 老年代串行

    -XX:+UseParallelOldGC 使用Parallel收集器+ 老年代并行

    -XX:GCTimeRatio=ratio GC时间比例,1/(1+ratio),目标是提高吞吐率

    -XX:MaxGCPauseMillis=ms 最大GC暂停毫秒数,默认200ms

3.3.4. CMS收集器(Concurrent Mark Sweep)

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。

  • 特点
    • 多线程
    • 堆内存较大,适合多核cpu
    • 尽可能让单次STW的时间最短

从名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于“标记-清除”算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为4个步骤,**其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。**包括:

  • 初始标记(CMS initial mark)

    • 初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快
  • 并发标记(CMS concurrent mark)

    • 并发标记阶段就是进行GC Roots Tracing的过程
    • 并发标记时不需暂停用户线程
  • 重新标记(CMS remark)

    • 重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
  • 并发清除(CMS concurrent sweep)

    由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行。可以用作老年代收集器(新生代使用ParNew)

  • 优点: 并发收集、低停顿

  • 缺点: 产生大量空间碎片、并发阶段会降低吞吐量

  • 参数控制:

    -XX:+UseConcMarkSweepGC 使用CMS收集器

    -XX:+UseCMSCompactAtFullCollection Full GC后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长

    -XX:+CMSFullGCsBeforeCompaction 设置进行几次Full GC后,进行一次碎片整理

    -XX:ParallelCMSThreads 设定CMS的线程数量(一般情况约等于可用CPU数量),默认为4

    -XX:CMSInitiatingOccupancyFraction=percent 执行CMS回收的内存占比

    -XX:+CMSScavengeBeforeRemark 做重新标记前,对新生代进行一次回收,+打开,-关闭

    在这里插入图片描述

  • cms是一种预处理垃圾回收器,它不能等到old内存用尽时回收,需要在内存用尽前,完成回收操作,否则会导致并发回收失败

3.3.5. G1收集器(Garbage First)

G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。

  • 与CMS收集器相比G1收集器有以下特点:
    1. **并行与并发:**G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
    2. **分代收集:**分代概念在G1中依然得以保留。虽然G1可以不需要其它收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。也就是说G1可以自己管理新生代和老年代了。
    3. 空间整合:由于G1使用了独立区域(Region)概念,G1从整体来看是基于“标记-整理”算法实现收集,从局部(两个Region)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片。
    4. **可预测的停顿:**这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用这明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。

上面提到的垃圾收集器,收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region的集合。
在这里插入图片描述

G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划的避免在整个Java堆中进行全区域的垃圾收集
- G1跟踪各个Region里垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率

每个Region被标记了E、S、O和H,说明每个Region在运行时都充当了一种角色,其中H是以往算法中没有的,它代表Humongous,这表示这些Region存储的是巨型对象(humongous object,H-obj),当新建对象大小超过Region大小一半时,直接在新的一个或多个连续Region中分配,并标记为H。

为了避免全堆扫描,G1使用了Remembered Set来管理相关的对象引用信息。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏了。

如果不计算维护Remembered Set的操作,G1收集器的运作大致可划分为以下几个步骤:

1、初始标记(Initial Making)

  • 初始阶段仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可以用的Region中创建新对象,这个阶段需要停顿线程,但耗时很短。

2、并发标记(Concurrent Marking)

  • 并发标记阶段是从GC Roots开始对堆中对象进行可达性分析,找出存活对象,这一阶段耗时较长但能与用户线程并发运行。

3、最终标记(Final Marking)

  • 而最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但可并行执行。

4、筛选回收(Live Data Counting and Evacuation)

  • 筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,这一过程同样是需要停顿线程的,但Sun公司透露这个阶段其实也可以做到并发,但考虑到停顿线程将大幅度提高收集效率,所以选择停顿。

下图为G1收集器运行示意图:
在这里插入图片描述

3.3.6. 垃圾回收器比较

如果两个收集器之间存在连线,则说明它们可以搭配使用。虚拟机所处的区域则表示它是属于新生代还是老年代收集器。

在这里插入图片描述

整堆收集器: G1

回收器名称算法分类作用区域是否多线程类型备注
Serial复制算法新生代单线程串行简单高效、不建议使用
ParNew复制算法新生代多线程并行唯一和CMS搭配的新生代垃圾回收器
Parallel Scavenge复制算法新生代多线程并行更关注吞吐量
Serial Old标记整理老年代单线程串行能和所有young gc搭配使用
Parallel Old标记整理老年代多线程并行搭配Parallel Scavenge
CMS标记清除老年代多线程并发追求最短的暂停时间
  • 垃圾回收器选择策略

    • 客户端程序 : Serial + Serial Old;

    • 吞吐率优先的服务端程序(比如:计算密集型) : Parallel Scavenge + Parallel Old;

    • 响应时间优先的服务端程序 :ParNew + CMS。

Java虚拟机快速入门-全干货-JVM 定义:java 程序的运行环境(java 二进制字节码的运行环境)好处:一次编写,到处运行(跨平台性自动内存管理,垃圾回收功能数组下标越界检查多态Java Virtual Machine Stacks (Java 虚拟机栈)1.每个线程运行时所需要的内存,称为虚拟机栈2.每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存。3.每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。栈的特点:先进后出栈-线程运行需要的内存空间。 阅读详情

相关推荐

Java 虚拟机(JVM)是什么

Java 虚拟机(JVM)是一个全面的信息处理和分析系统,集成了各种 Java 技术和工具的系统平台,能够运行 Java 字节码,从而为 Java 程序提供一个运行环境。JVM 是一个虚构出来的计算机,它是一款软件,用来执行一系列虚拟计算机指令。大体上,虚拟机可以分为系统虚拟机和程序虚拟机。Java 虚拟机就是典型的程序虚拟机,它专门为执行单个计算机程序而设计,在 Java 虚拟机中执行的指令我们称为 Java 字节码指令。JVM 主要由类装载器、运行时数据区、执行引擎、本地接口库和本地方法库等部分组成。

百态·数智思维 | 聚焦大数据、量化与未来AI 1461

《Java虚拟机学习》 java代码的运行过程

对于不怎么占用资源的代码,可以采用解释执行的方式运行;而对于小部分的热点代码,采用即时编译将其转换成机器码执行,降低资源的占用。当我们保存java文件后,首先由编译器编译成class文件,然后通过Java虚拟机将class文件转换成字节码文件。HotSpot 虚拟机采用的是混合模式,首先解释执行字节码,将其中反复执行的热点代码,以方法为代码,进行即时编译。首先将java文件加载到java虚拟机中,然后由虚拟机将类元信息存储在 虚拟机的方法区中。即时编译 : 以方法为单位,将方法中的所有字节码编译成机器码。

纸上得来终觉浅,绝知此事要躬行 1279

Java笔记——Java虚拟机(JVM)从入门到实战_java jvm虚拟机

G1的整个堆会被划分成多个大小相等的区域,称之为区Region,区域不要求是连续的。如果所有的加载器都没加载过,就会层层向上委派查看是否加载过,如果都没加载过,就会由顶向下尝试进行加载,如果一个类加载器,发现这个类在自己的加载路径中,就会选择去加载这个类。1.可以通过调整年轻代和老年代的比例来适应不同类型的应用程序,提高内存的利用率和性能(假如用户比较多,有大量用户会在同一时段访问订单数据,如果新生代设置小,大量数据创建,会频繁发生minor GC,所以我们希望这些生命周期短的数据能在)。

a25847a的博客 1303

Java虚拟机-Java程序执行流程

程序执行流程我把它划分为以下几个步骤:编辑源码、编译生成class文件、(加载class文件、运行class字节码文件),其中后两个步骤都是在jvm虚拟机上执行的。

张丰的博客 8100

SVG绘制圆环进度条

在我们的大屏可视化项目中,经常会用到各种各样的图表。与传统的表格展示、枯燥的文字阐述相比,图表展示则使用户看起来更加直观、数据的展示则更加一目了然。本文基于svg绘图技术结合前端技术栈vue,以工作中常用的环形进度条为例,简单阐述一下绘制进度条的一些思路。............

qq_40289557的博客 8360

从solidworks导出URDF模型

一、下载sw_urdf_exporter插件 ROS目前提供了SolidWorks转URDF的插件,叫做sw_urdf_exporter,从http://wiki.ros.org/sw_urdf_exporter下载sw2urdfSetup.exe文件,下载后点击安装,安装完成后就能在solidworks中导出URDF模型了。 二、创建solidworks模型 下面以较为简单的四轮小车为例进行说...

weixin_45168199的博客 4万+

Java虚拟机

实现通过类的权限限定名获取该类的二进制字节流的代码块叫做类加载器。启动类加载器(BootstrapClassLoader),使用C++语言编写,用来加载Java核心类库,它无法被应用程序直接使用。扩展类加载器(ExtensionClassLoader),使用Java编写,且父类加载器是Bootstrap,用来加载扩展库里面的类。系统类加载器(ApplicationClassLoader),根据Java的类路径(CLASSPATH)来加载Java类,参考JVMhttphttps。...

zhangzhang_one的博客 2454

BUCK电路为何无法实现100%占空比?自举电路原理与替代方案解析

在开关电源设计中,DC-DC转换器是实现电压变换的核心器件,其工作原理基于功率开关管的周期性导通与关断。BUCK(降压)电路作为一种经典拓扑,通过调节开关管的占空比来控制输出电压。然而,其占空比存在理论上限,无法达到100%,这一限制源于高边开关管的驱动需求。为了驱动高边N沟道MOSFET(NMOS),需要栅极电压高于源极电压,而自举(Bootstrap)电路通过利用开关节点电压的跳变,巧妙地生成了这一高压驱动信号。自举电路必须在每个开关周期内存在续流阶段(低边管导通)为电容充电,这从根本上限制了最大占空比

weixin_32239523的博客 309

JVM——Java虚拟机架构

0. 前言 Java虚拟机(Java virtualmachine)实现了Java语言最重要的特征:即平台无关性。 平台无关性原理:编译后的Java程序(.class文件)由JVM执行。JVM屏蔽了与具体平台相关的信息,使程序可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。因此实现Java平台无关性。 1. JVM结构图 ...

关于Android开发的一些技术点总结 ╮( ̄▽ ̄”)╭ 3万+

高分六号GF-6 WFV数据处理全流程:从原始数据解压到经纬度投影转换

本文详细解析了高分六号GF-6 WFV遥感数据的完整处理流程。从原始数据解压与分块结构理解入手,指导如何安装ENVI国产卫星支持工具进行自动拼接,并重点阐述了利用RPC模型进行正射校正以消除几何畸变,以及将UTM投影转换为地理经纬度坐标的关键步骤。文章旨在帮助用户高效地将原始卫星数据转化为可直接用于农业监测、生态环境分析等领域的标准地理空间图像。

weixin_29047919的博客 196

SFF-8654接口引脚定义全解析:从SAS到U.2的高速硬件连接核心

在高速数据存储与传输领域,接口标准是连接主机与存储设备、确保信号完整性与协议互通性的物理基础。其核心原理在于通过精确定义的物理引脚和电气特性,承载不同的高速串行通信协议,如SAS和PCIe。这类高密度连接器的技术价值在于以紧凑的形态支持多协议兼容与高速率传输,满足了企业级存储、高性能计算对带宽和可靠性的苛刻要求。典型的应用场景包括服务器硬盘背板连接、NVMe U.2固态硬盘直连以及外部存储扩展。本文聚焦于SFF-8654(亦称Mini SAS HD)这一关键接口,深入剖析其引脚定义、信号分区(如高速数据通道

weixin_29188043的博客 712

【jvm系列-01】初识虚拟机与java虚拟机

为了解决java的跨平台性问题,

zhenghuishengq的博客 9756

数据分析案例-电影数据可视化分析

数据介绍数据为2011-2021电影数据可视化分析首先导入本次项目需要的包和数据 数据预处理 可视化

m0_64336780的博客 2万+

java虚拟机jvm下载_Java虚拟机(JVM)简介

java虚拟机jvm下载什么是JVM Java虚拟机(JVM)是使计算机能够运行Java程序的抽象计算机。 JVM有三个概念: 1.规格 2.实施 3.实例。 该规范是正式描述JVM实现要求的文档。 具有单一规范可确保所有实现都可互操作。 JVM实现是满足JVM规范要求的计算机程序。 JVM的实例是在进程中运行的实现,该进程执行编译为Java字节码的计算机程序。 Java运行时环境(JR...

cunhui1209的博客 1119

课题申报新手入门必备指南!利用AI辅助搞定 6大关键部分,高效提升申报成功率(附AI提示词)

以下是我(或团队)与本课题相关的已有基础:【如:承担过的公开课、校本教研活动、教学改革实践、课程建设、区级/校级课题、教学反思积累等】请你完成以下任务:1)判断哪些内容与本课题的研究目标和内容具有直接或间接关联;以下是我当前的研究设想(可能比较笼统):【填写你的研究想法 / 教学困惑 / 初步设想】,请你帮我完成以下任务:1)先判断该设想中隐含的核心研究问题是什么;以下是我目前拟定的研究目标,请你从课题评审视角进行校正:【逐条粘贴你已有的研究目标】,请你帮我:1)判断每个目标是否具体、清晰、可评估;

智能AI让学术科研更容易 917

【ESD专题】从原理上分析TVS管PCB Layout的经验法则

我们常常会听到的一些经验法则,按这些经验法则去做,一般也不会有什么问题,但是到底是为什么呢? 没有从原理上分析和证明的时候,往往在Layout之后还要担心实际测试结果怎么样。或是在实际测试出问题后,根本无法进行分析,难道说修改一个版本就好了?那要从哪里修改呢?也无从下手呀。...

一点硬件 1446

JVM学习 认识java虚拟机

学习JVM之前对它的认识: 在高级语言被计算机认知并给出响应的过程中:通常是需要将高级语言由识别它们的解释编译器将他们翻译成汇编语言文件,计算机操作系统可以通过相应的编译器识别汇编语言从而将其转换为01组合的机器指令,这些指令是可以被CPU识别并作出相应操作的。 jvm虚拟机在对比计算机操作系统层面来讲,相当于一个针对java编译与运行的环境搭建的软件。它相当于计算机操作系统层面的一个只针对与java语言的虚拟的计算机软件。它的垃圾回收算法其实与操作系统中的外部/内部碎片回收也有一定的相似性。对于一个只

m0_59588838的博客 1498

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

【Java 基础(人话版)】Java 虚拟机(JVM)

它负责把 Java 代码转换成计算机能识别的机器语言,并且让不同操作系统上的 Java 程序表现一致。JVM 负责运行 Java 代码,使得 Java 具有良好的跨平台能力。,都能保证相同的 Java 代码在不同平台上运行时,结果是一致的。不同 JVM 各有特点,适用于不同的场景。只是 JVM 规范的一种实现方式,除此之外,还有。,任何公司、组织甚至个人都可以实现自己的 JVM。JVM 运行时会划分出不同的内存区域,用来存储。等不同系统,所有 JVM 只要遵循相同的。不同 JVM 适用于不同场景,例如。

qq_45729306的博客 1万+

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Java虚拟机JVM简介与理解(一)

Java虚拟机JVM简介与理解(一)

cucgyfjklx的博客 1万+

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

Java中JVM虚拟机详解

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

HuangQinJian 3万+

Java虚拟机(JVM 虚拟机)

所谓虚拟机,就是一台虚拟的计算机。他是一款软件,用来执行一系列虚拟计算机指令。大体上,虚拟机可以分为系统虚拟机和程序虚拟机。大名鼎鼎的 VisualBox、VMware就属于系统虚拟机。他们完全是对物理计算机的仿真。提供了一个可以运行完整操作系统的软件平台。程序虚拟机的典型代表就是Java虚拟机,它专门为执行单个计算机程序而设计,在Java虚拟机中执行的指令我们称为Java字节码指令。

m0_74201628的博客 964

java虚拟机jvm

java虚拟机jvm -生命周期结束 1.执行System.exit()方法 2.程序正常执行结束 3.程序在执行过程中遇到异常或因错误而抛异常终止 4.由于操作系统出现错误而导致java虚拟机进程终止 -执行流程 加载:查找并加载类的二进制数据 连接: 验证被加载类的正确性 准备为类的静态变量分配内存,并将其初始化为默认值 解析将类中的符号引用,转换为直接引用 初始化:为静态变量赋值正确的...

weixin_34324081的博客 227

JVM_Java虚拟机<深入理解Java虚拟机>走进JVM

由于跨平台性的设计,Java的指令都是根据栈来设计的。不同平台CPU架构不同,所以不能设计为基于寄存器的。优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令。时至今日,尽管嵌入式平台已经不是Java程序的主流运行平台了(准确来说应该是HotSpotVM的宿主环境已经不局限于嵌入式平台了),那么为什么不将架构更换为基于寄存器的架构呢?栈:跨平台性、指令集小、指令多、执行性能比寄存器差言包括:Java、Scala、Groovy、Kotlin;

Coastlise的博客 1168

【JVM】JAVA虚拟机总结(一)——什么是JVM

前言:Java语言里负责解释执行字节码文件的是Java虚拟机,即JVM——Java Virtual Machine(Java虚拟机)。执行Java程序的两个步骤:由Java语言编写的程序需要进过编译步骤,但这个编译步骤并不会生成特定平台的机器码,而是生成一种与平台无关的字节码(*.class文件)。这种字节码不是可执行的,必须使用Java解释器来解释执行。所有Java语言既不是纯粹的编译型语言,也...

一路向前! 4746

Java虚拟机 (JVM)(1) —— 初识JVM

Java虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是由虚拟机的具体实现指定的。JVM是java虚拟机,是整个java实现跨平台的最核心的部分,能够运行以Java语言写作的软件程序。

知唔猪的博客 1025
上一篇: Java进阶--注解与反射
下一篇: 类初始化,类加载,类加载器
likehack
博客等级 码龄8年 526粉丝 35原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

likehack

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值