转载自:https://blog.csdn.net/singit/article/details/54920387?utm_source=blogkpcl11

什么是jvm?JVM的基本结构,

也就是概述。说是概述,内容很多,而且概念量也很大,

不过关于概念方面,你不用担心,我完全有信心,让概念在你的脑子里变成图形,

所以只要你有耐心,仔细,认真,并发挥你的想象力,这一章之后你会充满自信。

当然,不是说看完本章,就对jvm了解了,jvm要学习的知识实在是非常的多。

在你看完本节之后,后续我们还会来学jvm的细节,但

是如果你在学习完本节的前提下去学习,再学习其他jvm的细节会事半功倍。

为了让你每一个知识点都有迹可循,希望你按照我的步骤一步步继续。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

知识点1:什么是Java虚拟机(你以为你知道,如果你看我下面的例子,你会发现你其实不知道)

第一步:先来写一个类:

  1. package test;
  2. public class JVMTestForJava {
  3. public static void main(String[] args) throws InterruptedException {
  4. Thread.sleep(10000000);
  5. }
  6. }

第二步:cmd窗口输入:Javatest.JVMTestForJava

第三步:打开任务管理器-进程

你看到一个叫java.exe的程序没有,是滴这个就是java的虚拟机,java xxx这个命令就是用来启动一个java虚拟机,而main函数就是一个java应用的入口,main函数被执行时,java虚拟机就启动了。好了ctrl+c结束你的jvm。

第四步:打开你的ecplise,右键run application,再run application一次

第五步:打开任务管理器-进程

好了,我已经圈出来了,有两个javaw.exe,为什么会有两个?因为我们刚才运行了两次run application。这里我是要告诉你,一个java的application对应了一个java.exe/javaw.exe(java.exe和javaw.exe你可以把它看成java的虚拟机,一个有窗口界面一个没有)。你运行几个application就有几个java.exe/javaw.exe。或者更加具体的说,你运行了几个main函数就启动了几个java应用,同时也启动了几个java的虚拟机。

知识点1总结:

什么是java虚拟机,什么是java的虚拟机实例?java的虚拟机相当于我们的一个java类,而java虚拟机实例,相当我们new一个java类,不过java虚拟机不是通过new这个关键字而是通过java.exe或者javaw.exe来启动一个虚拟机实例。

看了上面我的描述方式,你觉得如何?概念需要背吗?如果你对我的笔记有信心,继续看下去吧!

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

知识点2:jvm的生命周期

基本上学习一种容器(更具体的说我们在学习servlet的时候),我们都要学习它的生命周期。那么jvm的生命周期如何,我一惯不喜欢丢概念,所以来实验,实践出真知,老师说过的,对不!

第一步:copy我代码

  1. package test;
  2. public class JVMTestLife {
  3. public static void main(String[] args) {
  4. new Thread(new Runnable() {
  5. @Override
  6. public void run() {
  7. for(int i=0;i<5;i++){
  8. try {
  9. Thread.currentThread().sleep(i*10000);
  10. System.out.println("睡了"+i*10+"秒");
  11. } catch (InterruptedException e) {
  12. System.out.println("干嘛吵醒我");
  13. }
  14. }
  15. }
  16. }).start();
  17. for(int i=0;i<50;i++){
  18. System.out.print(i);
  19. }
  20. }
  21. }

第二步:ecplise里run application

第三步:打开任务管理器-进程,看到一个javaw.exe的虚拟机在跑

第四步:查看控制台输出,并观察任务管理器中的javaw.exe什么时候消失

  1. 0 睡了0秒
  2. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 睡了10秒
  3. 睡了20秒
  4. 睡了30秒
  5. 睡了40秒

这是我ecplise里的输出结果,而如果你观察控制台和任务管理器的javaw.exe会发现,当main函数的for循环打印完的时候,程序居然没有退出,而等到整个new Thread()里的匿名类的run方法执行结束后,javaw.exe才退出。我们知道在c++的win32编程(CreatThread()),main函数执行完了,寄宿线程也跟着退出了,在c#中如果你用线程池(ThreadPool)的话,结论也是如此,线程都跟着宿主进程的结束而结束。但是在java中貌似和我们的认知有很大的出入,这是为什么呢?

这是由于java的虚拟机种有两种线程,一种叫叫守护线程,一种叫非守护线程,main函数就是个非守护线程,虚拟机的gc就是一个守护线程。java的虚拟机中,只要有任何非守护线程还没有结束,java虚拟机的实例都不会退出,所以即使main函数这个非守护线程退出,但是由于在main函数中启动的匿名线程也是非守护线程,它还没有结束,所以jvm没办法退出(有没有想干坏事的感觉??)。

知识点2总结:java虚拟机的生命周期,当一个java应用main函数启动时虚拟机也同时被启动,而只有当在虚拟机实例中的所有非守护进程都结束时,java虚拟机实例才结束生命。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

知识点三:java虚拟机的体系结构(无奈,我怀着悲痛心情告诉你,我们必须来一些概念,别急,咱有图)

看到这个图没,名词不是普通滴多,先来看看哪些名词我们之前是说过的,执行引擎(笔记一),类装载器(笔记二),java栈(笔记十一)。

在了解jvm的结构之前,我们有必要先来了解一下操作系统的内存基本结构,这段可不能跳过,它会有助于消化上面的那个图哦!好先来看图

操作系统内存布局:

那么jvm在操作系统中如何表示的呢?

操作系统中的jvm

为什么jvm的内存是分布在操作系统的堆中呢??因为操作系统的栈是操作系统管理的,它随时会被回收,所以如果jvm放在栈中,那java的一个null对象就很难确定会被谁回收了,那gc的存在就一点意义都莫有了,而要对栈做到自动释放也是jvm需要考虑的,所以放在堆中就最合适不过了。

操作系统+jvm的内存简单布局

从上图中,你有没有发现什么规律,jvm的内存结构居然和操作系统的结构惊人的一致,你能不能给他们对号入座?还不能,没关系,再来看一个图,我帮你对号入座。看我下面红色的标注

从这个图,你应该不难发现,原来jvm的设计的模型其实就是操作系统的模型,基于操作系统的角度,jvm就是个该死的java.exe/javaw.exe,也就是一个应用,而基于class文件来说,jvm就是个操作系统,而jvm的方法区,也就相当于操作系统的硬盘区,所以你知道我为什么喜欢叫他permanent区吗,因为这个单词是永久的意思,也就是永久区,我们的磁盘就是不断电的永久区嘛,是一样的意思啊,多好对应啊。而java栈和操作系统栈是一致的,无论是生长方向还是管理的方式,至于堆嘛,虽然概念上一致目标也一致,分配内存的方式也一直(new,或者malloc等等),但是由于他们的管理方式不同,jvm是gc回收,而操作系统是程序员手动释放,所以在算法上有很多的差异,gc的回收算法,估计是jvm里面的经典啊,后面我们也会一点点的学习的,不要着急。

有没有突然自信的感觉?如果你对我的文章有自信,我们再继续,还是以图解的方式,我还是那一句,对于概念我绝对有信心让它在你脑子里根深蒂固。

看下面的图。

将这个图和上面的图对比多了什么?没错,多了一个pc寄存器,我为什么要画出来,主要是要告诉你,所谓pc寄存器,无论是在虚拟机中还是在我们虚拟机所寄宿的操作系统中功能目的是一致的,计算机上的pc寄存器是计算机上的硬件,本来就是属于计算机,(这一点对于学过汇编的同学应该很容易理解,有很多的寄存器eax,esp之类的32位寄存器,jvm里的寄存器就相当于汇编里的esp寄存器),计算机用pc寄存器来存放“伪指令”或地址,而相对于虚拟机,pc寄存器它表现为一块内存(一个字长,虚拟机要求字长最小为32位),虚拟机的pc寄存器的功能也是存放伪指令,更确切的说存放的是将要执行指令的地址,它甚至可以是操作系统指令的本地地址,当虚拟机正在执行的方法是一个本地方法的时候,jvm的pc寄存器存储的值是undefined,所以你现在应该很明确的知道,虚拟机的pc寄存器是用于存放下一条将要执行的指令的地址(字节码流)。

再对上面的图扩展,这一次,我们会稍微的深入一点,放心啦,不会很深入,我们的目标是浅显易懂,好学易记嘛!看下面的图。

多了什么?没错多了一个classLoader,其实这个图是要告诉你,当一个classLoder启动的时候,classLoader的生存地点在jvm中的堆,然后它会去主机硬盘上将A.class装载到jvm的方法区,方法区中的这个字节文件会被虚拟机拿来new A字节码(),然后在堆内存生成了一个A字节码的对象,然后A字节码这个内存文件有两个引用一个指向A的class对象,一个指向加载自己的classLoader,如下图。

那么方法区中的字节码内存块,除了记录一个class自己的class对象引用和一个加载自己的ClassLoader引用之外,还记录了什么信息呢??我们还是看图,然后我会讲给你听,听过一遍之后一辈子都不会忘记。

你仔细将这个字节码和我们的类对应,是不是和一个基本的java类惊人的一致?下面你看我贴出的一个类的基本结构。

  1. package test;import java.io.Serializable;public final class ClassStruct extends Object implements Serializable {//1.类信息
  2. //2.对象字段信息
  3. private String name;
  4. private int id;
  5. //4.常量池
  6. public final int CONST_INT=0;
  7. public final String CONST_STR="CONST_STR";
  8. //5.类变量区
  9. public static String static_str="static_str";
  10. //3.方法信息
  11. public static final String getStatic_str ()throws Exception{
  12. return ClassStruct.static_str;
  13. }}

你将上面的代码注解和上面的那个字节码码内存块按标号对应一下,有没有发现,其实内存的字节码块就是完整的把你整个类装到了内存而已。

所以各个信息段记录的信息可以从我们的类结构中得到,不需要你硬背,你认真的看过我下面的描述一遍估计就不可能会忘记了:

1.类信息:修饰符(public final)

是类还是接口(class,interface)

类的全限定名(Test/ClassStruct.class)

直接父类的全限定名(java/lang/Object.class)

直接父接口的权限定名数组(java/io/Serializable)

也就是 public final class ClassStruct extends Object implements Serializable这段描述的信息提取

2.字段信息:修饰符(pirvate)

字段类型(java/lang/String.class)

字段名(name)

也就是类似private String name;这段描述信息的提取

3.方法信息:修饰符(public static final)

方法返回值(java/lang/String.class)

方法名(getStatic_str)

参数需要用到的局部变量的大小还有操作数栈大小(操作数栈我们后面会讲)

方法体的字节码(就是花括号里的内容)

异常表(throws Exception)

也就是对方法public static final String getStatic_str ()throws Exception的字节码的提取
       4.常量池:

4.1.直接常量:

1.1CONSTANT_INGETER_INFO整型直接常量池public final int CONST_INT=0;

1.2CONSTANT_String_info字符串直接常量池   public final String CONST_STR="CONST_STR";

1.3CONSTANT_DOUBLE_INFO浮点型直接常量池

等等各种基本数据类型基础常量池(待会我们会反编译一个类,来查看它的常量池等。)

4.2.方法名、方法描述符、类名、字段名,字段描述符的符号引用

也就是所以编译器能够被确定,能够被快速查找的内容都存放在这里,它像数组一样通过索引访问,就是专门用来做查找的。

编译时就能确定数值的常量类型都会复制它的所有常量到自己的常量池中,或者嵌入到它的字节码流中。作为常量池或者字节码流的一部分,编译时常量保存在方法区中,就和一般的类变量一样。但是当一般的类变量作为他们的类型的一部分数据而保存的时候,编译时常量作为使用它们的类型的一部分而保存

5.类变量:

就是静态字段( public static String static_str="static_str";)

虚拟机在使用某个类之前,必须在方法区为这些类变量分配空间。

6.一个到classLoader的引用,通过this.getClass().getClassLoader()来取得为什么要先经过class呢?思考一下,然后看第七点的解释,再回来思考

7.一个到class对象的引用,这个对象存储了所有这个字节码内存块的相关信息。所以你能够看到的区域,比如:类信息,你可以通过this.getClass().getName()取得

所有的方法信息,可以通过this.getClass().getDeclaredMethods(),字段信息可以通过this.getClass().getDeclaredFields(),等等,所以在字节码中你想得到的,调用的,通过class这个引用基本都能够帮你完成。因为他就是字节码在内存块在堆中的一个对象

8.方法表,如果学习c++的人应该都知道c++的对象内存模型有一个叫虚表的东西,java本来的名字就叫c++- -,它的方法表其实说白了就是c++的虚表,它的内容就是这个类的所有实例可能被调用的所有实例方法的直接引用。也是为了动态绑定的快速定位而做的一个类似缓存的查找表,它以数组的形式存在于内存中。不过这个表不是必须存在的,取决于虚拟机的设计者,以及运行虚拟机的机器是否有足够的内存

--------------------------------------------------------------------------------------忽略下面虚线间的废话-------------------------------------------------------------------------------------------------------------------------

好了,还剩这么多没讲过。不过不要急,我一向提倡,学到哪里讲到哪里,看到哪里。所以没有学到的概念,让他随风去。

但是我还是会来串一下思路滴:

首先,当一个程序启动之前,它的class会被类装载器装入方法区(不好听,其实这个区我喜欢叫做Permanent区),执行引擎读取方法区的字节码自适应解析,边解析就边运行(其中一种方式),然后pc寄存器指向了main函数所在位置,虚拟机开始为main函数在java栈中预留一个栈帧(每个方法都对应一个栈帧),然后开始跑main函数,main函数里的代码被执行引擎映射成本地操作系统里相应的实现,然后调用本地方法接口,本地方法运行的时候,操纵系统会为本地方法分配本地方法栈,用来储存一些临时变量,然后运行本地方法,调用操作系统APIi等等。

好吧,你听晕了,我知道,先记住这段话的位置,等某年某月我提醒你回来看,你就焕然大悟了,现在你只需要走马观花咯!!!

--------------------------------------------------------------------------------------忽略下面虚线间的废

【深入理解JVM】学习笔记——-1、JVM基本结构的更多相关文章

  1. java之jvm学习笔记十三(jvm基本结构)

    java之jvm学习笔记十三(jvm基本结构) 这一节,主要来学习jvm的基本结构,也就是概述.说是概述,内容很多,而且概念量也很大,不过关于概念方面,你不用担心,我完全有信心,让概念在你的脑子里变成 ...

  2. JVM学习笔记:JVM的体系结构与JVM的生命周期

    1 JVM在java平台中的位置 1.1 Java平台组成 Java平台主要由Java虚拟机和Java API这两部分组成.参考Oracle官网. 1.2 java平台结构图 JDK1.2开始,迫于J ...

  3. java之jvm学习笔记十三(jvm基本结构) 通俗易懂的JVM 文件,没有之一

    http://blog.csdn.net/yfqnihao/article/details/8289363

  4. java jvm学习笔记七(jar包的代码认证和签名)

    欢迎装载请说明出处:http://blog.csdn.net/yfqnihao 前言: 如果你循序渐进的看到这里,那么说明你的毅力提高了,jvm的很多东西都是比较抽像的,如果不找相对应的代码来辅助理解 ...

  5. java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)

    java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessCo ...

  6. java之jvm学习笔记三(Class文件检验器)

    java之jvm学习笔记三(Class文件检验器) 前面的学习我们知道了class文件被类装载器所装载,但是在装载class文件之前或之后,class文件实际上还需要被校验,这就是今天的学习主题,cl ...

  7. java之jvm学习笔记四(安全管理器)

    java之jvm学习笔记四(安全管理器) 前面已经简述了java的安全模型的两个组成部分(类装载器,class文件校验器),接下来学习的是java安全模型的另外一个重要组成部分安全管理器. 安全管理器 ...

  8. java之jvm学习笔记二(类装载器的体系结构)

    java的class只在需要的时候才内转载入内存,并由java虚拟机的执行引擎来执行,而执行引擎从总的来说主要的执行方式分为四种, 第一种,一次性解释代码,也就是当字节码转载到内存后,每次需要都会重新 ...

  9. JVM学习笔记-JVM模型

    JVM学习笔记 == 标签(空格分隔): jvm 学习笔记全部来自于<深入理解java虚拟机>总结 jvm内存示意图 虚拟机栈(Java Virtual Machine Stacks): ...

  10. JVM学习笔记——内存结构篇

    JVM学习笔记--内存结构篇 在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的内存结构部分 我们会分为以下几部分进行介绍: JVM整体介绍 程序计数器 虚拟机栈 本地方法栈 堆 方法 ...

随机推荐

  1. Android studio 在项目里配置签名 + cmd命令安装apk在测试机

    一.在项目里配置签名 搜索百度里有很多可视化操作在项目里配置签名,但是对于已经有签名的旧项目来说,用语句是最方便的. 方法: 第一步:把签名文件放到项目中,和build.gradle的同一级目录下.当 ...

  2. Vue3 + Element ui 后台管理系统

    Vue3 + Element ui  后台管理系统 概述:这是一个用vue3.0和element搭建的后台管理系统界面. 项目git地址: https://github.com/whiskyma/vu ...

  3. Robot Framework(1)——环境搭建及安装

    一.了解Robot Framework Robot Framework不是一个测试工具,准确来说,它是一个自动化测试框架,或者说它是一个自动化测试平台 特性如下: 1.支持关键字驱动.数据驱动和行为驱 ...

  4. c++: internal compiler error: Killed (program cc1plus)

    转自https://blog.csdn.net/qq_27148893/article/details/88936044 这是在开发板上编译opencv的时候报了一个错,主要是在编译过程中,内存不够造 ...

  5. 初识ABP vNext(7):vue身份认证管理&租户管理

    Tips:本篇已加入系列文章阅读目录,可点击查看更多相关文章. 目录 前言 开始 按钮级权限 身份认证管理 R/U权限 权限刷新 租户管理 租户切换 效果 最后 前言 上一篇介绍了vue+ABP国际化 ...

  6. JS学习阶段性总结-1

    各种函数的声明 /** * 函数的声明 */ // 声明一个方法,任意调用 function aaa(args){...} // 声明一个函数并以变量的形式展示出去,因此无法再声明前调用 var fn ...

  7. jQuery源码分析系列(三)Sizzle选择器引擎-下

    选择函数:select() 看到select()函数,if(match.length === 1){}存在的意义是尽量简化执行步骤,避免compile()函数的调用. 简化操作同样根据tokenize ...

  8. Python最火的第三方开源测试框架 ——pytest

    一.介绍 本篇文章是介绍的是Python 世界中最火的第三方单元测试框架:pytest.它有如下主要特性: assert 断言失败时输出详细信息(再也不用去记忆 self.assert* 名称了) 自 ...

  9. SQL SERVER管理维护计划错误,备份错误,1053/3041/错误18204,严重性16,状态1

    在sqlserv2008/2012里设置了管理-维护计划-备份计划,前些天遇到报错-1053/3041/错误18204,严重性16,状态1等:分享下解决方法. 1.在服务器执行任务报错 2.解决办法 ...

  10. Weights and Measures (贪心+dp)

    I know, up on top you are seeing great sights, But down at the bottom, we, too, should have rights. ...