JVM总结--JVM体系结构

https://blog.csdn.net/samjustin1/article/details/52215274

需要不断的学习才可以. 
2016年08月15日 22:03:56 sam_justin 阅读数 1933
 
版权声明:本文为博主原创文章,未经博主允许不得转载。 

一. 虚拟机

虚拟机是模拟执行某种指令集体系结构(ISA)的软件,是对操作系统和硬件的一种抽象。

图1 计算机系统中的抽象
        计算机系统的这种抽象类似于面向对象编程(OOP)中的针对接口编程泛型(或者是依赖倒转原则),通过一层抽象提取底层实现中共性的部分,底层实现这个抽象并完成自己个性的部分。也就是说通过一个抽象层次来隔离底层的不同实现。虚拟机规范定义了这个虚拟机要完成的功能(也就是接口),底层的操作系统和硬件利用自己提供的功能来实现虚拟机需要完成的功能(实现)。通过运行在虚拟机之上,Java才具有很好跨平台特性。
图2 jvm实现,32bit+Win Jvm,32bit+Linux Jvm, 64bit+Win Jvm

二. Java虚拟机

        Java虚拟机(JVM)是由Java虚拟机规范定义的,其上运行的是字节码指令集。这种字节码指令集包含一个字节的操作码(opcode),零至多个操作数(oprand),虚拟机规范明确定义了每种字节码指令完成的功能是什么以及需要多少个操作数。Java虚拟机上运行的class文件,这个文件中包含字节码指令流以及类定义的信息,所以Java虚拟机规范还定义了class文件的格式(精确到每个字节)。所以实现Java虚拟机的两个要素是字节码指令集和class文件格式,Java虚拟机的实现者只要以正确方式读取class文件中的每一条字节码指令,并按照要求实现字节码指令的功能就可以实现JVM。
        目前常用的商用JVM主要有:Sun HotSpot,BEA JRocket以及IBM J9。其中由于BEA和Sun已经被Oracle收购,所以Oracle拥有当今世界上最流行的两个JVM,并有传言说Oracle将在Java8时将两个虚拟机合并,各取所需,取长补短,打造一个更加精湛的JVM。HotSpot会以解释+即时编译执行代码,HotSpot在解释执行字节码的时候,会探测热点(hotspot)代码,然后将这部分代码编译为本地代码,之后将直接运行本地代码,而不是解释,这样会有效提高虚拟机性能。JRocket主要是定位于服务器应用,所以不关注虚拟机的启动速度,它会将所有代码即时编译为本地代码执行,JRocket的垃圾收集器具有很高的收集效率。J9定位与HotSpot类似,专注于桌面应用和服务器应用,主要是针对IBM的各种Java产品。

三. Java语言与Java虚拟机

        我们知道Java源代码,即.java文件,通过javac编译为.class文件。.class文件可以运行在JVM上,JVM底层会通过字节码解释器或者即时编译器(JIT Compiler)执行.class文件中的字节码指令。JVM是运行在操作系统之上的,操作系统又通过指令集调用底层硬件服务执行其上的各种软件。
图3 Java语言在JVM上的执行
        从图3中,可以看到Java是运行在JVM之上的。但是Java语言和JVM没有必然的联系。Java语言并不是只能运行在JVM之上,只要实现了相应的编译器Java语言就可以运行在任何平台之上(比如J++),也可以被编译为本地代码直接运行在操作系统之上,比如,Linux上的GCJ(GNU Compiler for Java)就可以把Java语言编译为本地代码直接执行。同样的,JVM上也不是只能执行Java语言,只要实现了适当的编译器,将其他语言编译为JVM上的字节码,就可以在JVM上运行。比如,JRuby,Jython以及Groovy等其他JVM语言,都会通过相应的编译器或是解释器转化为.class,然后再JVM上运行。由于JVM并不关心.class文件是由Java、JRuby、Jython等转化而来,只要这个文件结构正确并能通过class文件校验。因此,由于.class文件屏蔽了Java、JRuby等上层语言的差异,所以Java、Groovy等可以相互调用。

四. Java虚拟机体系结构

        如图,概念上讲,JVM由类加载器子系统,运行时数据区,执行引擎以及本地方法接口组成。
图4 JVM体系结构
1. 类加载器子系统主要用于定位类定义的二进制信息,然后将这些信息解析并加载至虚拟机,转化为虚拟机内部的类型信息的数据结构。类加载器子系统还承担着安全性的责任,并且是JVM的动态链接和动态加载的基础。将二进制信息=>类型信息的数据结构,中间需要经过很多步骤。首先类加载器是JVM安全沙箱的第一道防线,能够防止非信任类破坏虚拟机。每一个被加载的class文件需要经过四次校验才能被加载。校验通过后,类加载器的命名空间和运行时包的特性能够防止非信任类伪装成信任类来破坏虚拟机。类加载器在方法区构造具有这个类的信息的数据结构后,会在堆上创建一个Class对象作为访问这个数据结构的接口。同时,类加载还需要初始化类的静态数据,也就是调用类的<clinit>方法。以上就是一个类的加载、链接及初始化的过程。
2. 运行时数据区是JVM运行时的内存空间的组织,逻辑上又划分为多个区,这些区的生命周期和它是否线程共享有关,它们分别是:
        堆:用于存放对象或数组实例,也就是运行期间new出来的对象。堆的生命周期与JVM相同,并且在线程之间共享访问。由于多线程并发访问,所以需要考虑线程安全的问题,有两种方法。第一种是,加锁进行互斥访问。第二种是线程本地分配缓冲(Thread Local Allocate Buffer, TLAB),在线程创建时预先给每个线程分配一块区域,这块区域是线程私有的,对其他线程是不可见,也就不会被共享。JVM规范规定在申请不到足够的内存时,堆会抛出OutOfMemoryException。
        方法区:存放类型信息和运行时常量池(Runtime Constant Pool)。每个被类加载器加载的类都会在方法区中形成一个与子对应的类型信息的数据结构,包括:这个类的类名、直接超类、实现的接口列表、字段列表、方法列表等。运行时常量池是class文件中的常量池列表(Constant Pool List)在运行时的一种体现,其中存储各种基本数据类型及String类型的常量以及其他类、方法、字段的符号引用。方法区的生命周期与JVM相同,被多个线程共享,所以要考虑并发访问的安全性的问题。JVM规范规定在需要的内存得不到满足的情况下,方法区会抛出OutOfMemoryException。
        PC(Program Counter):线程私有的,生命周期与线程相同,是对CPU中PC的一种模拟。如果线程正在执行的是Java方法,则该线程的PC中存放的下一条字节码指令的地址。在进行Java方法的调用和返回时,需要更新PC以保存当前方法(Current Method)正在执行的字节码指令的地址。PC是JVM规范中唯一没有规定会抛出异常的存储区。
        JVM栈:线程私有,生命周期与线程相同,是对传统语言(比如C)中的方法调用栈的一种模拟。JVM栈中存放栈帧(Frame)用于进行方法调用和返回、存储局部变量以及计算的中间结果。JVM规范规定栈可以抛出两种异常:(1)StackOverflowException,在栈的深度大于某个规定值的情况下抛出。(2)OutOfMemoryException,在为新栈帧分配内存或者是为线程分配栈的内存时,申请不到足够的内存的情况下抛出。
        JVM栈中存放的是栈帧,每个栈帧对应着一次方法调用。每一时刻,JVM线程只能执行一个方法(Current Method),该方法的栈帧是JVM栈的栈顶的元素(叫做当前栈帧,Current Frame),当调用一个方法时,会初始化一个栈帧压入JVM栈;当方法调用返回或者抛出异常没有被处理的情况下,JVM栈会弹出该方法对应的栈帧。每一个栈帧中存放局部变量表(Local Variable Table)、操作数栈(Oprand Stack)以及其他栈帧信息。栈帧的大小在编译时就确定了,编译器会把局部变量表和操作数栈的大小记录在class文件中method_info的属性表中。局部变量表类似于数组存放局部变量和方法参数。由于JVM采用的是基于栈的指令集体系结构,而不是基于寄存器,所以JVM上的所有计算都是在操作数栈上进行的(比如,算术运算、方法调用、内存访问等)。
        本地方法栈:用于支持本地方法调用,抛出的异常与JVM栈相同。
3. 执行引擎用于执行JVM字节码指令,主要由两种实现方式:(1)将输入的字节码指令在加载时或执行时翻译成另外一种虚拟机指令;(2)将输入的字节码指令在加载时或执行时翻译成宿主主机本地CPU的指令集。这两种方式对应着字节码的解释执行和即时编译。比如在HotSpot VM中执行引擎的实现是一种解释-编译的层次结构:
        (1)解释执行:解释执行字节码,并以方法为单位收集“热点(HotSpot)代码”的信息,将“热点代码”执行C0编译。
        (2)C0编译:将收集的“热点代码”编译成本地代码,并进行一些简单的优化。继续收集运行时信息,将一些频繁执行的本地代码进行C1编译。
        (3)C1编译:将C0阶段的本地代码,进行一些比较激进的优化。如果某些优化导致本地代码执行失败,此时JVM会退化到解释执行字节码阶段。
4. 自动内存管理用于管理运行时数据区的分配和释放。和C和C++相比,Java不需要程序员主动的管理内存(在new出对象后,不需要显示的delete),这样JVM就需要承担内存管理这个任务。内存管理的重点主要是在申请内存(new对象、类加载和初始化、启动线程时初始化栈等)得不到满足时,JVM可以自动回收那些不再存活的对象所占用的内存,也就是经常听到的垃圾收集。在回收过程中还要保证处理内存空间的碎片,以提高空间利用率。回收过程主要有两个关键点,标记存活对象和回收内存的算法。
       标记存活对象主要有引用计算和根搜索法两种。
       (1)引用计数,是一种很普遍的方法,在python、lua等一些脚本语言中都是使用这种算法。每个对象持有一个计数器,标记这个对象被引用的次数。进行垃圾收集时,那些引用计数为0的对象就是“死”对象,需要被收集。引用计数的一个缺点就是它没有办法处理循环引用的情况(A->B, B->A)。
       (2)根搜索,HotSpot虚拟机采用这种算法标记存活对象。把方法区、JVM栈中的所有的引用组成的集合作为搜索的根,从这个集合开始遍历直到结束。其中被遍历到的对象是存活对象;那些没有被遍历到的对象需要被垃圾收集。这样可以有效的避免循环引用的情况。
       回收内存的算法主要有:
       (1)复制算法,将内存分成两个部分,每一时刻只是用其中的一个。进行回收时,将所有存活的对象依次复制到另一个部分(依次复制避免了内存碎片的产生),接下来只用这一个部分。复制算法需要在两个内存区域来回复制,有一定的复制开销和空间开销(每一时刻只使用一个区域),但是可以很好的解决内存碎片的问题,适用于对象频繁创建并且生命周期短的情况。
       (2)标记清扫,先进行存活对象标记,回收时将“死”对象占用的内存直接释放掉,会产生大量的内存碎片。
       (3)标记整理,标记阶段与标记清扫算法一样,回收阶段释放“死”对象的内存后,还需要进行对象的移动使得所有对象依次在内存中排列,避免了内存碎片的产生。标记整理与复制算法相反,适用于对象创建不频繁,生命周期长得情况。
       (4)按代收集,将内存按照对象生命周期的不同划分为多个部分,每个部分采用不同的收集算法。目前,大部分商业虚拟机都是采用这种算法。比如,在HotSpot中,内存被划分为:新生代(New)、老年代(Old)和永久代(Perm)。新生代采用复制算法,老年代和永久代采用标记整理算法。内存分配、回收的策略是,对象首先在新生代分配,如果新生代内存不满足要求,则触发一次新生代内存的垃圾收集(Young GC,或者是Minor GC)。Young GC会导致部分新生代的对象被移动至老年代,一部分是因为新生代内存不足以放下所有的对象;另一部分是因为这些对象的年龄(每个对象都保存着这个对象被垃圾收集的次数,表示它的年龄。存储在对象头的age属性中)大到足以晋升到老年代。当新生代的对象进入老年代,而老年代的内存不满足要求时,则会触发一次整个新生代和老年代的垃圾收集(Full GC, 或者是Major GC)。
       在JVM中有多个后台线程用于完成自动内存管理,对于CPU来说这些后台线程和用户线程是一样的,都需要占用系统的资源。在GC线程进行垃圾收集时必须执行“Stop the World”这一操作,也就是暂停所有的用户线程。这就导致对于实时性要求比较高的系统,JVM的垃圾收集可能是一个短板。但是在JDK1.5,Sun提供了CMS(Concurrent Mark and Sweep)垃圾收集器,通过GC线程和用户线程并发执行减少GC时间,提高了JVM的实时性。在JVM的各种应用中,gc调优是一个关键的部分,主要目标是减少GC的次数并且降低每次GC的时间。关于这部分内容,后续的JVM内存管理会详细讨论。

五. JVM执行程序的流程

       在命令行执行"java Main"就会开启一个JVM实例,我们可以通过jps,jstat等JVM工具观察JVM的运行状态,下面以运行com.ntes.money.Main这个类为例来描述一下JVM执行一个程序的流程。
       当在命令行执行"java -Xmx=12m -Xms=12m -Dname=value com.ntes.money.Main"这个命令时,JVM的执行流程是,

(1)加载JVM,主要是加载动态链接库,windows下是jvm.dll,Linux下是libjvm.so;
       (2)设置JVM启动参数,比如命令中的-Xmx=12m -Xms=12m用于设置堆大小。
       (3)初始化JVM。
       (4)调用类加载器子系统,加载com.ntes.money.Main。这里给出的是自定义类,根据类加载器双亲委派链,最后是由系统默认类加载器(Classpath类加载器)进行加载。首先,根据全路径类型转化为文件路径com/ntes/money/Main.class,然后读取Main.class中的二进制信息、解析、加载,在方法区中形成Main类对应的数据结构。这里可能抛出ClassNotFoundException,有两种原因。一是文件路径com/ntes/money/Main.class不存在;二是com/ntes/money/Main.class文件路径存在,但是Main.class文件中存储的不是Main类的信息,比如是Main1,Main2等其他类的信息。这种情况下,会抛出NoClassDefFoundError,然后导致ClassNotFoundException。
       (5)在方法区com.ntes.money.Main类对应的数据结构中,根据方法描述符及访问标志,查找main方法。这里的描述符,包括了方法的方法名、参数、返回值,也就是public static void main(String[])。如果找不到对应的main方法,会抛出NoSuchMethodError: main异常。
       (6)通过本地方法(JNI)执行main方法。

六. 小结

       这里,主要是对JVM体系结构的一个概述,后续打算会讨论一下JVM的内存管理(垃圾收集部分),Java源码的编译以及字节码指令的执行与即时编译。由于我也是在不断的学习中,所以文章中可能有错误和理解不对的地方,请大家及时指出。

[转帖]JVM总结--JVM体系结构的更多相关文章

  1. jvm的内部体系结构浅析

    转自:http://www.cnblogs.com/evan2012/archive/2012/05/09/2489417.html 1.jvm的内部体系结构浅析 2.jvm的几个运行时数据区域 3. ...

  2. 【JVM之内存与垃圾回收篇】JVM与Java体系结构

    JVM与Java体系结构 前言 作为Java工程师的你曾被伤害过吗?你是否也遇到过这些问题? 运行着的线上系统突然卡死,系统无法访问,甚至直接OOMM! 想解决线上JVM GC问题,但却无从下手. 新 ...

  3. JVM1 JVM与Java体系结构

    目录 JVM与Java体系结构 虚拟机与Java虚拟机 虚拟机 Java虚拟机 JVM的位置 JVM的整体结构 Java代码执行流程 JVM的架构模型 基于栈的指令级架构 基于寄存器的指令级架构 两种 ...

  4. JVM解毒——JVM与Java体系结构

    你是否也遇到过这些问题? 运行线上系统突然卡死,系统无法访问,甚至直接OOM 想解决线上JVM GC问题,但却无从下手 新项目上线,对各种JVM参数设置一脸懵逼,直接默认,然后就JJ了 每次面试都要重 ...

  5. JVM上篇:JVM与Java体系结构

    JVM笔记 JVM传言 Java不是最强大的语言,但是JVM是最强大的虚拟机 虚拟机分类 系统虚拟机 类似VMware,就属于系统虚拟机,它提供了一个可运行完整操作系统的平台 程序虚拟机 Java虚拟 ...

  6. Java -JVM:JVM百科

    ylbtech-Java -JVM:JVM百科 JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机 ...

  7. 【死磕JVM】JVM快速入门之前戏篇

    简介 Java是一门可以跨平台的语言,但是Java本身是不可以实现跨平台的,需要JVM实现跨平台.javac编译好后的class文件,在Windows.Linux.Mac等系统上,只要该系统安装对应的 ...

  8. JVM初探 -JVM内存模型

    JVM初探 -JVM内存模型 标签 : JVM JVM是每个Java开发每天都会接触到的东西, 其相关知识也应该是每个人都要深入了解的. 但接触了很多人发现: 或了解片面或知识体系陈旧. 因此最近抽时 ...

  9. 【JVM】JVM随笔索引

    JVM目录 [JVM]Java内存模型 [JVM]类加载机制 [JVM]深度分析Java的ClassLoader机制(源码级别) [JVM]关于类加载器准备阶段的一道面试题目 [JVM]JVM垃圾收集 ...

随机推荐

  1. Flash上传超大文件解决方案

    文件夹数据库处理逻辑 public class DbFolder { JSONObject root; public DbFolder() { this.root = new JSONObject() ...

  2. 通过nginx转发,用外网连接阿里云的redis,报Unexpected end of stream的解决办法

    一.在与redis同一个内网的服务器上A的nginx做了下面的设置 stream { upstream redis { server  redis.rds.aliyuncs.com:6379 max_ ...

  3. MySQL_(Java)使用JDBC创建用户名和密码校验查询方法

    MySQL_(Java)使用JDBC向数据库发起查询请求 传送门 MySQL数据库中的数据,数据库名garysql,表名garytb,数据库中存在的用户表 通过JDBC对MySQL中的数据用户名和密码 ...

  4. Selenium定位策略

    1.通过XPath使用contains() 它将启动一个窗口,其中包含文本框开发中涉及的所有特定代码. 记下它的id属性. 通过XPath定位元素的语法 - 使用contains()可以写成: //& ...

  5. Java多线程核心知识(跳槽面试必备)

    多线程相对于其他 Java 知识点来讲,有一定的学习门槛,并且了解起来比较费劲.在平时工作中如若使用不当会出现数据错乱.执行效率低(还不如单线程去运行)或者死锁程序挂掉等等问题,所以掌握了解多线程至关 ...

  6. spring-jms,spring-boot-starter-activemq JmsTemplate 发送方式

    spring-jms,spring-boot-starter-activemq JmsTemplate 发送方式 背景: 原来我准备是setDefaultDestinationName 设置队列的名称 ...

  7. react 的基础知识

    react 是目前最流行的框架: 其中是采用 mvvm 的思想,让我们把所有的只关注视图层和逻辑层, 从而可以更好的书写代码: 在 react 中我们的 html 结构也是通过 js 来实现的,而且在 ...

  8. JS判定数据类型

    1.typeof                我们能够使用typeof判断变量的身份,判断字符串得到string,数字和NaN得到number,函数会得到function等,但是判断数组,对象和nu ...

  9. GitHub:Alibaba

    ylbtech-GitHub:Alibaba 1.返回顶部   2.返回顶部   3.返回顶部   4.返回顶部   5.返回顶部 1. https://github.com/alibaba 2.   ...

  10. Zabbix - 实现对磁盘动态监控

        回到目录 前言 zabbix一直是小规模互联网公司服务器性能监控首选,首先是免费,其次,有专门的公司和社区开发维护,使其稳定性和功能都在不断地增强和完善.zabbix拥有详细的UI界面和分组策 ...