JVM虚拟机

虚拟机是一台虚拟的计算机,是一款软件,用来执行一系列虚拟计算机指令。分为两种虚拟计算机和程序计算机

系统虚拟机是完全对物理计算机的仿真,提供了一个可以运行完整操作系统的软件平台,VMware

程序虚拟机是专门为执行某个单个计算机程序而设计的,JVM

jvm的作用

Java 虚拟机负责装载字节码到其内部,解释/编译为对应平台上的机器码指令执行

特点:

一次编译,到处执行

自动内存管理

自动垃圾回收功能

jvm组成部分

jvm由类加载器、运行时数据区、执行引擎、本地方法接口四个部分组成

程序的.class文件jvm的运行流程:jvm先将字节码文件通过类加载器中把文件加载到内存中的运行数据区,由执行引擎将字节码翻译成底层系统指令,交由CPU执行,而这个过程中需要调用其他语言的接口 本地库接口(Native
Interface) 来实现整个程序的功能,

在这里插入图片描述

类加载子系统

在这里插入图片描述

作用:负责从硬盘、网络中加载字节码文件,加载的类信息存放在方法区中

类加载过程

在这里插入图片描述

加载

1.通过类名来获取此类的二进制字节流

2.将字节流的静态存储的数据结构转换为方法区运行的结构

3.在内存中生成一个代表这个类的 java.lang.Class 对象,作为这个类的各种数

据的访问入口.

链接

验证:检验被加载的类是否有正确的内部结构,并和其他类协调一致

对字节码文件的格式进行验证:class 文件在文件开头有特定的文件标识(字节码文件都以 CA FE BA BE 标识开头);主,次版本号是否在当前 java 虚拟机接收范围内.

元数据验证:对字节码描述的信息进行语义分析。来保证信息符合java语言的规范要求

准备:负责为静态属性分配内存,并设置默认初始值

 public static int value = 123//value 在准备阶段后的初始值是 0,而不是 123.

不包含用 final 修饰的 static 常量,在编译时进行初始化.

解析:将符号引用转为直接引用,将字节码中的表现形式,转为内存中的表现形式(内存地址)

初始化

类的初始化,为类中定义的静态变量进行赋值

public static int value = 123//value 是 123.

类什么时候被加载

1.在类中运行main方法

2.创建类的实例

3.访问类的静态变量、静态方法

4.反射(Class.forName(“”))

5.子类被加载

以下两种情况不会被初始化

1.引用该类中的静态常量,

2.构造某个类的数组时不会导致该类的初始化

类加载器分类

从jvm的角度,类加载器分为两种:

1.引导类加载器(启动类加载器)

2.其他所有类加载器,这些类加载器由java语言实现,并独立存在与虚拟机外部,全部继承自抽象类java.lang.ClassLoader

从java开发人员的角度来看,java一直保证三种类加载器:

1.引导类加载器,用c/c++语言实现,嵌套在jvm内部,用来加载java核心类库,与java语言无关,没有父加载器,负责加载扩展类加载器和应用程序加载器,并为他们指定父类加载器

2.扩展类加载器,Java 语言编写的,由 sun.misc.Launcher$ExtClassLoader 实现.派生于 ClassLoader 类.从 java.ext.dirs 系统属性所指定的目录中加载类库,或从 JDK 系统安装目录的jre/lib/ext 子目录(扩展目录)下加载类库.如果用户创建的 jar 放在此目录下,也会自动由扩展类加载器加载.

3.应用程序类加载器,Java 语言编写的,由 sun.misc.Launcher$AppClassLoader 实现.派生于 ClassLoader 类.加载我们定义的类,该类加载器是程序中默认的类加载器

双亲委派机制

​ java虚拟机对class文件采用是按需查找的方式,当需要该类的时候才会将该类的class文件加载到内存中生成class对象。在加载某个类的class文件时,java虚拟机采用双亲委派模式,即将请求交给父类处理。

原理:

​ 1.如果一个类加载器收到一个请求,它不会自己先去加载,而是将请求发给它的父类的加载器去执行,如果父类加载器还存在父类加载器,则进一步向上委托,请求最终会到达顶层的启动类加载器

​ 2.如果父类加载器可以完成类的加载任务,则成功返回,若无法完成加载任务,子加载器才会尝试自己去加载。这就时双亲委派机制

若均加载失败,就会抛出 ClassNotFoundException 异常。

双亲委派优点

1.安全,可避免用户自己编写的类替换 Java 的核心类,如 java.lang.String.
2.避免类重复加载,当父亲已经加载了该类时,就没有必要子 ClassLoader 再加
载一次.

如何打破双亲委派机制

可以通过自定义类加载器,重写类加载方法打破双亲委派机制

ClassLoader类中涉及类加载的方法有两个findClass、loadClass方法

重写loadClass方法(是实现双亲委派逻辑的地方,重写该方法会破坏双亲委派机制)

重写findClass方法,不会打破双亲委派机制

tomcat有自己的自定义加载器

运行时数据区

1.程序计数器

是一块很小的内存空间,用来记录每个线程当前正在运行的指令位置,每个线程都拥有一个程序计数器,生命周期与线程一致,不会出现内存溢出的情况

2.本地方法栈

用来运行本地方法的区域,是线程私有的,空间大小可以调整,会出现内存溢出的情况

3.堆

是java虚拟机中最大的一块区域,被所有线程共享,在虚拟机启动的时候被创建,堆的作用就是存储对象,几乎所有的对象实例都在这里分配内存,堆的大小可以调节,堆在物理上内存空间可以不连续,逻辑上是连续的,在方法结束时堆中的对象不会马上被移除,仅在垃圾回收时才会被移除

堆是垃圾回收的重点区域

堆区域的划分:

堆内存被分为新生代和老年代

新生代又分为伊甸园区(Eden,对象刚开始被创建存储的地方)和幸存者区(Survivor)

在这里插入图片描述

为什么分区

根据对象存货概率进行分类,将存活时间上的对象,放到固定区,减少扫描垃圾时间及GC频率

对象创建内存分配过程

1.new的新对象先放到伊甸园区,此区大小有限制

2.当伊甸园的空间填满时,程序又需要创建对象时,JVM 的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园区中的不再被引用的对象进行销毁.再加载新的对象放到伊甸园区

3.然后将伊甸园区中的剩余对象移动到幸存者 0 区

4.如果再次出发垃圾回收,此时上次幸存下来存放到幸存者 0 区的对象,如果没有回收,

就会被放到幸存者 1 区,每次会保证有一个幸存者区是空的

5.什么时候去养老区呢?默认是 15 次,也可以设置参数,最大值为 15

在对象头中它是由 4 位数据来对 GC 年龄进行保存的,所以最大值为 1111, 即为 15。所以在对象的 GC 年龄达到 15 时,就会从新生代转到老年代

6.在老年区,相对悠闲,当养老区内存不足时,再次触发 Major GC,进行养老区的内存清理.

7.若养老区执行了 Major GC 之后发现依然无法进行对象保存,就会产生 OOM 异常

新生代和老年代的配置比例

一般不会调默认是:默认**-XX:NewRatio**=2,表示新生代占 1,老年代占 2,新生代占整个堆的 1/3

当发现在整个项目中,生命周期长的对象偏多,那么就可以通过调整老年代的大小,来进行调优
在这里插入图片描述

在新生代中Eden、S0、S1的大小也能调整

默认Eden 空间和另外两个 survivor 空间缺省所占的比例是 8 : 1 : 1

分代收集思想 Minor GC、 Major GC、Full GC

部分收集: 新生区收集 Minor GC

老年区收集 Major GC

整堆收集:Full GC 收集整个java堆和方法区的垃圾收集

整堆收集的情况:老年区空间不足,方法区空间不足,System.gc();时

开发区应避免整堆收集

字符串常量池

JDK1.7之后将字符串常量池从方法区移到到堆中,因为方法区的回收效率很低,只有等到方法区空间不足时,才会执行Full GC进行垃圾回收。放到堆里能及时回收

4.栈

描述的是java方法执行的内存模型,每个方法执行时都会创建一个栈帧用来存储局部变量表、动态链接、操作数栈、方法出口等信息

方法的执行过程对应栈帧的出栈、入栈的过程

栈不存在垃圾回收的问题,但会出现内存溢出的情况

栈的运行原理

一个活动的线程中,一个时间点上,只有一个活动栈,只有当前执行的方法的栈帧(栈顶)是有效的,这个栈帧被称为当前栈帧,执行引擎的所有字节码指令只对当前栈帧操作,如果该方法还调用了其他方法,对应的栈帧就会被创建出来放在栈顶,称为新的当前栈帧。

不同线程中所包含的栈帧(方法)是不允许相互引用的

如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,接着虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧

栈帧的内部结构

**局部变量表:**是一组存储变量值的存储空间,用于存放方法参数和方法内部定义的局部变量,对于基本数据类型的变量存储的是他的值,对于引用类型的变量,存储的是指向对象的引用

**操作数栈:**程序中所有的计算过程都是借助操作数栈中完成的

**动态链接:**在方法的执行过程中,可能有需要用到类中的常量,所以必须要有一个引用指向运行时常量

**方法返回地址:**当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。

5.方法区

用来存储加载的类信息,以及即时编译期后的信息,以及运行时常量池

方法区在jvm启动时被创建,被所有线程共享,物理上存储空间不连续,逻辑上是连续的,方法区的大小可以被调整

方法区它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存,运行常量池等。

方法区、栈、堆的关系

栈存储引用变量

堆存储对象

方法区存储类信息
在这里插入图片描述

方法区的内部结构

运行常量池就是一张表,虚拟机指令根据这张表,找到要执行的类名、方法名、参数类型、字面量(常量)等信息,存放编译期间生成的各种字面量(常量)和符号引用

在这里插入图片描述

方法区的垃圾回收:方法区的垃圾收集主要回收两部分内容:运行时常量池中废弃的常量和不再使用的类型。

类被回收的条件:

1.该类中所有的实例都被回收,即java堆中不存在该类及期任何派生子类的实例

2.加载该类的类加载器已被回收

3.该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

本地方法接口

用native关键字修饰的方法,没有方法体

为什么要使用本地方法接口

1.与java外环境交互

执行引擎

执行引擎是java虚拟机核心的组成成分,执行引擎的任务就是将字节码指令解释/编译为对应平台上的本地机器指令

1.前端编译:从 .Java 文件编译为字节码文件的这个过程叫前端编译.

2.后端编译:.class(字节码文件)编译为机器码,后端编译

翻译器:将字节码逐行解释执行,效率低

JIT编译器:将字节码编译,缓存起来,执行更高效,不会立即使用编译器

为什么java是半编译半解释语言

现在 JVM 在执行 Java 代码的时候,通常都会将解释执行与编译执行二者结合起来进行

将一些频繁执行的热点代码进行编译,并缓存到方法区中,提高执行效率

原因:程序启动后,先使用解释器立即执行,省去的编译时间,程序运行一段时间后,对热点编译缓存,提高后续执行效率。采用解释器和编译器结合的方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序J

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值