带着新人看java虚拟机03

  分享一篇博客:https://blog.csdn.net/yfqnihao/article/details/8289363,本篇有部分参考这篇博客!!!

  还是继续说一下java虚拟机,为什么呢?因为我随意翻着别人的博客一不小心看到有关jvm的一点新的东西,挺有趣的,就按照我的理解分享一下;

  还记得以前学过一首诗,“看成岭侧成峰,远近高低各不同”,这一句诗的内在含义有的时候真的会让你猛然惊醒,进而如获至宝!的确,有的时候换一个角度看问题,你会发现不一样的世界。

  我们平常学java的时候肯定涉及到了进程,多线程的概念,但是有没有想过操作系统也有进程和线程的概念,两者有关系吗?假如我们视角放高一点,以操作系统的角度看看一个java程序的运行,又会是什么样子的呢?jvm在将字节码文件翻译成机器码之后怎么会调用cpu呢?自己调用的还是假借了谁的手呢?jvm在操作系统中到底扮演着一个什么角色呢?还有最基本的一个问题,操作系统是什么?

  下面我们就来把这些东西整个的给串一下,当然,具体的细节还要每个人自己去研究;

 

1.简单看看操作系统

  水平有限,不可能对操作系统理解得那么透彻,只是说说我自己的理解吧!

  一说起操作系统大家肯定既陌生又贼熟悉,为什么呢?因为我们经常使用操作系统,比如windows系列,unix系列,macos等等,我们每天一打开电脑首先就会自动启动一个操作系统,但是又有几个人真的能了解透彻操作系统呢?我们总是在操作系统中用着几个最常见的应用,qq、LOL、淘宝、360、优酷等等软件,然而我们却很少关注操作系统到底能干什么,假如没有操作系统会怎么样?

  我们现在用的所有计算机结构都是由冯•诺依曼计算机演变过来的,下面我们简单看看冯诺依曼计算机结构:

 

  我们可以看看,我们使用电脑基本就是用这几部分,有这几部分足够我们运行一个程序了啊!那么,我们需要操作系统干嘛呢?

  首先我们要考虑到一点,由于运算器是能识别二进制码,所以我们在输入设备中只能输入很多很多的0和1组成的代码,这样的代码简直就是一股泥石流,让人绝望,然而早期的计算机还就是这样编程的,通过我们人工的方式写很多的0和1去对那些屏幕、cpu等硬件的驱动程序(比如声音驱动、显卡驱动,再驱动程序再底层其实就是用高低电位去使得硬件发生变化)发出指令进行操作;这个时候可没有什么线程什么的,没有cpu等待时间什么的,可想而知效率感人!

  在操作系统没有出来的时候编程真的很痛苦,一般人真干不了!过了很多年之后,终于有了操作系统,操作系统本质上是一个软件,我们可以简单的把操作系统看作是对这些驱动的封装,在操作系统内部(可以叫做内核)提供了两类接口,一类是只要那些硬件驱动程序实现了这些接口,操作系统就通过这些接口使用那些硬件设备;然后另一类接口就是给我们程序员去实现的,我们只需要面向这些接口编程我们就可以间接的操作硬件!

  而我们熟悉的JVM就是实现了操作系统提供的给程序员实现的那一类接口,而JVM又向JAVA程序员提供了一些java api,我们只需要按照java api就能间接的通过jvm对操作系统进行控制,进而对驱动发指令,最后就是可以对那些硬件中的数据进行处理;

  于是我们可以看看下面这个图(暂时忽略UNIX命令和库),这个图中用户编写的应用程序可以看作是JVM,JVM可以根据操作系统提供的系统调用接口对操作系统内核发出一些指令,内核就会调用硬件的驱动程序对硬件进行一些操作,比如读取文件数据、向文件中写入数据等等

  由于操作系统提供了这么强大的功能,于是我们现在几乎所有的编程语言都是在操作系统的基础上进行编程,然后通过解释器这类东西将我们写的程序转化为计算机能够识别的机器码,然后调用操作系统接口,最终实现对硬件的操作。

  顺便一提,这里用户编写的应用程序我们就可以看作是QQ,优酷,酷狗音乐等等软件,如果是单核cpu,你同时打开多个应用,那么cpu会来回在多个应用切换,只是由于切换时间太短,我们感觉不到,还以为是同时运行的!就好像电影,其实电影是一张张图每隔零点几秒进行翻页,但是我们眼睛察觉不出来,还有我们鼠标移动在屏幕上移动,为什么这么流畅呢?因为我们的屏幕每隔零点几秒就刷新一次,当然我们察觉不出来。

   现在我们看看操作系统的定义:是一组控制和管理计算机硬件和软件资源,合理对各类作业进行调度,以及方便用户的程序的集合”,是不是有点懂了,我们继续往下看!

 

2.操作系统的内存结构

  我们可以简单看看操作系统的内存结构,一般查资料我们看到的是下图这样的,我们肯定看不懂!玛德,这都是什么鬼,除了那个栈和堆其他的都不知道干嘛的!

 

  于是我们可以稍作修改,去掉一些不利于我们理解的东西,如下图所示,是不是就熟悉了一点了,貌似跟jvm的内存结构很像啊!!!

 

   我们把硬盘和操作系统的PC寄存器添加上去试试效果,下图所示!有没有似曾相识的感觉,对的,jvm跟这个几乎一模一样,这里的硬盘其实就是相当于jvm的方法区(方法区也叫做永久代,看名字就知道是可以永久保存的啊!),还有jvm中的本地方法栈不在jvm的内存结构中,原来在这里啊,其实指的就是操作系统的栈;

  操作系统的PC寄存器和jvm的PC寄存器用处差不多,是记录当前CPU运行命令的地址;

 

 3.jvm在操作系统中的角色

  举个简单的例子,当我们在电脑桌面上双击了QQ,那么QQ这个应用就会启动,操作系统就会创建一个进程,然后会在操作系统的堆中为这个进程开辟空间,用于存放该进程中产生的数据;

  对操作系统来说,JVM和这个QQ应用没有什么两样,一视同仁,于是我们可以得到这样的一个图:

 

  最后我们再把jvm中的内存结构完善一下,如下图:

 

  看到这里,我们不禁的笑了,原来jvm就是按照操作系统的样子做了一遍,假如我们站在操作系统角度看,jvm其实就是一个平常的应用;假如我们站在java字节码文件的角度看,其实jvm就相当于一个操作系统,把字节码文件放进了jvm这个虚拟操作系统的硬盘中(方法区)中,然后jvm中对方法区中的数据进行读取、创建对象等后续各种操作;

  操作系统栈和jvm栈基本一样;操作系统堆和jvm堆基本也一样,但是释放内存的方式有点差异,操作系统的堆是要程序员手动释放,而jvm的堆是靠gc自动清理;

  现在我们再百度一下看看jvm的定义,现在应该知道jvm是个什么东西了吧!哈哈

 

 

4.方法区中的信息

   虽然我们之前简要的说了方法区中的数据就是有关于类的基本信息,静态变量和常量池,但是说得比较笼统;

   现在我们再回头看看jvm方法区中存的具体是什么信息:

  (1)类信息

  (2)字段信息

  (3)方法信息

  (4)常量池

  (5)静态变量

  (6)一个到Class对象的引用

  (7)一个到加载该类的类加载器的引用

  (8)方法表(有的JVM有,有的JVM没有):这是一个数组,保存了该类在堆中创建的所有对象的实例方法的引用,但是比较耗内存,所以有的jvm设计者没有设计这个方法表;

  对应的java代码如下:

public final class com.wyq.test.ClassStruct extends Object implements Serializable {//1.类信息:包括访问修饰符,全类名,父类名,实现接口等
   //2.对象字段信息:包括访问修饰符,类型,字段名
   private String name;
   private int id;
 
   //4.常量池:常量和一些符号引用
   public final int CONST_INT=0;
   public final String CONST_STR="CONST_STR";
    
    //5.静态变量
   public static String static_str="static_str";
    
 
 //3.方法信息:包括修饰符,方法返回值,方法名,局部变量,方法体(花括号中的内容)
   public static final String getStatic_str (){
      return ClassStruct.static_str;
 }}

  可以清楚的看到其实方法区中保存的就是将字节码文件中的所有信息!   

 

5.java程序执行流程

  现在我们结合操作系统的知识和上图看看我们运行一个java程序,电脑中到底是干了什么?就不考虑缓存了。。。

  1.我们在Eclpse中写完一个java源程序,必须要Ctrl+S保存一下,这个动作就是将源程序保存到电脑硬盘中;

  2.然后我们运行一个main方法,这个时候编译器就会将硬盘中的源程序读取到内存并编译成字节码文件(注意这个字节码文件不一定非要是本计算机编译的,只要是符合字节码文件格式的字节码文件都行,别人电脑传给你的肯定可以;这符合java一处编译到处运行的原则)

  3.我们运行一个main方法的同时,对操作系统来说就是新创建了一个进程,就要在操作系统的堆中申请这个进程的内存空间,我们把这块内存空间称为JVM(注意,假如运行两个java程序那么这里就会创建两个jvm的内存空间)

  4.jvm实例创建成功,就会在这个实例之中的内存空间进行分配,java栈,java堆,方法区等

  5.由于类装载子系统,会把类加载器先加载到java堆中,然后类加载器根据我们的java源程序类名去指定路径中去加载字节码文件(双亲委托机制),放入方法区中

  6.由执行引擎去执行这个字节码文件,伴随着验证、准备、解析和初始化,最终在java堆中生成了Class对象和实例化对象。

  7.假如调用本地方法(就是Native修饰的方法)就会涉及到本地方法栈(和java栈作用差不多,压栈和弹栈),在操作系统栈中压入栈帧,假如在本地方法中还调用java中的方法,这个时候在操作系统的栈中压入一个栈帧,然后下一个栈帧却到了jvm的栈中,很有趣的一个东西。

  8.我们方法调用完毕,栈中栈帧弹出,栈清理完毕;然后gc会对java堆中对象进行回收释放内存空间,然后gc还会对方法区进行清理,自此jvm中的内存空间清理完毕;

  9 操作系统堆jvm进行清理,jvm进程结束。

 

总结: 

  由于对操作系统的理解还处于比较懵懂的状态,所以文中可能会有很多词语运用不当,很惭愧,以后肯定会抽个时间慢操作系统整个的学习一遍的,希望这一天不要来的太迟,哈哈哈

 

posted @ 2019-04-20 14:54  java小新人  阅读(616)  评论(0编辑  收藏  举报