JVM内存结构

内存是⾮常重要的系统资源,是硬盘和CPU的中间仓库及桥梁,承载这操作系统和应⽤程序的实时运⾏。JVM内存布局规定了java在运⾏过程中内存申请、分配、管理的策略,保证了JVM的⾼效稳定运⾏。不同的JVM对于内存的划分⽅式和管理机制存在着部分差异。结合JVM虚拟机规范,来探讨⼀下经典的JVM布局。
  HotSpot VM是⽬前市⾯上⾼性能虚拟机的代表之⼀。它采⽤解释器即时编译器并存的架构。在今天,java程序的运⾏性能早已脱胎换⻣,已经到了可以和C/C++程序⼀较⾼下的地步。在这里插入图片描述
 Class文件被类加载器加载后,类加载器会把class信息放到运行时数据区,再被执行引擎执行字节码文件,如果需要调用本地方法,会从本地接口中进行调用.
⽅法区是Java虚拟机规范中的定义,是⼀种规范,⽽永久代元空间是 HotSpot VM 不同版本的两种实
现。
 jdk1.7及之前,HotSpot虚拟机对于⽅法区的实现称之为“永久代”, Permanent Generation 。
 jdk1.8之后,HotSpot虚拟机对于⽅法区的实现称之为“元空间”, Meta Space 。
在这里插入图片描述
在这里插入图片描述
 java虚拟机定义了若⼲种程序运⾏期间会使⽤到的运⾏时数据区,其中有⼀些会随着虚拟机启动⽽创建,随着虚拟机退出⽽销毁,这些是线程共享的;另外⼀些则是与线程⼀⼀对应的,这些与线程对应的数据区域会随着线程开始和结束⽽创建和销毁,这些是属于线程独享的。
每个线程(线程私有、线程独享):程序计数器、虚拟机栈、本地⽅法栈
线程间共享(线程共有、线程共享):堆、堆外内存(⽅法区(永久代或元空间)、代码缓存)
代码缓存:JVM在运⾏时会将频繁调⽤⽅法的字节码编译为本地机器码。这部分代码所占⽤的内存空间成为CodeCache区域。Java进⾏JIT(即时编译)的时候,会将编译的本地代码放在codecache中。
在这里插入图片描述

PC寄存器

 JVM中的程序计数寄存器(Program Counter Register),Register的命名源于CPU寄存器,寄存器存储指令地址(或者称偏移地址),CPU只有把数据装载到寄存器才能运⾏,在这⾥,并⾮是⼴义上指的物理寄存器,或许翻译成PC计数器(或指令计数器)会更加贴切(也称为程序钩⼦),并且也不容易引起⼀些不必要的误会。JVM中的PC寄存器是对物理PC寄存器的⼀种抽象模拟。

作用:
   PC寄存器⽤来存储指向下⼀条指令的地址,也就是即将要执⾏的指令代码。由执⾏引擎读取下⼀条指令。
在这里插入图片描述
特点:

  • 它是⼀块很⼩的内存空间,⼏乎可以忽略不计。也是运⾏速度最快的存储区域。
  • 在JVM规范中,每个线程都有它的程序计数器,是线程私有的,它的⽣命周期与线程的⽣命周期保持⼀致。
  • 任何时间⼀个线程都有⼀个⽅法在执⾏,也就是所谓的当前⽅法。程序计数器会存储当前线程正在执⾏的java⽅法JVM指令地址;或者,如果是执⾏native⽅法,则是未指定值(undefined)
  • 它是程序控制流的指示器,分⽀、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
  • 字节码解释器⼯作时就是通过改变这个计数器的值来读取下⼀条需要执⾏的字节码指令。
  • 它是唯⼀⼀个在java虚拟机规范中没有规定任何OutOfMemoryError(OOM)情况的区域。

单核并发
在这里插入图片描述
 在单核机器上,“多进程”并不是真正的多个进程在同时执⾏,⽽是通过CPU时间分⽚,操作系统快速在进程间切换⽽模拟出来的多进程。我们通常把这种情况成为并发。单核的多个进程,不是“⼀并发⽣”的,是cpu⾼速切换让我们看起来像“⼀并发⽣”⽽已。
多核并行
在这里插入图片描述

 我们使⽤的计算机基本上都搭载了多核CPU,这时,我们能真正的实现多个进程并⾏执⾏,这种情况叫做并⾏(⼀并进⾏)。多核cpu让多进程的并发有可能变成了并⾏。所以我们说得并发,有可能是是并⾏,也可能是并发,这跟cpu的核⼼数有关系。在多核机器上,我们的多个线程可以并⾏执⾏在多个核上,进⼀步提升效率。
2 为什么使⽤PC寄存器记录当前线程的执⾏地址?
 因为CPU需要不停地切换线程,这时候切换回来以后,线程就得知道接着从哪开始继续执⾏。JVM的字节码解释器就需要通过改变PC寄存器的值来明确下⼀条应该执⾏什么样的字节码指令.
在这里插入图片描述

虚拟机栈

在这里插入图片描述
是运⾏时的单位,⽽是存储的单位;
即:栈解决程序的运⾏问题,即程序如何执⾏,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放、放在哪⼉.

  java虚拟机栈(java virtual Machine Stack),早期也叫java栈。每个线程在创建时都会创建⼀个虚拟机栈,其内部保存⼀个个的栈帧(Stack Frame),对应着⼀次次的java⽅法调⽤。它是线程私有的。
⽣命周期
 ⽣命周期和线程⼀致。
作⽤
 主管java程序的运⾏,它保存⽅法的局部变量(8种基本数据类型、对象的引⽤地址)、部分结果,并参与⽅法的返回和调⽤
特点

  • 栈是⼀种快速有效的分配存储⽅式,访问速度仅次于程序计数器。

  • JVM直接对java栈的操作只有两个:每个方法的执行,伴随着入栈,执行结束后的出栈。
    可能出现的异常
    Java虚拟机规范允许虚拟机栈的⼤⼩是动态的或者是固定不变的。

  • 如果采⽤固定⼤⼩的Java虚拟机栈,那每⼀个线程的Java虚拟机栈容量可以在线程创建的时候独⽴选定。如果线程请求分配的栈容量超过Java虚拟机栈允许的最⼤容量,Java虚拟机将会抛出⼀个StackOverflowError异常

  • 如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候⽆法申请到⾜够的内存,或者在创建新的线程时没有⾜够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出⼀个OutOfMemoryError异常。
    设置栈大小
      我们可以使⽤参数-Xss选项来设置线程的最⼤栈空间,栈的⼤⼩直接决定了函数调⽤的最⼤可达深度。 JDK5.0以后每个线程栈⼤⼩为1M,以前每个线程栈⼤⼩为256K。根据应⽤的线程所需内存⼤⼩进⾏调整。在相同物理内存下,减⼩这个值能⽣成更多的线程。但是操作系统对⼀ 个进程内的线程数还是有限制的,不能⽆限⽣成,经验值在3000~5000左右
    栈中存储什么?
     每个线程都有⾃⼰的栈,栈中的数据都是以栈帧(Stack Frame)的格式存在。在这个线程上,正在执⾏的每个⽅法都各⾃对应⼀个栈帧,栈帧是⼀个内存区块,是⼀个数据集,维系着⽅法执⾏过程中的各种数据信息.

JVM直接对java栈的操作只有两个,就是对栈帧的压栈和出栈,遵循先进后出/后进先出的原则。
在⼀条活动线程中,⼀个时间点上,只会有⼀个活动的栈帧。即只有当前正在执⾏的⽅法的栈帧(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧(Current Frame),与当前栈帧对应的⽅法就是当前⽅法(Current Frame)
执⾏引擎运⾏的所有字节码指令只针对当前栈帧进⾏操作 如果在该⽅法中调⽤了其他⽅法,对应的新的栈帧会被创建出来,放在栈的顶端,成为新的当前栈帧。 不同线程中所包含的栈帧是不允许相互引⽤的,即不可能在另⼀个栈帧中引⽤另外⼀个线程的栈帧。如果当前⽅法调⽤了其他⽅法,⽅法返回之际,当前栈帧会传回此⽅法的执⾏结果给前⼀个栈帧,接着,虚拟机会丢弃当前栈帧,使得前⼀个栈帧重新成为当前栈帧 Java⽅法有两种返回函数的⽅式,⼀种是正常的函数返回,使⽤return指令;另外⼀种是抛出异常。不管使⽤哪种⽅式,都会导致栈帧被弹出

栈帧的内部结构
每个栈帧中存储着:

  • 局部变量表(Local Variable Table)
  • 操作数栈(Operand Stack)(或表达式栈)
  • 动态连接(Dynamic Linking)(或指向运⾏时常量池的⽅法引⽤)
  • ⽅法返回地址 (Return Address) (或⽅法正常退出或⽅法异常退出的定义)
  • 附加信息

本地方法栈

 简单地讲,⼀个Native Method就是⼀个java调⽤⾮java代码的接⼝。⼀个Native Method是这样⼀个java的⽅法:该⽅法的实现由⾮java语⾔实现,⽐如C。这个特征并⾮java所特有,很多其它的编程语⾔都有这⼀机制,⽐如在C++中,你可以⽤extern "C"告知C++编译器去调⽤⼀个C的函数。
为什么要使用本地方法
 java使⽤起来⾮常⽅便,然⽽有些层次的任务⽤java实现起来不容易,或者我们对程序的效率很在意时,问题就来了。
与java环境外交互:
  有时java应⽤需要与java外⾯的环境交互。这是本地⽅法存在的主要原因,你可以想想java需要与⼀些底层系统如操作系统或某些硬件交换信息时的情况。本地⽅法正是这样⼀种交流机制:它为我们提供了⼀个⾮常简洁的接⼝,⽽且我们⽆需去了解java应⽤之外的繁琐的细节。
与操作系统交互:
  JVM⽀持着java语⾔本身和运⾏时库,它是java程序赖以⽣存的平台,它由⼀个解释器(解释字节码)和⼀些连接到本地代码的库组成。然⽽不管怎样,它毕竟不是⼀个完整的系统,它经常依赖于⼀些底层(underneath在下⾯的)系统的⽀持。这些底层系统常常是强⼤的操作系统。通过使⽤本地⽅法,我们得以⽤java实现了jre的与底层系统的交互,甚⾄JVM的⼀些部分就是⽤C写的。还有,如果我们要使⽤⼀些java语⾔本身没有提供封装的操作系统的特性时,我们也需要使⽤本地⽅法。

并不是所有的JVM都⽀持本地⽅法。因为java虚拟机规范并没有明确要求本地⽅法栈的使⽤语⾔、具体实现⽅式、数据结构等。如果JVM产品不打算⽀持native⽅法,也可以⽆需实现本地⽅法栈。
在Hotspot JVM中,直接将本地⽅法栈和虚拟机栈合⼆为⼀。

  • ⼀个JVM实例只存在⼀个堆内存,堆也是java内存管理的核⼼区域。

  • java堆区在JVM启动的时候即被创建,其空间⼤⼩也就确定了。是JVM管理的最⼤⼀块内存空间。堆的⼤⼩是可以调节的

  • <<Java虚拟机规范>>规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。

  • 所有的线程共享java堆。
    内部结构
     Java7 及之前堆内存逻辑上分为三部分: 新⽣代+⽼年代+永久代(Permanent Space)
     Java8 及之后堆内存逻辑上分为三部分:新⽣代+⽼年代+元空间(Meta Space)
    最大的区别是永久代空间是在JVM里,而元空间独立于JVM内存空间。

  • <<Java虚拟机规范>>中对java堆的描述是:所有的对象实例以及数组都应当在运⾏时分配在堆上.

  • 数组和对象可能永远不会存储在栈上,因为栈帧中保存引⽤,这个引⽤指向对象或者数组在堆中的位置。

  • 在⽅法移除后,堆中的对象不会⻢上被移除,仅仅在垃圾收集的时候才会被移除。

  • 堆,是GC(Garbage Collection,垃圾收集器)执⾏垃圾回收的重点区域。
    在这里插入图片描述

  • java堆区⽤于存储java对象实例,那么堆的⼤⼩在JVM启动时就已经设定好了,可以通过"-Xmx"和"-Xms"来进⾏设置。
    ‘’-Xmx"则⽤于表示堆区的最⼤内存, -Xmx2048m
    等价于-XX:MaxHeapSize=2048m,设置JVM最⼤堆内存为2048m
    "-Xms"则⽤于表示堆区的初始内存,-Xms512m
    等价于-XX:InitialHeapSize=512m,设置JVM最初堆内存为512m

  • ⼀旦堆区中的内存⼤⼩超过"-Xmx"所指定的最⼤内存时,将会抛出OutOfMemoryError异常。OOM

  • 通常会将 -Xms和 -Xmx两个参数配置相同的值,其⽬的是为了能够在java垃圾回收机制清理堆区后不需要重新分割计算堆区的⼤⼩,从⽽提供性能

  • 初始内存⼤⼩: 物理电脑内存⼤⼩ /64 ; 最⼤内存⼤⼩: 物理电脑内存⼤⼩ /4

/**
1. 设置堆空间⼤⼩的参数
-X 是jvm 的运⾏参数
-Xms ⽤来设置堆空间(年轻代+⽼年代)的初始内存⼤⼩
-Xmx ⽤来设置堆空间(年轻代+⽼年代)的最⼤内存⼤⼩
2. ⼿动设置 -Xms600m -Xmx600m
 开发中建议将初始化堆内存和最⼤的堆内存设置成相同的值
3. 查看设置的参数: ⽅式⼀: jps / jstat -gc 进程id
 ⽅式⼆: -XX:+PrintGCDetails
*/
public class HeapSpaceInitial {
 public static void main(String[] args) {
 long initMemory = Runtime.getRuntime().totalMemory()/1024/1024;
 long maxMemory = Runtime.getRuntime().maxMemory()/1024/1024;
 System.out.println("-Xms:"+initMemory+" M");
 System.out.println("-Xmx:"+maxMemory+" M");
 System.out.println("系统内存⼤⼩:"+initMemory*64.0/1024+" G");
 System.out.println("系统内存⼤⼩:"+maxMemory*4.0/1024+" G");
 }
}

在这里插入图片描述

  • S0C:第⼀个幸存区的⼤⼩

  • S1C:第⼆个幸存区的⼤⼩

  • S0U:第⼀个幸存区的使⽤⼤⼩

  • S1U:第⼆个幸存区的使⽤⼤⼩

  • EC:伊甸园区的⼤⼩

  • EU:伊甸园区的使⽤⼤⼩

  • OC:⽼年代⼤⼩

  • OU:⽼年代使⽤⼤⼩

  • MC:⽅法区⼤⼩

  • MU:⽅法区使⽤⼤⼩

  • CCSC:压缩类空间⼤⼩

  • CCSU:压缩类空间使⽤⼤⼩

  • YGC:年轻代垃圾回收次数

  • YGCT:年轻代垃圾回收消耗时间

  • FGC:⽼年代垃圾回收次数

  • FGCT:⽼年代垃圾回收消耗时间

  • GCT:垃圾回收消耗总时间
    新⽣代与⽼年代中相关参数的设置
    存储在jvm中的java对象可以划分为两类:
     ⼀类是⽣命周期较短的瞬时对象,这类对象的创建和销毁⾮常迅速;
     另外⼀类对象的⽣命周期却⾮常⻓,在某些极端的情况下还能够与jvm的⽣命周期保持⼀致。
    java堆区进⼀步细分的话,可以划分为年轻代(YoungGen)和b(OldGen)
    其中年轻代可以划分为Eden空间Survivor 0、Survivor 1 区。(或者From Survivor 、To Survivor 区 )
    在这里插入图片描述
    1) 配置新⽣代与⽼年代在堆结构的占⽐。
    默认 -XX:NewRatio=2 设置新⽣代与⽼年代在堆空间的⼤⼩,表示新⽣代占1,⽼年代占2,新⽣代占整个堆的1/3
    可修改-XX:NewRatio=4 表示 新⽣代占1,⽼年代占4,新⽣代占整个堆的1/5
    2) 在HotSpot中,Eden空间和另外两个Survivor空间所占的缺省所占的⽐例是8:1:1
    -XX:SurvivorRatio =8 新⽣代中Eden所占区域的⼤⼩,Eden 占8 ,Survivor 0 占1 ,Survivor 1 占1

    3) 绝⼤部分的java对象的销毁都在新⽣代进⾏了。IBM公司的专⻔研究表明,新⽣代中80%的对象都是"朝⽣夕死"的。
    4) 可以使⽤选项“-Xmn” 设置新⽣代最⼤内存⼤⼩。这个参数⼀般使⽤默认值就可以了
    -Xmn:⾄于这个参数则是对 -XX:newSize、-XX:MaxnewSize两个参数的同时配置,也就是说如果通过-Xmn来配置新⽣代的内存⼤⼩,那么-XX:newSize = -XX:MaxnewSize = -Xmn,虽然会很⽅便,但需要注意的是这个参数是在JDK1.4版本以后才使⽤的。

** 对象分配⼀般过程**
为新对象分配内存是⼀件⾮常严谨和复杂的任务,JVM的设计者们不仅需要考虑内存如何分配、在
哪⾥分配等问题,并且由于内存分配算法与内存回收算法密切相关,所以还需要考虑GC执⾏完内存回收
后是否会在内存空间中产⽣内存碎⽚。

  1. new 的对象先放在Eden区。此区有⼤⼩限制。

  2. 当Eden区空间填满时,程序⼜需要创建对象,JVM的垃圾回收器将对Eden区进⾏垃圾回收(Minor
    GC),将Eden区中的不再被其他对象所引⽤的对象进⾏销毁。再加载新的对象放到Eden区。

  3. 然后将Eden区中的剩余对象移动到Survivor 0区

  4. 如果再次触发垃圾回收,此时上次幸存下来的放到Survivor 0区的,如果没有回收,就会放到
    Survivor 1区。

  5. 如果再次经历垃圾回收,此时会重新放回Survivor 0区,接着再去Survivor 1区。

  6. 什么时候去⽼年代呢?可以设置次数。默认是15次。可以设置参数: -XX:MaxTenuringThreshold=
    进⾏设置。

  7. 在⽼年代,相当悠闲。当养⽼区内存不⾜是,再此触发GC:Major GC,进⾏⽼年代的内存清理。

  8. 若⽼年代执⾏了 Major GC 之后发现依然⽆法进⾏对象的保存,就会产⽣OOM异常。
    java.lang.OutOfMemoryError: Java Heap Space
    在这里插入图片描述
    总结:
    针对幸存者S0、S1区的总结:复制之后有交换,谁空谁是to。
    关于垃圾回收:频繁在新⽣代收集,很少在⽼年代收集,⼏乎不在永久代/元空间收集。
    在这里插入图片描述
    体会堆空间分代的思想
    为什么需要把java堆分代?不分代就不能正常⼯作了吗?

     经研究,不同对象的⽣命周期不同,70%-99%的对象是临时对象.
     新⽣代:有Eden、两块相同⼤⼩的Survivor(⼜称from/to,s0/s1)构成,to总为空
     ⽼年代:存放新⽣代中经历多次GC仍然存活的对象。
      其实不分代完全可以,分代的唯⼀理由就是优化GC性能。如果没有分代,那所有的对象都在⼀块,就如同把⼀个学校的⼈都关在⼀个教室。GC的时候要找到哪些对象没⽤,这样就会对堆的所有区域进⾏扫描。⽽很多对象都是朝⽣夕死的,如果分代的话,把新创建的对象放到某⼀地⽅,当GC的时候先把这块存储“朝⽣夕死”对象的区域进⾏回收,这样就会腾出很⼤的空间出来。
    内存分配策略(或对象提升(Promotion)规则)
     如果对象在Eden出⽣并经过第⼀次MinorGC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象年龄设为1.对象再Survivor区中每熬过⼀次MinorGC,年龄就增加1岁,当它的年龄增加到⼀定程度(默认为15岁,其实每个JVM、每个GC都有所不同)时,就会被晋升到⽼年代中。对于晋升⽼年代的年龄阀值,可以通过选项 -XX:MaxTenuringThreshold来设置
    针对不同年龄段的对象分配原则如下所示:
    1)优先分配到Eden
    2)⼤对象直接分配到⽼年代 尽量避免程序中出现过多的⼤对象
    3)⻓期存活的对象分配到⽼年代
    4)动态对象年龄判断
    如果Survivor区中相同年龄的所有对象⼤⼩的总和⼤于Survivor空间的⼀半,年龄⼤于或等于该年龄的对象可以直接进⼊⽼年代,⽆须等到MaxTenuringThreshold中要求的年龄

堆空间的参数设置
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
-XX:+PrintFlagsInitial:查看所有参数的默认初始值
-XX:+PrintFlagsFinal:查看最终值(初始值可能被修改掉)
-Xms:初始堆空间内存 (默认为物理内存的1/64)
-Xmx:最⼤堆空间内存(默认为物理内存的1/4)
-Xmn: 设置新⽣代的⼤⼩。(初始值及最⼤值)
-XX:NewRatio:配置新⽣代与⽼年代在堆结构的占⽐
-XX:SurvivorRatio:设置新⽣代中Eden和S0/S1空间的⽐例
-XX:MaxTenuringThreshold 设置新⽣代垃圾的最⼤年龄
-XX:+PrintGCDetails:输出详细的GC处理⽇志 打印GC简要信息: -XX:PrintGC -verbose:gc
-XX:HandlePromotionFailure: 是否设置空间担保

方法区

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
1) ⽅法区(Method Area) 与java堆⼀样,是各个线程共享的内存区域
2) ⽅法区在JVM启动的时候被创建,并且它的实际的物理内存空间中和java堆区⼀样都可以是不连续
的。
3) ⽅法区的⼤⼩决定了系统可以保存多少个类,如果系统定义了太多的类,导致⽅法区溢出,虚拟机同样会抛出内存溢出错误:java.lang.OutOfMemoryError: PermGen space 或者
java.lang.OutOfMemoryError:Metaspace

  • 加载⼤量的第三⽅的jar包;
  • Tomcat 部署的⼯程较多(30-50个);
  • ⼤量动态的⽣成反射类

4)关闭JVM就会释放这个区域的内存
在这里插入图片描述
 元空间的本质和永久代类似,都是对JVM规范中⽅法区的实现。不过元空间与永久代最⼤的区别在与:
元空间不在虚拟机设置的内存中,⽽是使⽤本地内存。
 永久代、元空间⼆者并不只是名字变了,内部结果也调整了。
根据《java虚拟机规范》的规定,如果⽅法区⽆法满⾜新的内存分配需求时,将抛出OOM异常。
** 设置⽅法区⼤⼩参数**
⽅法区⼤⼩不必是固定的,JVM可以根据应⽤的需要动态调整

  1. JDK7及以前:
    -XX:PermSize 来设置永久代初始分配空间。
    -XX:MaxPermSize来设定永久代最⼤可分配空间。
    当JVM加载的类信息容量超过了这个值,会报异常java.lang.OutOfMemoryError: PermGen space
  2. JDK8及以后:
    元数据⼤⼩可以使⽤参数:-XX:MetaspaceSize=N和-XX:MaxMetaspaceSize=N指定,替换上述原有的两个参数。
    与永久代不同,如果不指定⼤⼩,默认值情况下,虚拟机会耗尽所有的可⽤系统内存。如果元数据区发⽣溢出,虚拟机⼀样会抛出异常 java.lang.OutOfMemoryError: Metaspace

 《深⼊理解Java虚拟机》书中对⽅法区(Method Area)存储内容描述如下:
它⽤于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。
类型信息
对每个加载的类型(类class、接⼝interface、枚举enum、注解annotation),JVM必须在⽅法区中存储以下类型信息:
这个类的完整有效名称(全名=包名,类名)
这个类型直接⽗类的完整有效名(对于interface或是java.lang.Object,都没有⽗类)
这个类型的修饰符(public,abstract,final的某个⼦集)
这个类型直接接⼝的⼀个有序列表
域(Field)信息
JVM必须在⽅法区中保存类型的所有域的相关信息以及域的声明顺序。
域的相关信息包括:域名称、域类型、域修饰符(public,private,protected、static、final、
volatile,transient的某个⼦集
⽅法(Method)信息
JVM必须保存所有⽅法的以下信息,同域信息⼀样包括声明顺序:
⽅法名称
⽅法的返回类型(或void)
⽅法参数的数量和类型(按顺序)
⽅法的修饰符(public,private,protectd,static,final,synchronized,native,abstract的⼀个⼦集)
⽅法的字节码(bytecodes)、操作数栈、局部变量表及⼤⼩(abstract和native⽅法除外)
异常表(abstract和native⽅法除外)
每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引

类加载器

Java虚拟机对class⽂件采⽤的是按需加载的⽅式,也就是说当需要使⽤该类是才会将它的class⽂件加载到内存⽣成class对象。⽽且加载某个类的class⽂件时,java虚拟机采⽤的是双亲委派模式,即把请求交有⽗类处理,它是⼀种任务委派模式。
⼯作原理

  1. 如果⼀个类加载器收到了类加载请求,它并不会⾃⼰先去加载,⽽是把这个请求委托给⽗类的加
    载器去执⾏;
  2. 如果⽗类加载器还存在器⽗类加载器,则进⼀步向上委托,依次递归,请求最终将到达顶层的启
    动类加载器
  3. 如果⽗类加载器可以完成类加载任务,就成功返回,若⽗类加载器⽆法完成加载任务,⼦加载器
    才会尝试⾃⼰去加载,这就是双亲委派模式
    虚拟机⾃带的加载器
    启动类加载器(引导类加载器,Bootstrap ClassLoader)
  • 这个类加载使⽤C/C++语⾔实现的,嵌套在JVM内部。

  • 它⽤来加载java的核⼼库(%JAVA_HOME%/jre/lib/rt.jar、resources.jar或sun.boot.class.path路径下的内容),⽤于提供JVM⾃身需要的类

  • 并不继承⾃java.lang.ClassLoader,没有⽗加载器。

  • 加载扩展类和应⽤程序类加载器,并指定为他们的⽗类加载器。

  • 处于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类
    扩展类加载器(Extension ClassLoader)

  • java语⾔编写,有sun.misc.Launcher$ExtClassLoader实现。 派⽣于ClassLoader类

  • ⽗类加载器为启动类加载器

  • 从java.ext.dirs系统属性所指定的⽬录中加载类库,或从%JAVA_HOME%/jre/lib/ext⼦⽬录(扩展⽬录)下加载类库。如果⽤户创建的JAR放在此2⽬录下,也会⾃动有扩展类加载器加载。

    应⽤程序类加载器(系统类加载器,AppClassLoader)

  • java语⾔编写,由sun.misc.Launcher$AppClassLoader实现 派⽣于ClassLoader类

  • ⽗类加载器为扩展类加载器

  • 它负责环境变量classpath或系统属性 java.class.path 指定路径下的类库

  • 该类加载是程序中默认的类加载器,⼀般来说,java应⽤的类都是有它来完成加载

  • 通过ClassLoader#getSystemClassLoader()⽅法可以获得该类加载器。
    在这里插入图片描述

优势
避免类的重复加载
保证程序安全,防⽌核⼼API被随意篡改

为什么需要破坏双亲委派?
 因为在某些情况下⽗类加载器需要加载的class⽂件由于受到加载范围的限制,⽗类加载器⽆法加载到需要的⽂件,这个时候就需要委托⼦类加载器进⾏加载。
 ⽽按照双亲委派模式的话,是⼦类委托⽗类加载器去加载class⽂件。这个时候需要破坏双亲委派模式才能加载成功⽗类加载器需要的类。也就是说⽗类会委托⼦类去加载它需要的class⽂件。
 以Driver接⼝为例,由于Driver接⼝定义在jdk当中的,⽽其实现由各个数据库的服务商来提供,⽐如mysql的就写了MySQL Connector,这些实现类都是以jar包的形式放到classpath⽬录下。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张嘉書

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值