JVM(Java Virtual Machine,Java虚拟机)

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

↑ 以上来自百度百科

JVM是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。所以,JAVA虚拟机JVM是属于JRE的,而现在我们安装JDK时也附带安装了JRE(当然也可以单独安装JRE)。

JVM内存区域划分
  • 类装载器

每一个Java虚拟机都由一个类加载器子系统(class loader subsystem),负责加载程序中的类型(类和接口),并赋予唯一的名字。每一个Java虚拟机都有一个执行引擎(execution engine)负责执行被加载类中包含的指令。JVM的两种类装载器包括:启动类装载器和用户自定义类装载器,启动类装载器是JVM实现的一部分,用户自定义类装载器则是Java程序的一部分,必须是ClassLoader类的子类。

  • 执行引擎:它或者在执行字节码,或者执行本地方法

主要的执行技术有:解释,即时编译,自适应优化、芯片级直接执行其中解释属于第一代JVM,即时编译JIT属于第二代JVM,自适应优化(目前Sun的HotspotJVM采用这种技术)则吸取第一代JVM和第二代JVM的经验,采用两者结合的方式 。

自适应优化:开始对所有的代码都采取解释执行的方式,并监视代码执行情况,然后对那些经常调用的方法启动一个后台线程,将其编译为本地代码,并进行仔细优化。若方法不再频繁使用,则取消编译过的代码,仍对其进行解释执行。

  • 运行时数据区,主要包括:程序计数器、Java虚拟机栈、本地方法栈、堆、方法区、常量池

    • 程序计数器(Program Counter Register)

      • 程序计数器是一块比较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。
      • 每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。PC寄存器的内容总是指向下一条将被执行指令的地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。
      • 若线程执行Java方法,则PC记录的是正在执行的虚拟机字节码指令的地址;若线程执行native方法,则PC的值为undefined。
    • Java虚拟机栈(Java Virtual Machine Stack)
      • Java虚拟机栈是线程私有的,它的生命周期和线程相同。
      • 虚拟机只会直接对Javastack执行两种操作:以帧为单位的压栈或出栈。每个帧代表一个方法,Java方法有两种返回方式,return和抛出异常,两种方式都会导致该方法对应的帧出栈和释放内存。
      • 帧的组成:局部变量区(包括方法参数和局部变量,对于instance方法,还要首先保存this类型,其中方法参数按照声明顺序严格放置,局部变量可以任意放置),操作数栈,帧数据区(用来帮助支持常量池的解析,正常方法返回和异常处理)。
    • 本地方法栈(Native Method Stack)
      • 本地方法栈和Java虚拟机栈的作用非常相似,不过是Java虚拟机栈执行Java的方法,而本地房发展则为虚拟机使用的native方法提供服务。如某个JVM实现的本地方法使用C连接模型,则本地方法栈就是C栈,可以说某线程在调用本地方法时,就进入了一个不受JVM限制的领域,也就是JVM可以利用本地方法来动态扩展本身。
    • 堆(Heap)
      • 对于大多数应用来说,堆是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,它是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配,Heap中的对象的内存需要等待GC进行回收。
      • 堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的。
      • Java堆是垃圾收集器管理的主要区域,也因为被称为GC堆。所以Java堆还可以分为:新生代和老年代。再细致一点的有Eden空间、From Survivor和To Servivor空间等。
      • Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配。
    • 方法区域(Method Area)
      • 方法区和Java堆一样,是线程共享的区域。
      • 方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。
    • 运行时常量池
      • 运行时常量池是方法区的一部分,用于存放编译器期间生成的各种字面量和符号引用,这部分会在类加载后进入方法区的运行时常量池中存放。
类是如何创建的

  1. 虚拟机遇到一条new指令,首先会去检查这个指令的参数是否能在常量池中定位到一个类的符号应用,并检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有,则必须先执行类加载过程,类加载过程本篇文章不做讨论,后面会做详细讲解。

  2. 在类加载检查通过之后,接下来为新生对象分配内存。

    1. 指针碰撞:假设Java堆中内存是绝对规整的,所有用过的内存都在一边,没用过的内存都在另一边,中间存放着一个指针作为分界点的指示器,那么内存分配就是将指针向空闲空间那边挪动一段与对象大小相等的距离
    2. 空闲列表:如果Java堆中的内存不是规整的,那么虚拟机就需要维护一个列表,列表上记录哪块内存是可用的,在分配的时候就从列表中找到一块足够大的空间分配给对象实例,并更新列表上的记录。
    3. 选择哪种方式是Java堆是否规整决定的,而Java对是否规整又是由垃圾收集器是否带有压缩整理功能决定的。比如,使用Serial、PerNew等待压缩整理功能的收集器时采用的分配算法是指针碰撞,使用CMS这种基于标记-整理算法的收集器时,通常采用的是空闲列表。
    4. 虚拟机保证线程安全的方法:①CAS+失败重试的方法保证更新操作的原子性。②把内存分配的动作按照线程划分在不同的空间之中运行,即每个线程在Java堆中预先分配一块小内存,称为本地线程分配缓冲(Thread Local Allocation BufferM,TLAB)。哪个线程需要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。
  3. 内存分配完成之后,虚拟机需要将已分配的内存空间都初始化为零。

  4. 设置对象,虚拟机需要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄信息等。这些信息都是保存在对象头(Object Header)中。

  5. 至此,一个新的对象就产生了。但是在程序员的角度来说才是刚刚开始,对象的<init>方法还没有执行,所有的字段都是零,所以说执行new指令之后会接着执行<init>方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算是完整产生出来。

对象的内存布局

在虚拟机中,对象内存中存储的布局可以分为三块区域:对象头、实例数据和对其填充。

  • 对象头分为两部分,第一部分用于存储对象自身运行时数据,如hashcode、GC分代年龄、锁标志状态等,这部分数据的长度在32位和64位的虚拟机中分别为32bit和64bit,官方称为Mark World。另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定队形是哪个类的实例。
  • 实例数据部分是真正存储的有效区域,也是在程序代码中所定义的各种类型的字段内容。
  • 对其填充并不是必然存在的,也没有特别的含义,仅仅起着占位符的作用。当实例没有对齐时就需要对齐填充来补全。
对象的访问定位

由于引用对象在Java虚拟机中值规定了一个指向对象的引用,并没有定义这个引用应该通过何种方式去定位、访问堆中对象的具体位置。目前主流的方式有两种

  • 句柄访问,Java堆中将会划分出一块内存作为句柄池,引用中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据具体的地址信息。
  • 指针访问,那么Java堆中引用中直接存储的就是对象地址。

指针具有访问高效的优点,但是在对象访问非常频繁的时候开销也是比较大。而句柄访问的好处是引用中存储的是稳定句柄地址,在对象被移动时只要改变句柄中的实例数据指针,而引用本身不需要修改,或者也可以理解为在对象GC的时候,只需要改变句柄。

JVM垃圾回收

GC的基本原理:将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停。

  1. 对新生代的对象的收集称为Minor GC
  2. 对老年代的对象的收集称为Major GC
  3. 程序中主动调用System.gc()强制执行的GC为Full GC,清理整个堆空间—包括年轻代、老年代、元空间

不同的对象引用类型, GC会采用不同的方法进行回收,JVM对象的引用分为了四种类型:

  1. 强引用:默认情况下,对象采用的均为强引用(这个对象的实例没有其他对象引用,GC时才会被回收)
  2. 软引用:软引用是Java中提供的一种比较适合于缓存场景的应用(只有在内存不够用的情况下才会被GC)
  3. 弱引用:在GC时一定会被GC回收
  4. 虚引用:由于虚引用只是用来得知对象是否被GC
  • Young(年轻代)

年轻代分三个区。一个Eden区,两个Survivor区。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制到年老区。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。

  • Tenured(老年代)

老年代存放从年轻代存活的对象。一般来说老年代存放的都是生命期较长的对象。老年代的对象比较稳定,所以MajorGC不会频繁执行。在进行MajorGC前一般都先进行了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。

  • Perm(持久代)

指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被放入永久区域,它和和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。持久代大小通过-XX:MaxPermSize进行设置。

参考书籍

《深入理解Java虚拟机》

JVM学习笔记——什么是JVM?相关推荐

  1. 【JVM学习笔记1】JVM与Java体系结构

    JVM与Java体系结构 一.JVM的重要性 ​ 面试,大厂必备: ​ 中高级程序员的必备:项目管理.调优的需要: ​ 极客精神:垃圾回收算法.JIT.底层原理. Java相较于C/C++自带安全检查 ...

  2. 尚硅谷JVM学习笔记:1.JVM与Java体系结构

    JVM上篇:内存与垃圾回收篇(一) 笔记来源:尚硅谷宋红康JVM全套教程(详解java虚拟机)_哔哩哔哩_bilibili 1.JVM与Java体系结构 1.1前言 开发人员如何看待上层框架 一些有一 ...

  3. JVM学习笔记二:JVM参数

    所有线程共享的内存主要有两块:堆内存和方法区. 其中堆内存分为两块:新生代Young generation(Eden区.From Survivor区.To Survivor区).老年代Tenured ...

  4. JVM学习笔记(四)------内存调优

    首先需要注意的是在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提 ...

  5. JVM学习笔记(四)

    JVM学习笔记(四) 文章目录 JVM学习笔记(四) 笔记链接 1.GC算法 1.1GC-判断对象是否可回收 1.1.1 引用计数法 1.1.1 可达性分析 1.2GC-回收算法 标记清除法(Mark ...

  6. jvm学习笔记(三)

    jvm学习笔记(三) 文章目录 jvm学习笔记(三) 1.全部笔记链接 2.堆 2.1堆的划分 使用JVM参数查看划分 Hotspot堆内存划分图(JDK8之前) 2.2 GC对堆的回收 GC的种类 ...

  7. jvm学习笔记(二)

    jvm学习笔记(二) 文章目录 jvm学习笔记(二) 1.全部笔记链接 2. Native关键字 3.关于JVM规范 3.1 JVM规范中运行时数据区的概念 4.HotSpot的JVM运行时数据区 4 ...

  8. jvm学习笔记(一)

    jvm学习笔记(一) 文章目录 jvm学习笔记(一) 1.全部笔记链接 3.类加载器 作用 类别 加载步骤 获得类加载器 4.双亲委派机制 5.沙箱安全机制 沙箱概念 JAVA沙箱的基本组件 基本组件 ...

  9. JVM学习笔记-04-java历史-沙箱安全机制

    JVM学习笔记-04-java历史-沙箱安全机制 文章目录 JVM学习笔记-04-java历史-沙箱安全机制 视频链接-最新JVM教程IDEA版[Java面试速补篇]-04-java历史-沙箱安全机制 ...

最新文章

  1. git常用命令速查表【转】
  2. Python+selenium 自动化-启用带插件的chrome浏览器,调用浏览器带插件,浏览器加载配置信息。
  3. 基于nuxt和iview搭建OM后台管理系统实践(2)-quill富文本组件的封装
  4. 让mysql返回的结果按照传入的id的顺序排序
  5. 文件重定向函数freopen
  6. Python正则表达式笔记
  7. java反射 虚拟机优化_面试官问我:Java反射是什么?我回答竟然不上来......
  8. C++语言基础 —— STL —— 容器与迭代器 —— list 与 deque
  9. docker for mac的JSON配置文件中的hosts项修改后无法生效
  10. LSTM训练过程与参数解读
  11. C 库函数 - abs()
  12. leetcode python3 简单题122. Best Time to Buy and Sell Stock II
  13. [Lua]Lua入门教程
  14. zabbix获取mysql主从状态_zabbix监控MySQL主从状态
  15. VS建立Web网站 20141201
  16. 小程序源码:全新外卖侠cps5.6全套微信小程序源码下载-多玩法安装简单
  17. mac中插入带圆圈数字序号①②③
  18. 如何下载乌龟svn中文版
  19. 【期权机理与python实践】
  20. 3D模型欣赏:《magician》次时代 美女 精灵

热门文章

  1. DataInputStream、readUTF方法详解
  2. 网站ui设计教程_方便使用的20种UI设计Photoshop教程
  3. Error: Flash Download failed - “Cortex-M3“的解决方法
  4. 基于Java基础的家庭收支记账软件
  5. 安卓手机如何在线预览PDF文档
  6. MD5算法已经被破解
  7. 转:高绩效团队应该如何带?关注具体的体验,胜过抽象的文化
  8. 网络安全 | 云上安全的舞步,能否跟上云计算的节奏?
  9. Elixir Ranch: 传输层抽象
  10. java序列化,从底层到序列化所隐藏的问题以及解决方案