java之jvm学习笔记十三(jvm基本结构)
这一节,主要来学习jvm的基本结构,也就是概述。说是概述,内容很多,而且概念量也很大,不过关于概念方面,你不用担心,我完全有信心,让概念在你的脑子里变成图形,所以只要你有耐心,仔细,认真,并发挥你的想象力,这一章之后你会充满自信。当然,不是说看完本章,就对jvm了解了,jvm要学习的知识实在是非常的多。在你看完本节之后,后续我们还会来学jvm的细节,但是如果你在学习完本节的前提下去学习,再学习其他jvm的细节会事半功倍。
为了让你每一个知识点都有迹可循,希望你按照我的步骤一步步继续。
---------------------------------------------------------------------------------------
知识点1:什么是java虚拟机(你以为你知道,如果你看我下面的例子,你会发现你其实不知道)
第一步:先来写一个类:
package test;public class JVMTestForJava {public static void main(String[] args) throws InterruptedException {Thread.sleep(10000000);}}
第二步:cmd窗口输入:java tet.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我代码
package test;public class JVMTestLife {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {for(int i=0;i<5;i++){try {Thread.currentThread().sleep(i*10000);System.out.println("睡了"+i*10+"秒");} catch (InterruptedException e) {System.out.println("干嘛吵醒我");}}}}).start(); for(int i=0;i<50;i++){System.out.print(i);}}}
第二步:ecplise里run application
第三步:打开任务管理器-进程,看到一个javaw.exe的虚拟机在跑
第四步:查看控制台输出,并观察任务管理器中的javaw.exe什么时候消失
0 睡了0秒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秒睡了20秒睡了30秒睡了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栈(笔记十一)。
--------------------------------------------------忽略虚线间的废话-------------------------------------------------
好了,还剩这么多没讲过。不过不要急,我一向提倡,学到哪里讲到哪里,看到哪里。所以没有学到的概念,让他随风去。
但是我还是会来串一下思路滴:首先,当一个程序启动之前,它的class会被类装载器装入方法区(不好听,其实这个区我喜欢叫做Permanent区),执行引擎读取方法区的字节码自适应解析,边解析就边运行(其中一种方式),然后pc寄存器指向了main函数所在位置,虚拟机开始为main函数在java栈中预留一个栈帧(每个方法都对应一个栈帧),然后开始跑main函数,main函数里的代码被执行引擎映射成本地操作系统里相应的实现,然后调用本地方法接口,本地方法运行的时候,操纵系统会为本地方法分配本地方法栈,用来储存一些临时变量,然后运行本地方法,调用操作系统api等等。
好吧,你听晕了,我知道,先记住这段话的位置,等某年某月我提醒你回来看,你就焕然大悟了,现在你只需要走马观花咯!!!
--------------------------------------------------忽略虚线间的废话-------------------------------------------------
知识三这一个知识点,我们重点讲java栈
什么是栈,看图说话!
上图就是一个栈,当有一个变量long i=1的时候,它在栈中就是这么存放的(windows下,小端存储,十六进制存放),如果再来一个临时变量int j呢,那么它从00 00 00 05开始存起,再来一个临时变量int k呢,那么00 00 00 04开始存,是滴,这就是所谓的栈的生成方向(向下伸展),也就是存数据是往高地址还是低地址延展(笔者材疏学浅,还没有见过有向上伸展的栈),至于为什么要向下伸展,向下伸展有个好处,就是当一个方法A调用另外一个方法B的时候,当被调用的方法B结束的时候,它刚好到了调用它的方法A的栈底,这样可以减少指针的回溯。
从这个栈帧中,你或许也发现了,每个存储单元(上图中一个个的小格子)存放的都是8个十六进制的数,是滴java虚拟机要求,虚拟机的存储单元只要要能存储,byte,short,char,int,float,returnAddress和reference(returnAddress和reference后面会讲),所以虚拟机的存储单元至少为四个字节,也就是32位,所以long和double在java虚拟机中夸两个存储单元的,就好像我们上面的例子,long i=1,看似一个简单的数据,但是它在虚拟机的内存中其实无形之中动用了两个存储单元。
待续............