前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >jvm入门

jvm入门

原创
作者头像
用户10832809
发布2025-02-23 22:37:03
发布2025-02-23 22:37:03
870
举报

01 jvm与java体系结构

程序运行四层结构

java一直在吹的任意地方运行

java字节码:java语言编译成的字节码,.class文件,jvm与java语言无必然联系,只与特定的二进制文件-class文件格式关联。class文件包含了jvm指令集(或称字节码)和符号表,其他辅助信息

虚拟机:虚拟的计算机,一款用来执行系列计算机指令,分为系统虚拟机和程序虚拟机,系统虚拟机如vmware,完全对物理计算机的仿真;程序虚拟机如jvm,专门为执行某个计算机程序而设计。

jvm

作用:二进制字节码的运行环境,负责装载字节码到其内部,解释/编译为对应平台上的机器指令执行,每一条java指令,java虚拟机规范中都有详细定义,如怎么取操作数,怎么处理操作数,处理结果放哪里

特点:一次编译,到处执行;自动内存管理;自动垃圾回收功能

jvm位置

jvm整体结构

hotspot vm,解释器与即时编译器并存的架构

组成:栈、本地方法栈、程序技术器、堆、方法区

java代码执行流程:

java源码(.java文件)通过java编译器生成字节码,编译器执行过程:词法分析、语法分析、语法/抽象语法树、语义分析、注解抽象语法树、字节码生成器

字节码加载到java虚拟机中;java虚拟机中过程:类加载器、字节码校验器、翻译字节码(解释执行)、JIT编译器(编译执行)

到操作系统

jvm架构模型

java编译器输入的指令流基本上一种基于栈的指令集架构,另外一种指令集架构则是基于寄存器的指令集架构

基于栈式架构的特点:1设计和实现简单,适用于资源受限的系统;2避开了寄存器分配难题,使用零地址指令方式分配;3指令流中的指令大部分是零地址指令,其执行过程依赖于操作栈。指令集更小,编译器容易实现;4不需要硬件支持,可移植性性好,更好实现跨平台

基于寄存器架构的特点:1典型的x86的二进制指令集,比如传统的pc以及android的davlik虚拟机;2指令集架构完全依赖于硬件,可移植性差;3性能优秀和执行更高效;4花费更少的指令区完成一项操作;5在大部分情况下ia,基于寄存器架构的指令集往往都以一地址指令,二地址指令和三地址指令为主,而基于栈架构的指令集以零地址为主

如2+3,栈的:有入栈和出栈操作;寄存器,直接在一个集群器上执行加法操作

java架构模型总结:由于跨平台的设计,java的指令都是基于栈来设计的、不同平台CPU架构不同,所以 不能设计为基于寄存器的。优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令。

jvm生命周期

虚拟机的启动:jva虚拟机的启动是通过引导类加载器(bootstrap class loader)创建的一个初始类(initial class)来完成的,这个类是由虚拟机的具体实现指令的

虚拟机的执行:一个运行的java虚拟机有着一个清晰的任务,执行java程序;程序开始执行时他才运行,程序结束后他就停止;执行一个所谓的java程序时,真正执行的是一个java虚拟机进程

虚拟机的退出:1程序正常执行结束;2程序在执行过程只遇到了异常或错误而异常终止;3由于操作系统出现错误而导致java虚拟机进程终止;4某线程调用Runtime类或System类的exit方法,或Rauntime的halt方法,并且java安全管理器也允许这次exit或halt操作;5除此之外,JNI(java native interface)规范描述了用JNI Inovocation API来加载或卸载java虚拟机时,java虚拟机的退出情况

02 类加载子系统

类加载器和类加载过程

类加载子系统作用

类加载器子系统负责从文件系统或网络中加载class文件,class文件在文件开头有特定的文件标识

classloader只负责class文件的加载,至于它是否可以运行,则由excution engine决定

加载的类信息存放于一块称为方法区的内存空间。除了类信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是class文件中常量池部分的内存映射)

类加载器classloader角色

class file存在于本地硬盘上,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载到jvm当中,根据这个文件实例化出n个一模一样的实例

class file加载到jvm中,被称为dna元数据模板,放在方法区

在.class文件->jvm->最终称为元数据模板,此过程就要一个运输工具,类加载器 class loader,扮演一个快递员的角色

类加载过程

加载:1通过一个类的全限定名获取定义此类的二进制字节流;2将这个字节流所代表的静态存储结构转为方法区的运行时数据结构;3在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口

链接

验证:1目的在于确保class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身的安全;2主要包括四种验证,文件格式验证、元数据验证、字节码验证、符号引用验证

准备:1为类变量分配内存并且设置该类变量的默认初始值,即零值;2这里不包含final修饰的static,因为final在编译的时候就会分配了,准备阶段会显示的初始化;3这里不会为实例变量分配初始化,类变量会分配到方法区中,而实例变量会随着对象一起分配到堆中

解析:1将常量池中的符号引用转为直接引用的过程(将常量池中的符号引用转为直接引用,符号引用指的是包名类名,直接引用类似为地址;

  • 如果是类,直接引用可能是方法区中类元数据的地址。
  • 如果是字段,直接引用可能是字段在对象内存布局中的偏移量。
  • 如果是方法,直接引用可能是方法代码在内存中的入口地址。

);2事实上,解析操作往往伴随着jvm在执行完初始化之后再执行;3符号引用就是一组符号用来描述所引用的目标。符号引用的字面量形式明确定义在java虚拟机规范的class文件格式中。直接引用就是直接指向目标的指针,相对偏移量或一个间接的定位到目标的句柄;4解析动作主要针对类或接口、字段、类方法、接口方法、方法类型。对应常量池中的constant_class_info, constant_fieldref_info, constant_methodref_info

初始化:1初始化阶段就是执行类构造器方法clinit()过程;2此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块语句合并而来;3构造器方法中指令按语句在源文件中出现的顺序执行;4clinit不同于类构造器,(构造器是虚拟机视角下的init);5若该类具有父类,jvm会保证子类的clinit执行前,父类的clinit已经执行完毕;6虚拟机必须保证一个类的clinit方法在多线程下被同步加锁

类加载器分类

jvm支持两种类型的类加载器,引导类加载器(bootstrap classloader)、和自定义类加载器(user-defined classloader)

概念上讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是java虚拟机规范却没有这么定义,而是将所有派生于抽象类classloader的类加载器都划分为自定义类加载器

无论类加载器的类型如何划分,在程序中我们最常见的类加载器始终只有三个,启动类加载器、扩展类加载器、应用类加载器

虚拟机自带的类加载器

启动类加载器bootstrap:1这个类加载器使用c/c++语言实现,嵌套在jvm内部;2它用来加载JAVA的核心库,如java_home/jre/lib/rt.jar,rt.resource.jar, sun.boot.class.path 路径下的内容,用于提供jvm自身需要的类;3不继承java.lang.classloader,没有父加载器;4加载扩展类和应用程序类加载器,并指定为他们的父类加载器;5处于安全考虑,bootstrap启动类加载器只加载包名为java、javax、sun等开头的类

扩展类加载器 extension classloader:1java语言编写,有sun.misc.launcher$extclassloader实现;2派生于classloader类;3父类加载器为启动类加载器;4从java.ext.dirs系统属性所指定的目录中加载类库,或从jdk的安装目录的jre/lib/ext字目录下加载类库。如果用户创建的jar放在此目录下,也会自动由扩展类加载器加载

应用程序类加载器 appclassloader: 1java语言编写,有sun.misc.launcher$appclassloader实现;2派生于classloader类;3父类加载器为扩展类加载器;4它负责加载环境变量classpath或系统属性 java.class.path指定路径下的类库;5该类加载是程序中默认的类加载器,一般来说,java应用的类都是由它完成加载的;6通过classloader#getsystemclassloader方法获取到该类加载器

用户自定义类加载器

原因:隔离加载类、修改类加载的方式、扩展加载源、防止源码泄露

实现步骤:1继承classloader抽象类;2自定义类的类加载逻辑编写在findClass方法中;3在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承urlclassloader类,避免编写findclass方法及其获取字节码流的方式,更简洁

classloader是一个抽象类,其后所有类加载器都继承自classloader,除启动类

获取classloader的途径:1获取当前类的,clazz.getClassLoader(); clazz是一个实例化的对象;2获取当前线程上下文的,Thread.currentThread.getContextCalassLoader();3获取系统的,ClassLoader.getSystemClassLoader(); 4获取调用者的,DriverManager.getCallerClassLoader()

双亲委派机制

定义:java虚拟机对class文件采用的是按需加载的方式,当需要使用该类时才会将它的class文件加载到内存中生成class对象。加载某个类的class文件时,采用双亲委派机制,即把请求交由父类管理,一种任务委派模式

工作原理:1如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;2如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;3如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成,子类加载器才会自己去加载。

双亲委派机制优势:避免类的重复加载;保护程序安全,放置核心API被随意篡改

沙箱安全机制:自定义String类,但在加载该类时,会率先使用引导类加载器加载,引导类加载过程会先加载自带的jdk文件,即rt.jar包下的java\lang\String.class,报错信息没有main方法,则说明加载的是系统的String类

其他

jvm中表示两个class对象是否为同一个类的两个必要条件:1类的完整类名必须一致,包括包名;2加载这个类的classloader(classloader实例对象)必须相同

jvm中,即时两个类对象来源于同一个class文件,只要加载他们的classloader实例不一样,这两个类对象也不想等

jvm必须知道一个类型是由启动类加载器加载的,还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,则jvm会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。当解析一个类型到另一个类型的引用时,jvm需要保证这两个类型的类加载器是一样的

java程序对类的使用分为:主动使用、被动使用

主动使用:1创建类的实例;2访问某个类或接口的静态变量,或对该静态变量赋值;3调用类的静态方法;4反射(如Class.forName("com.at.Test"));5初始化一个类的子类;6java虚拟机在启动时被标明为启动类的类;7java.lang.invoke.MethodHandle实例解析的结果;REF_getStatic, REF_putStatic, REF_invokeStatic句柄对应类没有初始化,则初始化

除了上述使用,其他使用类的方式都为对类的被动使用,不会导致类的初始化

参考资料

康师傅jvm

尚硅谷宋红康JVM全套教程(详解java虚拟机)_哔哩哔哩_bilibili

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 01 jvm与java体系结构
    • 程序运行四层结构
    • jvm位置
    • jvm整体结构
    • java代码执行流程:
    • jvm架构模型
    • jvm生命周期
  • 02 类加载子系统
    • 类加载器和类加载过程
    • 类加载过程
    • 类加载器分类
    • 双亲委派机制
    • 其他
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档