JVM学习笔记(一)

虚拟机

所谓虚拟机就是一台虚拟的计算机。他是一款软件,用来执行一系列虚拟计算机指令。
虚拟机可以分为系统虚拟机(WMWare)和程序虚拟机(java虚拟机)

java虚拟机

就是二进制字节码的运行环境

JVM的位置

【用户User【字节码文件【JVM【操作系统(windows/Linux)【硬件】】】】】

JVM的整体结构

java编译器输入的指令流基本上是一种给予栈的指令集架构,另一种指令集架构则基于寄存器的指令集架构
两者区别:
基于栈式架构的特点:
设计和实现更简单,适用于资源受限的系统;
避开了寄存器的分配难题,使用零地址指令方式分配;
指令流种的指令大部分是零地址指令,其执行过程依赖与操作栈。指令集更小,编译器容易实现;
不需要硬件支持,可移植性更好,实现跨平台
基于寄存器架构的特点:
典型的应用是x86的二进制指令集:传统的PC
指令集架构则完全依赖硬件,可移植性差
性能优秀和执行更高效
花费更少的指令去完成一项操作

JVM的生命周期

虚拟机的启动
虚拟机的执行(执行所谓java程序的时候,叫做java虚拟机的进程)
虚拟机的退出:
程序正常执行结束
程序在执行过程中遇到异常或错误而异常终止
由于操作系统出现错误导致java虚拟机进程终止
某线程调用Runtime类或System类的exit方法,或Runtime类的halt方法,并且Java安全管理器也允许这次exit或halt操作

类的加载过程

加载-->(验证-->准备-->解析)-->初始化
                            链接
加载:生成一个代表这个类的java.lang.Class对象
验证:目的确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性(cofe babe开头)
准备:这里不包含用final修饰的static,因为final在编译的时候就含分配了,准备阶段会显示初始化
           分配内存并且设置该类变量的默认初始值
解析:将常量池内的符号引用转换成直接引用
初始化:初始化阶段就是执行类构造器方法<clinit>的过程,此类方法不需要被定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中合并类

虚拟机自带的加载器

启动类加载器(引导类加载器,BootStrap):
这个类加载使用c/c++语言实现,嵌套在JVM并不继承自java.lang.ClassLoading,没有父类加载器,用来加载java的核心库,用于提供JVM自身需要的类

双亲委派机制

1)如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行
2)如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器
3)如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务名字加载器才会尝试自己去加载
优势:
避免类的重复加载
保护程序安全,防止核心API被随意篡改(自定义类:java.lang.String)
沙箱安全机制:可以保证对java核心源代码的保护。自定义String类但是加载自定义String类的时候会率先使用引导类加载器加载,而引导类加载器在加载过程中会先加载jdk自带的文件
在JVM中表示两个class对象是否为同一个类存在的两个必要条件
类的完整类名必须一致,包括包名
加载这个类的ClassLoader必须相同

java程序对类的使用方式分为:主动使用和被动使用

主动使用分为七种情况:
创建类的实例
访问某个类或接口的静态变量,或者对该静态变量赋值
调用类的静态方法
反射
初始化一个类的子类
JDK 7 开始提供的动态语言支持
除了以上七种情况,其他使用java类的方式都被看作是对类的被动使用,都 不会导致类的初始化

运行时数据区

(JDK8后方法区变成元数据区)

线程:

在Hotspot JVM里,每个线程都与操作系统的本地线程直接映射(当一个java线程准备好执行以后,此时一个操作系统的本地线程也同时创建,java线程执行终止后,本地线程也会回收)
操作系统负责所有线程的安排调度到任何一个可用的CPU上。一旦本地线程初始化成功,调用java线程中的run()方法
JVM系统线程:
虚拟机线程:
周期任务线程:这种线程是时间周期事件的体现(比如中断),他们一般用于周期性操作的调度执行
GC线程:这种线程对jVM里不同种类的垃圾收集行为提供了支持
编译线程:这种线程在运行时会将字节码编译成本地代码
信号调度线程:这种线程接收信号并发送给JVM,内部通过调用适当的方法进行处理

程序计数寄存器:

*用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令。
*每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一致。
*任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的java方法的JVM指令地址
*字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令
使用PC寄存器存储字节码指令地址有什么用呢?
为什么使用PC寄存器记录当前线程的执行地址呢?
因为CPU需要不停的切换各个线程,这个时候切换回来后,要知道接着从哪里开始继续执行
JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令
PC寄存器为什么会被设定为线程私有(各自都有)?
为了能都准确记录各个线程正在执行的当前字节码指令地址,每一个线程都分配一个PC寄存器,这样各个线程之间可以独立计算,不会出现相互干扰的情况。

虚拟机栈(不存在垃圾回收问题):

(栈是运行时的单位,而堆是存储的单位)
每个线程都会在创建时创建一个虚拟机栈,其内部保存一个个栈帧对应一次次的java方法调用
作用:主管java程序的运行,保存方法的局部变量,部分结果,并参与方法的调用和返回
java虚拟机规范允许java栈的大小是动态的或者是固定不变的
*如果线程请求分配的栈容量超过java虚拟机栈允许的最大容量,java虚拟机将会抛出一个StackOverflowError异常
*如果java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈会抛出一个OutOfMemoryError

栈的存储结构

栈中的数据都是以栈帧的格式存在
在这个线程上正在执行的每个方法都各自对应一个栈帧
栈帧是一个内存区块,是一个数据集。

栈的运行原理

*不同的线程中所包含的栈帧是不允许存在相互引用的,既不可能在一个栈帧中引用另外一个线程的栈帧
*java方法中有两种返回函数的方式,一种是正常的函数返回,使用return指令,另一种的抛出异常。不管使用哪种方式,都会导致栈帧被弹出

栈帧的内部结构:

*局部变量表:
  定义一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量(不存在数据安全问题)
  局部变量表的变量只在当前方法调用中有效,当方法调用结束后随着方法栈帧的销毁,局部变量表也会随之销毁
  局部变量表,最基本的存储单元是Slot(变量槽)32以内的类型只占一个slot(returnAddress类型)64位的类型(long和double占两个slot)可以重复使用
  局部变量表中的变量也是重复的垃圾回收根节点,只要被局部变量表中直接或简介引用的对象都不会被回收
变量的分类:
按照数据类型分:①基本数据类型 ②引用数据类型
按照在类中声明的位置为:①成员变量:在使用前都经历国默认初始化赋值
                                             类变量:linking的prepare阶段:给类变量默认赋值--->initial阶段:给类变量显式赋值即静态代码块赋值
                                             实例变量:随着对象的创建,会在堆空间中分配实例变量空间,并进行默认赋值
                                        ②在使用前,必须要进行显式赋值,否则,编译不通过

*操作数栈

  每个独立的栈帧中除了包含局部变量表以外,还含有一个后进先出的操作数栈,也可以称之为表达式栈
  操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据
  主要用于保存计算过程的中间结果,同时作为计算过程中的变量临时的存储空间
栈顶缓存技术,将栈顶元素全部缓存在物理CPU的寄存器(指令少,执行快)中,以此降低堆内存的读写次数,提升执行引擎的执行效率
*动态链接(指向运行时常量池的方法引用)
  每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用,包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接
  动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用

方法的调用

在JVM中,将符号引用转换为调用方法的直接引用与方法的绑定机制相关
静态链接:
当一个字节码文件被装载进JVM内部时,如果被调用的目标方法在编译期可知,且运行期保持不变时,这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接
动态链接:
如果被调用的方法在编译器无法被确定下来,也就是说只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性被称之为动态链接

方法的调用:虚方法和非虚方法

非虚方法
如果方法在编译期就确定了具体的调用版本,这个版本在运行时是不可变的。这样的方法称为非虚方法
静态方法、私有方法、final方法、实例构造器、父类方法都是非虚方法。
其他方法都是虚方法
动态类型语言和静态类型语言
静态类型语言是判断变量自身的类型信息:动态类型语言是判断变量值的类型信息,变量没有类型信息,变量值才有类型信息

*方法返回地址

存储调用该方法的PC寄存器的值
在方法退出后都返回到该方法被调用的位置。方法正常退出时,调用的pc计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址时要通过异常表来确定,栈帧一般不会保存这部分信息。
正常完成出口和异常完成出口的区别在于:通过异常完成出口退出的不会给他的上层调用者产生任何返回值。
*一些附加信息
举例栈溢出的情况?StackOverflowError
调整栈的大小,就能保证不出现溢出嘛?不能
分配的栈内存越大越好嘛?会挤压其他的内存空间
垃圾回收是否会涉及到虚拟机栈?不会
方法中定义的局部变量是否线程安全?具体问题具体分析
//m1线程安全
public static void method1(){
    //StringBuilder线程不安全
    StringBuilder s1 = new StringBuilder();
    s1.append("a");
    s1.append("b");
}
//m2线程不安全
public static void method2(StringBuilder stringBuilder){
    stringBuilder.append("a");
    stringBuilder.append("b");
}
//m3线程不安全
public static StringBuilder method3(){
    StringBuilder s1 = new StringBuilder();
    s1.append("a");
    s1.append("b");
    return s1;
}

本地方法接口
一个Native Method就是一个java调用非java代码的接口。一个Native Method该方法的实现由非java语言实现,比如C
本地方法栈
java虚拟机栈用于管理java方法的调用,而本地方法栈用于管理本地方法的调用
HotSpotJVM直接将本地方法栈和虚拟机栈合二为一
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值