JVM-基础认识+目录

6 篇文章 0 订阅
6 篇文章 0 订阅

本文转载:https://blog.csdn.net/weixin_45676630/article/details/105777777

参考:https://blog.csdn.net/a_higher/article/details/104638022

1 基本概念

前言 了解jvm和并发的联系

JVM是可运行Java 代码的假想计算机 ,包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收,堆 和 一个存储方法域。JVM 是运行在操作系统之上的,它与硬件没有直接的交互。

2 运行过程

我们都知道Java 源文件,通过编译器,能够生产相应的.Class 文件,也就是字节码文件,而字节码文件又通过Java 虚拟机中的解释器,编译成特定机器上的机器码 。

  • Java 源文件—->编译器—->字节码文件
javac Person.java--->Person.class

Person.java -> 词法分析器 -> tokens流 -> 语法分析器 -> 语法树/抽象语法树 -> 语义分析器 -> 注解抽象语法树 -> 字节码生成器 -> Person.class文件

  • 字节码文件—->JVM—->机器码

每一种平台的解释器是不同的,但是实现的虚拟机是相同的,这也就是Java 为什么能够跨平台的原因了 ,当一个程序从开始运行,这时虚拟机就开始实例化了,多个程序启动就会存在多个虚拟机实例。程序退出或者关闭,则虚拟机实例消亡,多个虚拟机实例之间数据不能共享。

3 类加载机制

类加载机制是指我们将类的字节码文件所包含的数据读入内存,同时我们会生成数据的访问入口的一种 特殊机制。那么我们可以得知,类加载的最终产品是数据访问入口。(我的想法是类似网址)

在这里插入图片描述

  • ① 装载

(1)通过一个类的全限定名获取定义此类的二进制字节流
(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
(3)在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口

  • ② 链接

(1)验证:保证被加载类的正确性 文件格式验证/元数据验证/字节码验证/符号引用验证
(2)准备为类的静态变量分配内存,并将其初始化为默认值
(3)解析 把类中的符号引用转换为直接引用,类装载器装入类所引用的其它所有类。

附:解析也是处理stream数据流,数据流是有规则的,前面是magic + version + constant_pool +access_flags+super+interface等等

符号引用和直接引用(参考https://www.cnblogs.com/shinubi/articles/6116993.html

.符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。符号引用与虚拟机的内存布局无关,引用的目标并不一定加载到内存中。在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。各种虚拟机实现的内存布局可能有所不同,但是它们能接受的符号引用都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。

直接引用:直接引用可以是

(1)直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)

(2)相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)

(3)一个能间接定位到目标的句柄

直接引用是和虚拟机的布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经被加载入内存中了。

  • ③ 初始化

对类的静态变量,静态代码块执行初始化操作

4类加载器

在这里插入图片描述

双亲委派模型:

定义:如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

优势:Java类随着加载它的类加载器一起具备了一种带有优先级的层次关系。比如,Java中的Object类,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object在各种类加载环境中都是同一个类。如果不采用双亲委派模型,那么由各个类加载器自己取加载的话,那么系统中会存在多种不同的Object 类。

总结:

沙箱安全机制:比如自己写的String.class类不会被加载,这样可以防止核心库被随意篡改

避免类的重复加载:当父ClassLoader已经加载了该类的时候,就不需要子CJlassLoader再加载一次

注:如果要自定义类加载器,可以重写内部的findClass方法而不是loadClass方法

5 运行时数据区

在这里插入图片描述

方法区:

(1)方法区是各个线程共享的内存区域,在虚拟机启动时创建
(2)用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
(3)虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却又一个别名叫做Non-Heap(非 堆),目的是与Java堆区分开来
(4)当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常
(5)方法区在JDK8中就是Metaspace【元空间】,在JDK6或7中就是Perm Space【永久代】
(6)Run-Time Constant Pool(运行时常量池)在方法区分配

常量池有3种(参考https://blog.csdn.net/weixin_40999907/article/details/87907083

’运行时常量池(Runtime Constant Pool)

常量池(Constant Pool):也是常说的class文件常量池(class constant pool)

字符串常量池(String Constant Pool)

什么是常量池?
       常量池(class文件常量池):.java文件经过编译后生成.class文件,常量池可以理解为class文件的资源仓库。

常量池存放什么?
       常量池主要存放两大类常量:字面量(文本字符串、声明为final的常量值等)和符号引用(有三类:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符)

总结:常量池(类常量池)就是类在编译后的class文件中的一部分

运行时常量池

什么是运行时常量池?
    运行时常量池是方法区的一部分,如上图所示。class文件中的常量池在类加载后进入方法区的运行时常量池存放。

    运行时常量池相对于class温江常量池的一个重要特征是具有动态性。这是什么意思呢,就是当你的class文件一旦编译后,你的class常量池就是确定了的,而运行时常量池在运行期间也可能有新的常量放入池中(如String类的intern()方法)

String常量池(String Pool)
    String Pool是java堆内存中的存储字符串的一块区域

 

堆:

(1)堆是Java虚拟机所管理内存中最大的一块,在虚拟机启动时创建,被所有线程共享
(2)Java对象实例以及数组都在堆上分配
(3)当堆无法满足内存分配需求时,将抛出OutOfMemoryError异常

Java虚拟机栈:

是描述java 方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成 的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。每个栈帧对应一个被调用的方法,可以理解为一个方法的运行空间。

每个栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、动态链接、方法返回地址(Return Address)和附加信息。栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。

a(){
	b();
}
b(){
	c();
}
c(){

}

在这里插入图片描述

javap -c Person.class > Person.txt
public static int calc(int op1,int op2){
	op1 = 3;
	int result = op1 + op2;
	return result;
}
public static int calc(int, int);
    Code:
       0: iconst_3 // 将int类型常量压入操作数栈
       1: istore_0 // 将int类型值存入局部变量0
       2: iload_0  // 从局部变量0中装载int类型值
       3: iload_1  // 从局部变量1中装载int类型值
       4: iadd 	   // 执行int类型的加法
       5: istore_2 // 将int类型值存入局部变量2
       6: iload_2  // 从局部变量2中装载int类型值
       7: ireturn  // 从方法中返回int类型的数据

本地方法区:

本地方法区和Java Stack 作用类似, 区别是虚拟机栈为执行Java 方法服务, 而本地方法栈则为Native 方法服务, 如果一个VM 实现使用C-linkage 模型来支持Native 调用, 那么该栈将会是一个C 栈,但HotSpot VM直接就把本地方法栈和虚拟机栈合二为一。如果当前线程执行的方法是Native类型的,这些方法就会在本地方法栈中执行。

程序计数器:

一个JVM进程中有多个线程在执行,而线程中的内容是否能够拥有执行权,是根据CPU调度来的。
假如线程A正在执行到某个地方,突然失去了CPU的执行权,切换到线程B了,然后当线程A再获
得CPU执行权的时候,怎么能继续执行呢?这就是需要在线程中维护一个变量,记录线程执行到
的位置。这个内存区域是唯一一个在虚拟机中没有规定任何OutOfMemoryError 情况的区域。
(1)程序计数器占用的内存空间很小,由于Java虚拟机的多线程是通过线程轮流切换,并分配处器执行时间的方式来实现的,在任意时刻,一个处理器只会执行一条线程中的指令。因此,为了线切换后能够恢复到正确的执行位置,每条线程需要有一个独立的程序计数器(线程私有)
(2)如果线程正在执行Java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址
(3)如果正在执行的是Native方法,则这个计数器为空。

接下来看这篇

 JVM-内存模型与垃圾回收

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值