Java强化知识Day01----------JVM

JVM

Java虚拟机, 简称JVM, 是运行所有Java程序的假想计算机,是Java程序的运行环境之一,也是Java 最具吸引力的特性之一。我们编写的Java代码,都运行在JVM 之上。
在这里我们来简单的介绍一下虚拟机的内容,所知有限,不是太完全,仅供没有接触过的同学一个入门认知。

堆和栈

java自动管理堆(heap)和(栈),程序员不能直接的设置堆和栈。

操作系统的堆和栈

  • 堆(操作系统):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,分配方式类似于链表。(JVM运行于此)
  • 栈(操作系统):由操作系统自动分配释放,存放函数的参数值,局部变量值等。操作方式与数据结构中的栈相类似。

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

示例图

在这里插入图片描述
上图表明:jvm虚拟机位于操作系统的堆中,并且,程序员写好的类加载到虚拟机执行的过程是:当一个classLoder启动的时候,classLoader的生存地点在jvm中的堆,然后它会去主机硬盘上将A.class装载到jvm的方法区,方法区中的这个字节文件会被虚拟机拿来new A字节码(),然后在堆内存生成了一个A字节码的对象,然后A字节码这个内存文件有两个引用一个指向A的class对象,一个指向加载自己的classLoader

Java虚拟机栈(Java Virtual Machine Stack)

  • 线程私有的,它的生命周期与线程相同,每个线程都有一个。

  • 每个线程创建的同时会创建一个JVM栈,JVM栈中每个栈帧存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double;和reference (32 位以内的数据类型,具体根据JVM位数(64为还是32位)有关,因为一个solt(槽)占用32位的内存空间 )、部分的返回结果,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址;

  • 每一个方法从被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

栈运行原理:栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中,A方法又调用了B方法,于是产生栈帧F2也被压入栈,B方法又调用了C方法,于是产生栈帧F3也被压入栈…… 依次执行完毕后,先弹出后进…F3栈帧,再弹出F2栈帧,再弹出F1栈帧。

  • JAVA虚拟机栈的最小单位可以理解为一个个栈帧,一个方法对应一个栈帧,一个栈帧可以执行很多指令如下图:在这里插入图片描述
  • 对上图中的动态链接解释下,比如当出现main方法需要调用method1()方法的时候,操作指令就会触动这个动态链接就会找打方法区中对于的method1(),然后把method1()方法压入虚拟机栈中,执行method1栈帧的指令;此外如果指令表示的代码是个常量,这也是个动态链接,也会到方法区中的运行时常量池找到类加载时就专门存放变量的运行时常量池的数据。

本地方法栈

  • 先解释什么是本地方法:jvm中的本地方法是指方法的修饰符是带有native的但是方法体不是用java代码写的一类方法,这类方法存在的意义当然是填补java代码不方便实现的缺陷而提出的。案例介绍将在 下面知识点仔细介绍。

  • 作用同java虚拟机栈类似,区别是:虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到的Native方法服务。

  • 是线程私有的,它的生命周期与线程相同,每个线程都有一个。

Java 堆(Java Heap)

1)是Java虚拟机所管理的内存中最大的一块。
2)堆是jvm所有线程共享的。
3)在虚拟机启动的时候创建。
4)唯一目的就是存放对象实例,几乎所有的对象实例以及数组都要在这里分配内存。
5)Java堆是垃圾收集器管理的主要区域。
6)因此很多时候java堆也被称为“GC堆”(Garbage Collected Heap)。从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆还可以细分为:新生代和老年代;新生代又可以分为:Eden 空间、From Survivor空间、To Survivor空间。
7)java堆是计算机物理存储上不连续的、逻辑上是连续的,也是大小可调节的(通过-Xms和-Xmx控制)。
8)如果在堆中没有内存完成实例的分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

方法区(Method Area)

1)在虚拟机启动的时候创建。
2)所有jvm线程共享。
3)除了和堆一样不需要不连续的内存空间和可以固定大小或者可扩展外,还可以选择不实现垃圾收集。
4)用于存放已被虚拟机加载的类信息、常量、静态变量、以及编译后的方法实现的二进制形式的机器指令集等数据。
5)被装载的class的信息存储在Methodarea的内存中。当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件内容并把它传输到虚拟机中。
6)运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

方法区补充

指令集是个非常重要概念,因为程序员写的代码其实在jvm虚拟机中是被转成了一条条指令集执行的
在这里插入图片描述
首先看看上面各部位位于13图中的那些位置:左侧的foo代码是指令集,可见就是在方法区,程序计数器就不用说了,局部变量区位于虚拟机栈中,右侧最下方的求值栈(也就是操作数栈)我们从动图中明显可以看出存在栈顶这个关键词因此也是位于java虚拟机栈的。
另外,图中,指令是Java代码经过javac编译后得到的JVM指令,PC寄存器指向下一条该执行的指令地址,局部变量区存储函数运行中产生的局部变量,栈存储计算的中间结果和最后结果。
上图的执行的源代码是:

public class Demo {
    public static void foo() {
       int a = 1;
       int b = 2;
       int c = (a + b) * 5;
    }
}

下面简单解释下执行过程,注意:偏移量的数字只是简单代表第几个指令哦,首先常数1入栈,栈顶元素就是1,然后栈顶元素移入局部变量区存储,常数2入栈,栈顶元素变为2,然后栈顶元素移入局部变量区存储;接着1,2依次再次入栈,弹出栈顶两个元素相加后结果入栈,将5入栈,栈顶两个元素弹出并相乘后结果入栈,然后栈顶变为15,最后移入局部变量。执行return命令如果当前线程对应的栈中没有了栈帧,这个Java栈也将会被JVM撤销。

本地(native)方法

(1)本地方法就是带有native标识符修饰的方法;
(2)native修饰符修饰的方法并不提供方法体,但因为其实现体是由非java代码在在外部实现的,因此不能与abstract连用;
(3)存在的意义:不方便用java语言写的代码,使用更为专业的语言写更合适;甚至有些JVM的实现就是用c编写的,所以只能使用c来写,
(4)更多的本地方法最好是与jdk的执行引擎的解释器语言一致(执行引擎、解释器:参考上面的执行引擎)
(5)Windows、Linux、UNIX、Dos操作系统的核心代码大部分是使用C和C++编写,底层接口用汇编编写
(6)为什么native方法修饰的修饰的方法PC程序计数器为undefined。读懂上面的所有知识点可以就很容易自己理解了。在一开始类加载时,native修饰的方法就被保存在了本地方法栈中,当需要调用native方法时,调用的是一个指向本地方法栈中某方法的地址,然后执行方法直接与操作系统交互,返回运行结果。整个过程并没有经过执行引擎的解释器把字节码解释成操作系统语言,PC计数器也就没有起作用。

方法区与堆的区别

  • 方法区存放了类的信息,有类的静态变量、final类型变量、field自动信息、方法信息,处理逻辑的指令集,我们仔细想想一个类里面也就这些东西,而堆中存放是对象和数组,咋一看好像方法区跟堆的作用是一样的。

  • 其实呢,这里就关系到我们平时说的对象是类的实例,这里的对应关系就是 :“方法区->类“,“堆->对象”,

  • 以“人”为例就是,堆里面放的是你这个“实实在在的人,有血有肉的”,而方法区中存放的是描述你的文字信息,如“你的名字,身高,体重,还有你的行为,如吃饭,走路等”。

  • 再者我们从另一个角度理解,就是从前我们得知方法区中的类是唯一的,同步的。但是我们在代码中往往同一个类会new几次,也就是有多个实例,既然有多个实例,那么在堆中就会分配多个实例空间内存。

  • 方法区的内容是一次把一个工程的所有类信息都加载进去再去执行还是边加载边执行呢?

  • 其实单从性能方面也能猜测到是只加载当前使用的类,也就是边加载边执行。例如我们使用tomcat启动一个spring工程,通常启动过程中会加载数据库信息,配置文件中的拦截器信息,service的注解信息,一些验证信息等,其中的类信息就会率先加载到方法区。但如果我们想让程序启动的快一点就会设置懒加载,把一些验证去掉,如一些类信息的加载等真正使用的时候再去加载,这样说明了方法区的内容可以先加载进去,也可以在使用到的时候加载。

方法区,栈、堆之间的过程

类加载器加载的类信息放到方法区,–》执行程序后,方法区的方法压如栈的栈顶–》栈执行压入栈顶的方法–》遇到new对象的情况就在堆中开辟这个类的实例空间。(这里栈是有此对象在堆中的地址的)

类加载器子系统(class loader subsystem)

  • 1、根据给定的全限定名类名(如java.lang.Object)来装载class文件的内容到Runtimedataarea中的methodarea(方法区域)。Java程序员可以extends java.lang.ClassLoader类来写自己的Classloader。
  • 2、 对(1)中的加载过程是:当一个classloader启动时,classloader的生存地点在jvm中的堆,然后它去主机硬盘上去装载A.class到jvm的methodarea(方法区),方法区中的这个字节文件会被虚拟机拿来new A字节码,然后在堆内存生成了一个A字节码的对象,然后A自己码这个内存文件有两个引用,一个指向A的class对象,一个指向加载自己的classloader。见下图:
    在这里插入图片描述
执行引擎(Executionengine子系统)
  • 负责执行来自类加载器子系统(class loader subsystem)中被加载类中在方法区包含的指令集,通俗讲就是类加载器子系统把代码逻辑(什么时候该if,什么时候该相加,相减)都以指令的形式加载到了方法区,执行引擎就负责执行这些指令就行了。
  • 程序在JVM主要执行的过程是执行引擎与运行时数据区不断交互的过程,可理解为上面“方法区中的动图”
  • 但是执行引擎拿到的方法区中的指令还是人能够看懂的,这里执行引擎的工作就是要把指令转成JVM执行的语言(也可以理解成操作系统的语言),最后操作系统语言再转成计算机机器码。

顺便补充一下java里的ClassLoader(类加载器)

  • java.lang.ClassLoader是一个类,它负责根据一个指定的类的全限定名,找到对应的Class字节码文件,然后加载它,将字节码文件转为一个Java.lang.Class类的一个实例

类加载器的划分

  • 大部分java程序会使用以下3中系统提供的类加载器

启动类加载器(Bootstrap ClassLoader):

这个类加载器负责将\lib目录下的类库加载到虚拟机内存中,用来加载java的核心库,此类加载器并不继承于java.lang.ClassLoader,不能被java程序直接调用,代码是使用C++编写的.是虚拟机自身的一部分.

扩展类加载器(Extendsion ClassLoader):

这个类加载器负责加载\lib\ext目录下的类库,用来加载java的扩展库,开发者可以直接使用这个类加载器.

应用程序类加载器(Application ClassLoader):

  • 这个类加载器负责加载用户类路径(CLASSPATH)下的类库,一般我们编写的java类都是由这个类加载器加载,这个类加载器是CLassLoader中的getSystemClassLoader()方法的返回值,所以也称为系统类加载器.一般情况下这就是系统默认的类加载器.
  • 除此之外,我们还可以加入自己定义的类加载器,以满足特殊的需求,需要继承java.lang.ClassLoader类.

双亲委派机制

VM在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
**例如:**当jvm要加载Test.class的时候,
(1)首先会到自定义加载器中查找(其实是看运行时数据区的方法区有没有加载),看是否已经加载过,如果已经加载过,则返回字节码。
(2)如果自定义加载器没有加载过,则询问上一层加载器(即AppClassLoader)是否已经加载过Test.class。
(3)如果没有加载过,则询问上一层加载器(ExtClassLoader)是否已经加载过。
(4)如果没有加载过,则继续询问上一层加载(BoopStrap ClassLoader)是否已经加载过。
(5)如果BoopStrap ClassLoader依然没有加载过,则到自己指定类加载路径下(“sun.boot.class.path”)查看是否有Test.class字节码,有则返回,没有通知下一层加载器ExtClassLoader到自己指定的类加载路径下(java.ext.dirs)查看。
(6)依次类推,最后到自定义类加载器指定的路径还没有找到Test.class字节码,则抛出异常ClassNotFoundException。

为什么要使用这种加载方式

1,类加载器代码本身也是java类,因此类加载器本身也是要被加载的,因此显然必须有第一个类加载器不是Java类,这就是bootStrap,是使用c++写的,其他的是java。
2,虽说bootStrap、extclassLoader、appclassloader三个是父子类加载器关系,但是并没有使用继承,而是使用了组合关系。
3,优点,具备了一种带优先级的层次关系,越是基础的类,越是被上层的类加载器进行加载,可以比较笼统的说像jdk自带的几个jar包肯定是位于最顶级的,再就是我们引用的包,最后是我们自己写的,保证了java程序的稳定性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值