JVM个人小结

什么是JVM?

  JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
  引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

JVM的体系结构:

在这里插入图片描述


类加载器(ClassLoader)

java提供有3种类加载器
引导(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器

Bootstrap类加载器

  它用来加载 Java 的核心类,是用原生代码来实现的,负责加载系统类。$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作

Extension类加载器

Extension类加载器是由ExtClassLoader实现的,负责加载JRE的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类,由Java语言实现。

System类加载器

系统类加载器是由 Sun 的 AppClassLoader实现的,AppClassLoader负责加载应用类的。它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。由Java语言实现,父类加载器为ExtClassLoader。

类加载过程
  1. 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,会产生一个类对应Class对象
  2. 链接:将java类的二进制代码合并到JVM的运行状态之中的过程
    • 验证:确保加载的类信息符合JVM的规范,无安全问题
    • 准备:正式为类变量(static)分配内存并 设置类变量默认初始值 的阶段,这些内存都将在方法区中进行分配。
    • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
  3. 初始化
    • 执行类构造器()方法的过程。类构造器是由编译期自动收集类中的 所有类变量 的赋值动作和静态代码块中的语句合并产生的
    • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
    • 虚拟机会保证一个类的()方法在对线程环境中被正确加锁和同步

双亲委派机制(确保类的安全性)

  某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。这就是加载器的双亲委派机制
  对于任意一个类,都需要由加载它的类加载器和这个类本身来一同确立其在Java虚拟机中的唯一性
  为了系统类的安全,类似“ java.lang.Object”这种核心类,jvm需要保证他们生成的对象都会被认定为同一种类型。即通过代理模式,对于 Java 核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。为了避免用户自己创建java.lang.Object等系统类,类加载采用委托机制,这样可以保证父类加载器优先,父类加载器能找到的类,子加载器就没有机会加载。例如我们自己写一个System类,会发现根本没有机会得到加载。就是因为System类是Bootstrap加载器加载的,就算自己重写,也总是使用Java系统提供的System。

类加载器间的联系

在这里插入图片描述

注意:

  虽然上面一直说的是父类加载器,子类加载器,但其实这只是说明一下加载器之间上下层级的关系,而他们本身并不是继承关系,而是组合关系

组合也是实现类的复用的一种方式。组合是把旧类对象作为新类的成员变量组合起来,用以实现新类的功能,用户看到的是新类的方法,而不能看到被组合对象的方法。因此,通常要在新类里使用private修饰被组合的旧类对象。

那为什么类加载器用的是组合关系而不是继承关系呢?
  因为首先继承在编译时刻就已经定义了,因此无法在运行时刻改变从父类继承的实现。父类通常至少定义了部分子类的具体表示。因为继承对子类揭示了其父类的实现细节,所以继承常被认为“破坏了封装性” 。子类中的实现与它的父类有紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。当你需要复用子类时,实现上的依赖性就会产生一些问题
  而对象组合是通过获得对其他对象的引用而在运行时刻动态定义的。组合要求对象遵守彼此的接口约定,进而要求更仔细地定义接口,而这些接口并不妨碍你将一个对象和其他对象一起使用。因为对象只能通过接口访问,所以我们并不破坏封装性;只要类型一致,运行时刻还可以用一个对象来替代另一个对象;更进一步,因为对象的实现是基于接口写的,所以实现上存在较少的依赖关系
  对象组合对系统设计还有另一个作用,即优先使用对象组合有助于你保持每个类被封装,并被集中在单个任务上。


本地方法栈

  在java程序中,凡是带了native关键字的,说明java的作用范围达不到了,回去调用底层c语言的库,会进入本地方法栈,调用本地方法接口(JNI)。于是本地方法栈将登记native方法,在最终执行的时候,通过JNI加载本地方法库中的方法。

PC寄存器

  每个线程都有一个程序计数器(PC寄存器),是线程私有的,就是一个指针, 指向方法区中的方法字节码(用来存储指向像一条指令的地址, 也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。

方法区

  方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间
  静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关

java栈

  java栈主管程序的运行,生命周期和线程同步;主线程结束,栈内存就释放。java栈的内部保存一个个的栈帧(Stack Frame),对应着一次次的java方法调用。
每个栈帧存储着:
  局部变量表(Local Variables)
  操作数栈(Operand Stack)(或表达式栈)
  动态链接(Dynamic Linking)(或指向运行时常量池的方法引用)
  方法返回地址(return Address)(或方法正常退出或者异常退出的定义)
后三者也被称为帧数据区
注意:对于栈来说,不存在垃圾回收问题,一旦线程结束,栈就被回收。

  堆是java虚拟机所管理的内存中最大的一块。java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。所有的对象实例和数组都要在堆上分配。java堆是垃圾收集器管理的主要区域,因此很多时候也被称作GC堆

到这里,我们的JVM内部结构就介绍完了,那么一个完整的jvm运行过程是怎样的呢?


Java虚拟机运行过程

  一个类文件首先加载到方法区,一些符号引用被解析(静态解析)为直接引用或者等到运行时分派(动态绑定),经过一系列的加载过程(class文件的常量池被加载到方法区的运行时常量池,各种其它的静态存储结构被加载为方法区运行时数据解构等等)
  然后程序通过Class对象来访问方法区里的各种类型数据,当加载完之后,程序发现了main方法,也就是程序入口,那么程序就在里创建了一个栈帧,逐行读取方法里的代码所转换为的指令,而这些指令大多已经被解析为直接引用了,那么程序通过持有这些直接引用使用指令去方法区中寻找变量对应的字面量来进行方法操作。
  操作完成后方法返回给调用方,该栈帧出栈。内存空间被GC回收,堆里被new的那些也就被垃圾回收机制GC了。
  全流程包括以下几步:源码编写→编译(javac编译)→类文件被加载到虚拟机(类Class文件结构,虚拟机运行时内存分析,类加载机制)→虚拟机执行二进制字节码(虚拟机字节码执行系统)→垃圾回收(JVM垃圾回收机制)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值