深入JVM

Java内存区域与内存溢出异常

  1. 运行时数据区域
    Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。
    Java内存模型

  2. 程序计数器 线程私有
    程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器完成。

    如果线程正在执行的是一个Java方法,那么这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器的值应为空(Undefined)。此内存区域是唯一一个没有规定任何OutOfMemoryError情况的区域。没有内存溢出

  3. Java虚拟机栈 线程私有
    虚拟机栈描述的是Java方法执行的线程内存模型:每一个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。

    局部变量表存放了编译期可知的各种Java虚拟机基本数据类型、对象引用(reference类型,它只是一个指向对象启始地址的引用指针)和returnAddress类型(指向了一条字节码指令的地址)

    这个内存区域规定了两类异常状态:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果Java虚拟机栈容量是可以动态扩展的话,当无法申请到足够的内存就会抛OutOfMemoryError异常在VM中添加 -Xss1024k k m g来修改栈内存的大小

  4. 本地方法栈 线程私有
    本地方法栈(Native Method Stacks)于虚拟机栈所发挥的作用是非常相似的,区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。

    本地方法栈也会抛出StackOverflowError异常OutOfMemoryError异常

  5. Java堆 线程共享 存储中心
    Java堆(Java Heap)是虚拟机所管理的内存中最大的一块,并且是线程共享的区域在虚拟机启动时就创建。堆唯一的目的是存放对象实例,Java“几乎”所有的对象实例都是在这里被分配内存。所有的对象实例以及数组都应当在堆上分配,几乎是因为即时编译器的进步和逃逸分析技术的日渐强大,也可以在栈上分配。

    Java堆是垃圾收集器管理的内存区域,因此被称为“GC堆”。如果从分配内存的角度看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率。

    Java堆即可被实现成固定大小的,也可以是扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现 可以是-Xmx和-Xms设置最大和最小。当Java堆申请不到内存的时候就会抛出OutOfMemoryError

  6. 方法区 线程共享
    方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

    如果方法区无法满足新的内存分配需求时抛出OutOfMemoryError异常

  7. 运行时常量池 线程共享
    运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

    运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译器才能生产,运行期间也可以将新的常量放入池中。

    因为约束于方法区所以还是会在内存申请不到的时候抛OutOfMemoryError

  8. 对象的创建 限制于普通对象不包括数组和Class对象
    当Java虚拟机遇到一条字节码new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化。

    类加载检查通过后,就是给对象分配内存了,而且内存的大小是在类加载完成后就确定了的。在分配内存的时候会出现两个现象一个是堆的可用空间和已被使用空间是对半分的,中间放着一个指针作为分界点的指示器,那么分配内存就仅仅是把那个指针向内存方向挪动一段与对象大小相同的距离就可以,这个叫指针碰撞(Bump The Pointer)。第二种情况就是已经用内存和空闲内存是慌乱存放的那么虚拟机就必须维护一个列表,记录哪些内存可以哪些不可用,在分配的时候找到一块足够大小的内存分配给对象这个叫空虚列表(Free List)

    除了分配内存外,还有一个问题就是多线程问题,可能出现正在给对象A分配内存,指针还没来的及修改,对象B又同时使用了原来的指针分配内存的情况。解决方案一:对分配内存空间的动作进行同步处理。解决方案二:把内存分配的动作按照线程分在不同的空间进行,每个线程在Java堆中先预分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),哪个线程要分配内存,就在哪个本地缓冲区中分配,只有本地缓冲区用完了,分配新的缓存区时才需要同步锁定。虚拟机使用TLAB -XX: +/-UseTLAB

    内存分配完成之后,虚拟机必须将分配到的内存空间都初始化为零值,如果是TLAB的话,这项工作可以提前到TLAB分配时进行。

    Java虚拟机还要对对象进行必要的设置,要知道这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。

  9. 对象的内存布局
    在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)实例数据(instance Data)对齐填充(padding);

    HotSpot虚拟机对象的对象头部分包括两类信息。第一类是用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。另一部分是类型指针,即对象指向它的类型元数据的指针,Java虚拟机通过这个指针来确定该对象是哪个类的实例。如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通 Java对象的元数据信息确定Java对象的大小,但是如果数组的长度是不确定的,将无法通过元数据中的 信息推断出数组的大小。

    实例数据部分是对象真正存储的有效信息,即我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来 。

    对象的第三部分是对齐填充,这并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍。如果位数不够8的倍数就要补齐。

  10. 对象的访问定位
    Java程序会通过栈上的reference数据来操作堆上的具体对象。reference只是一个指向对象的引用,并没有定义这个引用应该通过什么方式去定位、访问到堆中对象的具体位置,所以对象访问方式也是由虚拟机实现而定的,主流的访问方式主要有使用句柄和直接指针两种:

    第一种:使用句柄访问,Java堆中将可能会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息
    在这里插入图片描述

    第二种:HotSpot是这个方式直接指针访问Java堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址,如果只是访问对象本身的话,就不需要多一次间接访问的开销
    在这里插入图片描述
    第一种的好处就是不需要移动reference,如果对象被移除了只要句柄改变即可,第二种就是速度会相对更快。

垃圾收集器与内存分配策略

  1. 概念
    程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基 本上是在类结构确定下来时就已知的因此这几个区域的内存分配和回收都具备确定性, 在这几个区域内就不需要过多考虑如何回收的问题,当方法结束或者线程结束时,内存自然就跟随着回收了。

    而堆和方法区(运行时常量池)中的数据是在程序运行期间动态生成的就有很多不确定性。只有处于运行期间,我们才能知道程序究竟会创建哪些对象,创建多少个对象,这部分内存的分配和回收是动态的。

  2. 引用计数算法 判断对象是否存活 标记
    在对象中添加一个引用计数器,每当有一个地方 引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可 能再被使用的。但是这个处理方法不能处理引用链的问题。

  3. 可达性分析算法 判断对象是否存活 标记
    基本思路就是通过 一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过 程所走过的路径称为引用链(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连, 或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。
    在这里插入图片描述
    被作为GC Roots的对象包括:

    • 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的 参数、局部变量、临时变量等。
    • 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
    • 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
    • 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
    • Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如 NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
    • 所有被同步锁(synchronized关键字)持有的对象。
    • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
  4. 在谈引用
    如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称该reference数据是代表某块内存、某个对象的引用。

    在JDK 1.2版之后,Java对引用的概念进行了扩充,将引用分为强引用(Strongly Reference)、软 引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)4种,这4种引用强 度依次逐渐减弱。

    1. 强引用(Strongly Reference):普通的引用赋值,无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
    2. 软引用(Soft Reference):描述一些还有用但不是必须的对象,当虚拟机内存不够的时候就可以将它们回收掉,如果回收后内存还是不够就会抛出OutOfMemeoryError异常。
    3. 弱引用(Weak Reference):来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够都会回收掉。
    4. 虚引用(Phantom Reference):这种引用不会获取到对象实例,存在仅仅是为了在回收的时候给系统一个通知。
  5. 回收方法区
    方法区的垃圾收集主要回收两部分内容:废弃的常量和不再使用的类型。回收废弃常量与回收 Java堆中的对象非常类似。

    判定一个类是否可以GC需要满足一下三个条件

    1. 该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。
    2. 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如 OSGi、JSP的重加载等,否则通常是很难达成的。
    3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
  6. 标记-清除算法 清除
    算法分为“标记”和“清除”两个阶段:首先标记出所有需要回 收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回 收所有未被标记的对象。

    这种算法的缺点主要分为

    1. 执行效率不稳定,如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低;
    2. 内存空间的碎片化问题,标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
      在这里插入图片描述
  7. 标记-复制算法 清除 HotSpot用在来年轻代上
    它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样实现简单,运行高效,不过其缺陷 也显而易见,这种复制回收算法的代价是将可用内存缩小为了原来的一半。
    在这里插入图片描述

    Appel式回收的具体做法是把新生代分为一块较大的Eden空间和两块较小的 Survivor空间,每次分配内存只使用Eden和其中一块Survivor。发生垃圾搜集时,将Eden和Survivor中仍 然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1,也即每次新生代中可用内存空间为整个新 生代容量的90%(Eden的80%加上一个Survivor的10%),只有一个Survivor空间,即10%的新生代是会 被“浪费”的。当然,98%的对象可被回收仅仅是“普通场景”下测得的数据,任何人都没有办法百分百 保证每次回收都只有不多于10%的对象存活,因此Appel式回收还有一个充当罕见情况的“逃生门”的安 全设计,当Survivor空间不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域(实 际上大多就是老年代)进行分配担保(Handle Promotion)。

  8. 标记-整理算法 老年代使用
    标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内 存
    在这里插入图片描述

如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活区域,移动存活对象并更新 所有引用这些对象的地方将会是一种极为负重的操作,而且这种对象移动操作必须全程暂停用户应用程序才能进行,这就更加让使用者不得不小心翼翼地权衡其弊端了,像这样的停顿被最初的虚拟机 设计者形象地描述为StopThe World

  1. 各类垃圾收集器
    在这里插入图片描述
    1. Serial垃圾收集器 HotSpot默认新生代垃圾收集器
      Serial收集器是最基础、历史最悠久的收集器,曾经(在JDK 1.3.1之前)是HotSpot虚拟机新生代收集器的唯一选择。这个收集器是一个单线程工作的收集器,但它的“单线程”的意义并不仅仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作,更重要的是强调在它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。
      在这里插入图片描述

    2. ParNew垃圾收集器
      ParNew收集器实质上是Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之 外,其余的行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio、-XX: PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一致,在实现上这两种收集器也共用了相当多的代码。
      在这里插入图片描述

    3. Serial Old垃圾收集器
      Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。这个收集器的主要意义也是供客户端模式下的HotSpot虚拟机使用。如果是服务器端模式下可以作为CMS收集器发送失败时的后备方案。
      在这里插入图片描述

    4. CMS收集器
      CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网网站或者基于浏览器的B/S系统的服务端上,这类应用通常都会较为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验。CMS收集器就非 常符合这类应用的需求。

      基于标记-清除算法实现,运行过程分为四步

      1. 初始标记(CMS initial mark)
      2. 并发标记(CMS concurrent mark)
      3. 重新标记(CMS remark)
      4. 并发清除(CMS concurrent sweep)

      其中初始标记、重新标记这两个步骤仍然需要Stop The World。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快;并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行;而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一 些,但也远比并发标记阶段的时间短;最后是并发清除阶段,清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。
      在这里插入图片描述
      但是这个种收集器还是有三个缺点:第一个是CMS收集器对处理器资源非常敏感,处理器核数太少会导致系统变慢。第二个是CMS收集器无法处理浮动垃圾,有可能出现Con-current Model Failure失败进而导致另一次完全stop the world的Full GC的产生。第三个是就CMS是基于标记-删除的算法实现的收集器,所以会有大量空间碎片产生。

    5. Garbage First收集器 G1
      Garbage First(简称G1)收集器是垃圾收集器技术发展历史上的里程碑式的成果,它开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。

      G1开创的基于Region的堆内存布局是它能够实现这个目标的关键。虽然G1也仍是遵循分代收集理论设计的,但其堆内存的布局与其他收集器有非常明显的差异:G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的 Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的 旧对象都能获取很好的收集效果。

      不去计算用户线程运行过程中的动作,G1收集器的运行过程分为四个步骤

      1. 初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际 并没有额外的停顿。
      2. 并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。
      3. 最终标记(Final Marking):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。
      4. 筛选回收(Live Data Counting and Evacuation):负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region 构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行 完成的。
        在这里插入图片描述
        配置收集器
        在这里插入图片描述

虚拟机性能监控、故障处理工具

  1. 基础故障处理工具
    根据人家可用性和授权不同,可以将工具分为三类

    1. 商业授权工具:主要是JMC(Java Mission Control)及它要使用到的JFR(Java Flight Recorder),JMC这个原本来自于JRockit的运维监控套件从JDK 7 Update 40开始就被集成到OracleJDK中。
    2. 正式支持工具:这一类工具属于被长期支持的工具,不同平台、不同版本的JDK之间,这类工具可能会有差异。
    3. 实验性工具:这一类工具在它们的使用说明中被声明为“没有技术支持,并且是实验性质的”(Unsupported and Experimental)产品,日后可能会转正,也可能会在某个JDK版本中无声无息地消失。但事实上它们通常都非常稳定而且功能强大,也能在处理应用程序性能问题、定位故障时发挥 很大的作用。
  2. jps:虚拟机进程状况工具
    JDK的很多小工具的名字都参考了UNIX命令的命名方式,jps(JVM Process Status Tool)是其中的典型。

    功能:可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class,main()函数所在的类)名称以及这些进程的本地虚拟机唯一ID(LVMID,Local Virtual Machine Identifier)。
    在这里插入图片描述

  3. jstat: 虚拟机统计信息监控工具
    jstat(JVM Statistics Monitoring Tool)是用于监视虚拟机各种运行状态信息的命令行工具。可以显示进程中类加载、内存、垃圾收集、即时编译等运行时数据。

    jstat命令格式:jstat [option vmid [interval[s | ms] [count]]]
    在这里插入图片描述
    jstat执行样例:这台服务器的新生代Eden区(E,表示Eden)使用了6.0%的空间,2个Survivor区(S0,S1)都是空的,老年代(O,表示Old)使用了0.0%。重新运行以来共发生0此MinorGC(YGC,Yong GC)总耗时0.000秒,发生Full GC(FGC,表示Full GC)3次,总耗时0.000秒。.
    在这里插入图片描述

  4. jstack: Java堆栈跟踪工具
    jstack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump或者 javacore文件)。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的目的通常是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间挂起等,都是导致线程长时间停顿的常见原因。
    在这里插入图片描述

  5. JConsole:Java监视与管理控制台 /bin/jconsole.exe
    JConsole(Java Monitoring and Management Console)是一款基于JMX(Java Manage-ment Extensions)的可视化监视、管理工具。它的主要功能是通过JMX的MBean(Managed Bean)对系统进行信息收集和参数动态调整。

    启用JConsole:通过JDK/bin目录下的jconsole.exe启动JCon-sole后,会自动搜索出本机运行的所有虚拟机进程,而不需要用户自己使用jps来查询

    “概述”页签里显示的是整个虚拟机主要运行数据的概览信息,包括“堆内存使用情况”“线 程”“类”“CPU使用情况”四项信息的曲线图,这些曲线图是后面“内存”“线程”“类”页签的信息汇总

    内存监控
    “内存”页签的作用相当于可视化的jstat命令,用于监视被收集器管理的虚拟机内存(被收集器直 接管理的Java堆和被间接管理的方法区)的变化趋势。
    在这里插入图片描述

虚拟机执行子系统

  1. Class类文件的结构
    Class文件是一组以8个字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数 据,没有空隙存在。当遇到需要占用8个字节以上空间的数据项时,则会按照高位在前的方式分割 成若干个8个字节进行存储。

    根据《Java虚拟机规范》的规定,Class文件格式采用一种类似于C语言结构体的伪结构来存储数 据,这种伪结构中只有两种数据类型:“无符号数”和“表”。
    无符号: 无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个 字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
    有符号: 表是由多个无符号数或者其他表作为数据项构成的复合数据类型,为了便于区分,所有表的命名都习惯性地以“_info”结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上也可以视作是一张表。

  2. 魔数与Class文件的版本
    每个Class文件的头4个字节被称为魔数(Magic Number),它的唯一作用是确定这个文件是否为 一个能被虚拟机接受的Class文件。Class的魔数值为0xCAFEBABE

  3. 常量池
    常量池可以比喻为Class文件里的资源仓库,它是Class 文件结构中与其他项目关联最多的数据,通常也是占用Class文件空间最大的数据项目之一,另外,它还是在Class文件中第一个出现的表类型数据项目。

    由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count)。并且这个容量计数是从1而不是0开始的,在Class文件格式规范制定之时,设计者将第0项常量空出来是有特殊考虑的,这样做的目的在于,如果后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,可以把索引值设置为0来表示。只有常量池的容量计数是从1开始

    常量池主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。
    符号引用
    1. 被模块导出或者开放的包(Package)
    2. 类和接口的全限定名(Full Qualified Name)
    3. 字段的名称和描述符(Descriptor)
    4. 方法的名称和描述符
    5. 方法句柄和方法类型(Method Handle、Method Type、Invoke Dynmic)
    6. 动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值