关于java虚拟机的学习(一)

时间:2021-12-05 10:25:19

前两周学习了计算机系统的内存分配相关的知识,类比着系统,回头看一次java虚拟机,本次学习的重点是了解构造、了解每个部分的作用。

关于java虚拟机的学习(一)
如上图1-1是虚拟机的运行时数据区,顾名思义其实虚拟机全部构造并没有完全展现出来,此图只是运行时的数据结构。

关于java虚拟机的学习(一)
上图1-2是操作系统中一个线程所对应的内存结构示意图
关于java虚拟机的学习(一)
上图1-3是操作系统中内核线程对应进程的关系

首先说一下操作系统,操作系统中,

  1. 每个进程 都有一个虚拟的地址空间(地址从下至上越来越大),这个地址空间是分块的,分为了heap(向上生长)、stack(向下生长)、只读的代码和数据、其他区域。
  2. 进程中存在多个线程,其中只有stack和寄存器是线程独立的,每个线程会被分配一个自己的stack和寄存器,而函数栈帧在这里面。其他内容由线程共享,比如堆里的数据、函数代码

jvm是仿照cpu以及内存的运行机制操作系统写成的虚拟机,它的内部机制和操作系统非常相似,但是它时 如何与操作系统结合的估计看了源码才能知道,这里留个坑,在JVM中:

  1. jvm本身就是一个进程,它拥有线程所拥有的结构,堆、方法区等等
  2. jvm内部的线程,则拥有独立的栈、本地方法栈(这个是jvm自行实现的特色)、pc(其实就是记录当前运行到哪个字节码的东西,相当于cpu里面的pc)

参考了这位大大的博客http://java-mzd.iteye.com/blog/848635
http://www.cnblogs.com/langtianya/p/4441206.html

接下来对比一下”一个c语言的可执行文件在操作系统中执行,内存的分配”和“一个java语言的.class文件在被jvm执行时内存的分配”
c语言文件中有其他文件的链接、全局变量、函数、函数内部的局部变量、函数内部的函数调用、malloc分配的堆上空间等
.class文件就是一个*类,他包括了类的非静态与静态属性域,类的非静态与静态方法、构造函数、方法内部的局部变量、方法内部的方法调用等

c的文件我对应1-2的图进行说明,连接等等属于编译期需要的内容,在预处理之后就会消失了,最后经过编译、汇编、链接等操作只剩下了全局变量、函数、函数内部的局部变量、函数内部的函数调用

  1. 全局变量存储于堆和栈下方的区域,一般是全局变量区域,是本进程内部的一个区域,与其他进程不共享,但是本进程内部的线程可以共享
  2. 函数在被执行时,以栈帧的形式将被调用的函数地址以及局部变量存放在栈中,其他函数的调用会使得栈帧继续增加push,即新的函数地址以及新的局部变量又会放入栈帧中,函数的返回也存放在栈帧中,返回时,栈帧pop
  3. malloc分配的空间在堆上,但是它对应的指针或者引用是存放在栈中的,其他线程的栈上的指针可以修改堆里面的内容,因为堆是线程共享的

.class文件已经经过了编译阶段,这个地方有一个很容易混淆的地方,而且是不同于c语言的,由于面向对象的存在,java这种语言是存在类和对象两种东西的,一个.class文件其实生成了一个类,也就是类的具体信息的一些代码,而并没有生成对象,对象在new关键字的作用下才会生成。

说到此处有必要清晰的说明一下方法区存放的信息:

  1. 类的基本信息:
    每个类的全限定名、每个类的直接超类的全限定名(可约束类型转换)、该类是类还是接口、该类型的访问修饰符、直接超接口的全限定名的有序列表;
    说通俗一点就是我们的写的类的相关代码大多数都在这里。
  2. 已装载类的详细信息:
    运行时常量池、字段信息、方法信息;
    我们写的声明成员变量的代码、方法的代码、常量基本都在这里了
  3. 静态变量:其实跟c里面的全局变量一样;
  4. 到类classloader的引用:类加载器,这个东西需要我研究下jvm类的加载之后再来填坑;
  5. 到类class的引用:经常说类是抽象的,其实类class的实例就是类的具象,所有的对象都是它的构造出来的,这也是java反射的基础。

由此我们可以知道反射的基础:在装载类的时候,加入方法区中的所有信息,最后都会形成Class类的实例,代表这个被装载的类。方法区中的所有的信息,都是可以通过这个Class类对象反射得到。我们知道对象是类的实例,类是相同结构的对象的一种抽象。同类的各个对象之间,其实是拥有相同的结构(属性),拥有相同的功能(方法),各个对象的区别只在于属性值的不同。
同样的,我们所有的类,其实都是Class类的实例,他们都拥有相同的结构—–Field数组、Method数组。而各个类中的属性都是Field属性的一个具体属性值,方法都是Method属性的一个具体属性值。

举个栗子:一个Test.class文件里面定义了一个Test类,Test里面包含一个非静态成员变量(com.taobao.List),这些信息是被放在方法区的,当这个类被装载的时候,上述列表中的4类装载器classloader的引用指向了classloader,classloader装载上面列表中123里面的信息,然后生成一个class实例也就是5,这个5的内容包含了所有的类信息包括Field数组和Method数组,当Test类的对象被这个class实例整出来的时候,这个对象test被分配在堆中,它内部包含一个com.taobao.List的引用,那这个引用显然也只能存在于堆中了。而test里面的方法是存在与方法区里面的,其实依旧是类的那个方法,只是test拥有了一个指向此方法的引用。

  1. 比如test这个对象,对象的引用应该是存在与栈上的,由线程分配,而对象的内容存在于堆中。(这个地方我真的不是很透彻,需要继续学习,说的可能不对)
  2. 对象的非静态成员变量是被分配在堆中的。
  3. 对象的方法不管是静态还是非静态都是指向在方法区的,如上面所述的那样
  4. 类/所有对象的静态变量也是被分配在方法区的,方法区内部有静态区,静态专门存放静态变量和静态块,注意静态方法 是没用必要放在静态区的,因为它与非静态方法的区别仅仅在于是否需要一个对象引用(this)作为参数传入(由编译器完成)。
  5. 类的方法内部的局部变量被分配在栈帧中,在方法返回的时候被释放掉,它与非静态成员变量的区别在于,非静态成员变量是对象被分配空间时。

通过对比其实发现两者有很多的相似之处,而不同之处就是对象这一概念的理解。