初步了解jvm第一篇(代码片段)

linzepeng linzepeng     2023-05-04     160

关键词:

大家都知道,Java中JVM的重要性,学习了JVM你对Java的运行机制、编译过程和如何对Java程序进行调优相信都会有一个很好的认知。

废话不多说,直接带大家来初步认识一下JVM。

  • 什么是JVM?

JVM(Java Virtual Machine)是一个抽象的计算机,和实际的计算机一样,它具有指令集并使用不同的存储区域,它负责执行指令,还要管理数据、内存和寄存器。

看到这里,可能不懂JVM的人,已经蒙圈了。没关系,下面让我详细为大家介绍JVM的体系架构图,或许你会明白些。

简单来说,JVM就是一个虚拟计算机。我们都知道Java语言其中的一个特性就是跨平台的,而JVM就是Java程序实现跨平台的关键部分。Java编译器编译Java程序时,生成的是与平台无关的字节码(也就是*.class文件),所谓的平台无关是指编译生成的字节码无论是在Window、Linux、Mac系统都是可执行。也就是说Java编译生成的*.class文件不是面向平台的,而是面向JVM的。不同平台上的JVM都是不同的,但是他们都是提供了相同的接口。图一为Java的大致运行步骤:

 

技术图片

 图一

引用一个《疯狂Java讲义》中提到例子来帮助大家理解JVM的作用:

JVM的作用就像有两只不同的铅笔,但需要把同一个笔帽套在两支不同的笔上,只有为这两支笔分别提供一个转换器,这个转换器向上的接口相同,用于适应同一个笔帽;向下的接口不同,用于适应两支不同的笔。在这个类比中,可以近似地理解两支不同的笔就是不同的操作系统,而同一个笔帽就是Java字节码程序,转换器角色则对应JVM。类似地,也可以认为JVM分为向上和向下两个部分,所有平台的JVM向上提供给Java字节码程序的接口完全相同,但向下适应的不同平台的接口则互不相同。

  • JVM体系结构概览

上面我们是初步介绍了JVM的作用,那么要深入去了解JVM我们就需要了解JVM的体系结构,请看图二:

技术图片

图二

图二是JVM的体系架构图,接下让我们一起来聊一聊每一个部分都是什么意思。

1.类装载器子系统(ClassLoader)

负责加载class文件,class文件在文件开头有特定的文件标示,将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。

Java编译生成的*.class文件就是通过ClassLoader进行加载的,那么这里就会有几个问题:

  • ClassLoader如何知道*.class文件就是需要加载的文件?
  • 如果我手动将一个普通文件的扩展名称改为class后缀,ClassLoader会加载这个文件吗?

实际上,class文件在文件的开头是有特定的文件标识的,随便编写一个Java程序,编译生成一个class文件,打开后你都能看到如下内容:

技术图片

cafe babe就是class文件的一个标识,ClassLoader负责加载有cafe babe的class文件,它将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时的数据结构并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定,请看图三:

 

技术图片

 

图三

Car.class文件通过ClassLoader进行加载到内存中,Car Class在内存中就相当一个模板,我们可以通过这个模板可以实例化成不同的实例car1、car2、car3。

不知大家会不会有一个疑问,ClassLoader加载Car.class在Java中是用什么类型的加载器加载的呢?在解答这个问题前我们先写个简单的代码看看:

        //new一个Car对象
        Car car = new Car();

        //得到ClassLoader
        ClassLoader classLoader = car.getClass().getClassLoader();

        //打印结果
        System.out.println(classLoader);

结果为:

技术图片

 我们再来看看另外一组代码:

        //new两个不同的对象
        Car car = new Car();
        String string = new String();

        //得到ClassLoader
        ClassLoader classLoader1 = car.getClass().getClassLoader();
        ClassLoader classLoader2 = string.getClass().getClassLoader();

        //打印结果
        System.out.println(classLoader1);
        System.out.println(classLoader2);

结果为:

技术图片

 从上面我们可以知道,ClassLoader的打印结果一个是“sun.misc.Launcher$AppClassLoader@18b4aac2”,一个则是“null”,这是怎么回事呢,细心的朋友就可以发现这两个不同的对象中,其中car对象是我们自己写的一个类,string对象是系统自带的一个类。简单来说就是ClassLoader会根据不同的类选择不同的类加载器去进行加载。这里就牵扯到了ClassLoader的分类

ClassLoader的类别:

  • 启动类加载器(BootStrap)
  • 扩展类加载器(Extension)
  • 应用程序类加载器(AppClassLoader)
  • 用户自定义加载器

一般我们自己所写的类用的类加载器都是AppClassLoader,就是上图所示的“sun.misc.Launcher$AppClassLoader@18b4aac2”,而为什么string这个对象是”null“呢?实际上,这个“null”指的就是使用BootStrap这个加载器。

那可能有人有疑问,自己定义的类用AppClassLoader,能理解,因为car这个对象输出的类加载器名字中有AppClassLoader这个字样,但是为什么string这个对象是”null“,从哪里可用体现是用BootStrap这个加载器呢?是这样的,BootStrap累加载器相当于扩展类加载器、应用程序类加载器的祖宗,若是用了BootStrap,由于BootStrap上一级已经没有了,所以就用“null”来表示

其实我们可以找一下String这个类在JDK的位置:

$JAVA_HOME/jre/lib/rt.jar/java/lang

所有在这个路径$JAVA_HOME/jre/lib/rt.jar这个jar包下的类都是用BootStrap来加载的。

下面请看图4:

 

技术图片

 

图四 

这张图就可以很清晰得看到:

1.所有在$Java_Home/jre/lib/rt.jar是通过BootStrap加载的

2.所有在$Java_Home/jre/lib/ext/*.jar是通过Extension加载的

3.所有在$CLASSPATH是通过SYSTEM加载的(应用程序类加载器也叫系统类加载器,加载当前应用的classpath的所有类)

接下来我们再来看一个例子:

如果创建一个java.lang包,然后创建String类,打印一句话执行会怎么样呢?

package java.lang;

public class String 
    public static void main(String[] args) 
        System.out.println("Hello World");
    

效果如下:

技术图片

 可以看到程序报错了,说是找不到main方法,可是明明就有main方法为什么没有执行呢?这里就涉及了双亲委派机制

双亲委派机制:

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

所以它实际的运行过程是这样的:

  • ClassLoader收到String类的加载请求。
  • 先去Bootstrap查找是否有这个类,没有则反馈无法完成这个请求,但是恰好,在rt.jar中找到了java.lang.Stirng这个类
  • 执行这个类,这个类是没有定义main方法的
  • 报错,类中没有定义main方法

所以上面的例子,他会找到jdk中java.lang.String这个类,这个类确实是没有定义main方法,简单来说它执行的类是JDK中java.lang.String这个类,而不是我们自己定义的类。

那用双亲委派机制有什么好处呢:

采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object对象。

2.执行引擎(Execution Engine)

执行引擎负责解释命令,提交给操作系统执行,这里对执行引擎就不做过多的解释了,只要知道他是负责解释命令的即可。

3.本地方法接口(Native Interface)和本地方法栈(Native Method Stack)

  • 本地接口:本地接口的作用是融合不同的编程语言为 Java 所用,它的初衷是融合 C/C++程序,Java 诞生的时候是 C/C++横行的时候,要想立足,必须有调用 C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是 Native Method Stack中登记 native方法,在Execution Engine 执行时加载native libraies。

   目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机或者Java系统管理生产设备,在企业级应用中已经比较少见。因为现在的异构领域间的通信很发达,比如可以使用    Socket通信,也可以使用Web Service等等,不多做介绍。

          如果在程序中有见到native关键字,就代表不是Java能完成的事情了,需要加载本地方法库才能完成

  • 本地方法栈:它的具体做法是Native Method Stack中登记native方法,在Execution Engine 执行时加载本地方法库。说白了就是本地方法由本地方法栈来登记,Java中的方法由Java栈来登记。

4.PC寄存器(Program Counter Register)

每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。
这块内存区域很小,它是当前线程所执行的字节码的行号指示器,字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令。
如果执行的是一个Native方法,那这个计数器是空的。
PC寄存器用来完成分支、循环、跳转、异常处理、线程恢复等基础功能。由于使用的内存较小,所以不会发生内存溢出(OutOfMemory)错误。

那么这篇文章先讲到这里,下篇文章中我们再继续来聊一聊方法区、栈和堆..........

初步了解jvm第三篇(堆和gc回收算法)

在《初步了解JVM第一篇》和《初步了解JVM第二篇》中,分别介绍了:类加载器:负责加载*.class文件,将字节码内容加载到内存中。其中类加载器的类型有如下:执行引擎:负责解释命令,提交给操作系统执行。启动类加载器(Bo... 查看详情

初步理解jvm(代码片段)

JVM的位置#mermaid-svg-UG27J2UcTlSa9uww.labelfont-family:\'trebuchetms\',verdana,arial;font-family:var(--mermaid-font-family);fill:#333;color:#333#mermaid-svg-UG27J2UcTlSa9uww.labeltextfill:#333#merma 查看详情

jvm初步理解(代码片段)

1、什么是运行时数据区?1、什么是运行时数据区? javac指令:编译java文件生成class文件? java指令:运行class文件即将数据放到jvm中? class文件运行,后将不同的数据放到jvm中不同的位置这就是运行时数据区的由来。2、运行时数... 查看详情

elasticsearch技术实战——第一篇(使用篇)(代码片段)

...一些使用心得体会,希望对大家有所帮助。计划分三篇:第一篇(使用篇),主要讲解基本概念、分词、数据同步、搜索API。第二篇(配置及参数调优篇),主要围绕JVM参数调优、异常排查、安全性等方面讲解。第三篇(倒排索... 查看详情

初步了解jvm

先看一眼JVM虚拟机运行时的内存模型: 1.方法区Perm(永久代、非堆)2.虚拟机栈3.本地方法栈(Native方法)4.堆5.程序计数器 1首先的问题是:jvm如何知道那些对象需要回收?目前两种标识算法、三种回收算法、两种清除算... 查看详情

一篇笔记整理jvm工作原理

前言:  想提高Java开发,了解jvm是必不可少的。它让开发者了解他们的代码,jvm是如何变异与运行。深入了解jvm:会让你的代码写的高效,逐步成为大神  下面介绍jvm的基本知识 >>数据类型  Java虚拟机中,数据... 查看详情

一篇笔记整理jvm工作原理

前言:  想提高Java开发,了解jvm是必不可少的。它让开发者了解他们的代码,jvm是如何变异与运行。深入了解jvm:会让你的代码写的高效,逐步成为大神  下面介绍jvm的基本知识 >>数据类型  Java虚拟机中,数据... 查看详情

初步了解jvm:jvm简介和简单调优

**首先份上一份比较详细的java虚拟机的思维导图:**什么是jvm:**简述就是**:JVM是JavaVirtualMachine(Java虚拟机)的缩写,java虚拟机本质上就是一个程序,当它在命令行上启动的时候,就开始执行保存在某字节码文件中的指令。通过jvm... 查看详情

android中调用so库-第一篇(代码片段)

前言公司是做人脸识别的,最近主管叫我了解一下车牌识别的sdk,里面就涉及so库的调用。想认真了解一下ndk和so库的使用,查了一下资料,然后总结一下自己的心得,理解的不对的地方请留言指教。大纲1.Andro... 查看详情

u-boot-2016.07移植(第一篇)初步分析

U-BOOT-2016.07移植 (第一篇) 初步分析目录U-BOOT-201607移植 第一篇 初步分析目录编译和移植环境更新交叉编译工具1下载arm-linux-gcc4432安装arm-linux-gcc443安装环境Ubuntu910下载u-boot-201607并解压分析顶层Makefile1找出目标依赖关系2总结... 查看详情

gradle初步(代码片段)

一、介绍Gradle是一个基于JVM的富有突破性构建工具。它为您提供了:一个像ant一样,通用的灵活的构建工具一种可切换的,像maven一样的基于约定优于配置的构建框架强大的多工程构建支持强大的依赖管理(基于ApacheIvy)对已有的mave... 查看详情

一文理解jvm的类加载系统(代码片段)

...字节码文件加载到内存中,然后对其进行执行校验、初步的初始化等工作,之后再交给JVM内存结构来创建对象和完成预定的服务。因此,类加载过程是JVM启动的第一步,也是我们理解JVM的第一步。本文,我们... 查看详情

爬虫初步了解(代码片段)

 一、爬虫的概念:口语版:也叫网络蜘蛛,它是批量下载网络资源的程序专业版:网络爬虫是伪装成客户端与服务端进行数据交互的程序二、爬虫的应用:1.数据采集例:收集数据,大数据2.搜索引擎例:百度,谷歌搜索引擎都属... 查看详情

react(初步了解)(代码片段)

1.什么是react?  1.1.React是一个用于构建用户界面的javacript库  1.2.React主要用于构建UI,很多人认为React是MVC中的V(视图)  1.3.React是拥有较高的性能,代码逻辑非常简单2.react特性?  2.1.声明式设计  2.2.高效 React通... 查看详情

对volley的初步分析第一篇

进入android世界已经快要两年了,放眼望去,在这两年的android世界里,自己多少也成长了点,一路上磕磕碰碰,即使现在的我还是android学术界里最垫底的那一位,但还是阻挡不了我对未来android世界的探索的脚步,自从上次我坚... 查看详情

初步了解vue源码(代码片段)

1、源码学习目录本项目所剖析的Vue.js源码版本是目前最新的版本,版本号为v2.6.11,其代码目录如下:  从上面的目录结构可以看出,Vue的整个项目包含了类型检测相关、单元测试相关、与平台无关的核心代码以及跨平台... 查看详情

初步了解redux(代码片段)

  redux作为react的状态状态管理工具,是十分重要的一部分,但是它在学习起来比较困难。它的写法一共分为三部分,而且他不像其他的东西一样可以写一步,在页面上查看一下。它必须三个部分全部完成之后,才能查看效果... 查看详情

socket初步了解(代码片段)

 在这之前我们先了解一下一些关于网络编程的概念  网络编程从大方面说就是对信息的发送和接收,中间传输为物理线路的作用,编程人员可以不用考虑  网络编程最主要的工作就是在发送端吧信息通过规定好的协议进行... 查看详情