JVM虚拟机学习笔记

谈谈自己对jvm的认识:

一、主要围绕以下几个方面讲解:

  • 1.class文件的构成

  • 2.类加载机制

  • 3.运行时数据区

  • 4.垃圾回收机制

  • 5.jvm的内存模型JMM

  • 6.GC分析,及调优

二、如下图是jvm的大体结构图

 

三、Class文件结构:

主要介绍大体的存储内容,这样对后面的内容了解起来更容易:

java的开号称是与平台无关的,大概就是起源于此。jvm其实不仅仅运行java文件,只要文件能够编译成class文件,并符合class文件的要求,都能在jvm上运行。

Class文件其实就是以8位字节为基本单位的二进制流:

Class存储的数据结构主要是:无符号数和表;无符号数用来描述数字,索引引用,数量值;表是用来描述复合结构的数据

结构如下:其中u4代表4字节,u2代表2字节,以_info结尾的为表

Class文件的前4位字节代表的是魔数,它的作用是来判断Class文件是否能被虚拟机所接受。0XCAFEBABE

魔数后面的四个字节表示的版本号;

紧接着2个字节就是常量池的入口,代表常量池容量的计数

紧接着就是表:也是存放常量池中的内容;常量池中主要存放两大类常量:字面量和符号引用

接着2个字节是访问标志:用于识别一些类或接口层次的访问信息:包括:这个Class是类还是接口;是否为public,若是类的话是否被final修饰

接着下来的两个2字节的分别表示类索引和父类索引:类索引是用于确定类的全限定名,父类索引用于确定这个类的父类全限定名

接下来的两个字节就是接口计数:用于明确实现了几个接口

再下来2个字节就是记录接口的集合;

接下来的两个字节就是方法个数:用于明确实现了几个接口

再下来2个字节就是记录方法的集合;

接下来的两个字节就是属性个数:用于明确实现了几个接口

再下来2个字节就是记录属性的集合;

Class文件是虚拟机执行引擎的数据入口

四、类加载机制:

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析、初始化,最终形成可以被虚拟机直接使用的Java类型,这个就是虚拟机的类加载机制

类加载主要分为:加载、验证、准备、解析、初始化

加载:主要是根据全限定名来获取二进制字节流

           然后将这个字节流中的静态的存储结构存转化为方法区运行时的数据结构,

           在堆中产生java.lang.class对象,作为方法区入口

验证:主要验证这几个方面:文件格式、元数据、字节码、符号引用验证;

           根据上面介绍的Class文件的结构可知:文件格式的验证可能就是根据魔数来验证,版本号的验证,常量池的验证等

           元数据的验证主要是对类的引用,接口的验证(如该类是否有父类,父类是否继承了类,该类是否是抽象类,是否实现其中的方                  法)

           字节码:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的

           符号引用验证:确保解析动作能正确执行

验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

准备: 为类变量分配内存并设置类变量的初始值,这些内存都将在方法区进行分配。

需要说明的是,

1.这个时候的进行内存分配的仅包括类变量(即被static修饰的变量),而不包括实例变量,实例变量会在对象实例化时随着对象一起分配在java堆中。

2、这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。

假设一个类变量的定义为:public static int value = 3;

那么变量value在准备阶段过后的初始值为0,而不是3,因为这时候尚未开始执行任何Java方法,而把value赋值为3的putstatic指令是在程序编译后,存放于类构造器<clinit>()方法之中的,所以把value赋值为3的动作将在初始化阶段才会执行。

对于局部变量来说,在使用前必须显式地为其赋值,否则编译时不通过。

3.如果类字段的字段属性表中存在ConstantValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstValue属性所指定的值。

假设上面的类变量value被定义为: public static final int value = 3;

编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为3

解析:把类中的符号引用转换为直接引用

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。

直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

 

初始化:

为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:

①声明类变量是指定初始值

②使用静态代码块为类变量指定初始值

JVM初始化步骤

1、假如这个类还没有被加载和连接,则程序先加载并连接该类

2、假如该类的直接父类还没有被初始化,则先初始化其直接父类

3、假如类中有初始化语句,则系统依次执行这些初始化语句

类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:

– 创建类的实例,也就是new的方式

– 访问某个类或接口的静态变量,或者对该静态变量赋值

– 调用类的静态方法

– 反射(如Class.forName(“com.shengsiyuan.Test”))

– 初始化某个类的子类,则其父类也会被初始化

– Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类

结束生命周期

•在如下几种情况下,Java虚拟机将结束生命周期

– 执行了System.exit()方法

– 程序正常执行结束

– 程序在执行过程中遇到了异常或错误而异常终止

– 由于操作系统出现错误而导致Java虚拟机进程终止

五、类加载器

jvm中虚拟机的主要加载器:

启动类加载器:Bootstrap ClassLoader:主要负责<JAVA_HOME>/lib目录下的内容,该加载器是由c++编写的,所以java应用程序是无法访问的

扩展类加载器:主要负责<JAVA_HOME>/lib/ext目录下的内容

应用类加载器:ClassPath路径下的

类加载机制

全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入

父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类

缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效

六:运行时数据区

1.程序计数器:用于记录程序执行的下一条指令的地址,线程私有的

2.虚拟机栈:基本单位是栈针,线程私有的,主要包括:局部变量表、操作栈、动态链接、返回地址,方法的调用直至完成所对应的就是入栈和出栈的操作。

局部变量表中:主要的就是存放基本的数据类型,以及对象引用和返回地址,局部变量表在编译期间就已经确定其大小,而在运行期不会改变局部变量表的大小。局部变量表是用于存放方法参数和方法内部定义的局部变量的。局部变量表的基本单位是solt

如果该方法是非static方法,则从0位置存放this(个人感觉这就是为什么static方法中不能使用this的根本原因)

局部变量表:从0开始存储this、方法参数、局部变量。

操作数栈:方法的工作区,在操作数栈和局部变量之间交换数据,存储中间结果,操作数栈深度在编译时就能确定。

帧数据:方法返回值,异常分派,以及当前方法所在类运行时常量池的引用。

在Java 虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大

于虚拟机所允许的深度,将抛出StackOverflowError 异常;如果虚拟机栈可以动态扩展

(当前大部分的Java 虚拟机都可动态扩展,只不过Java 虚拟机规范中也允许固定长度的

虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError 异常

3.本地方法栈:同虚拟机栈

4.方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

5.堆:是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以Java堆中还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。

根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值