【JVM】 jvm quick review知识 all-in-one

 1 总体结构

0

2 运行过程:

① Java 源文件—->编译器—->字节码文件

② 字节码文件—->JVM—->机器码

0

  • 用户线程:
  • 这里所说的线程指程序执行过程中的一个线程实体。JVM 允许一个应用并发执行多个线程。 Hotspot JVM 中的 Java 线程与原生操作系统线程有直接的映射关系。当线程本地存储、缓 冲区分配、同步对象、栈、程序计数器等准备好以后,就会创建一个操作系统原生线程。 Java 线程结束,原生线程随之被回收。操作系统负责调度所有线程,并把它们分配到任何可用的 CPU 上。当原生线程初始化完毕,就会调用 Java 线程的 run() 方法。当线程结束时,会释放原生线程和 Java 线程的所有资源。
  • 守护线程:

虚拟机线程 (VM thread)

这个线程等待 JVM 到达安全点操作出现。这些操作必须要在独立的线程里执行,因为当 堆修改无法进行时,线程都需要 JVM 位于安全点。这些操作的类型有:stop-the-world 垃圾回收、线程栈 dump、线程暂停、线程偏向锁(biased locking)解除。

周期性任务线程

这线程负责定时器事件(也就是中断),用来调度周期性操作的执行。

GC 线程

这些线程支持 JVM 中不同的垃圾回收活动。

编译器线程

这些线程在运行时将字节码动态编译成本地平台相关的机器码。

信号分发线程

这个线程接收发送到 JVM 的信号并调用适当的 JVM 方法处理。

3 内存区域

0

0

直接内存:面试细节,直接内存会导致oom吗?

  • 直接内存并不是 JVM 运行时数据区的一部分, 但也会被频繁的使用: 在 JDK 1.4 引入的 NIO 提 供了基于 Channel 与 Buffer 的 IO 方式, 它可以使用 Native 函数库直接分配堆外内存, 然后使用 DirectByteBuffer 对象作为这块内存的引用进行操作(详见: Java I/O 扩展), 这样就避免了在 Java 堆和 Native 堆中来回复制数据, 因此在一些场景中可以显著提高性能。

程序计数器(线程私有):

  • 一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的 程序计数器,这类内存也称为“线程私有”的内存。
  • 正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如 果还是 Native 方法,则为空。
  • 这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。

虚拟机栈(线程私有):

  • 是描述 java 方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame) 用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
  • 栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接 (Dynamic Linking)、 方法返回值和异常分派( Dispatch Exception)。栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异 常)都算作方法结束。

本地方法区(线程私有):

  • 本地方法区和 Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为 Native 方法服务, 如果一个 VM 实现使用 C-linkage 模型来支持 Native 调用, 那么该栈将会是一个 C 栈,但 HotSpot VM 直接就把本地方法栈和虚拟机栈合二为一。

堆(Heap-线程共享)-运行时数据区:

  • 是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行 垃圾收集的最重要的内存区域。由于现代 VM 采用分代收集算法, 因此 Java 堆从 GC 的角度还可以 细分为: 新生代33%(Eden 区80%、From Survivor 区10%和 To Survivor 区10%)和老年代66%。

方法区/永久代/元空间/(线程共享):

  • 即我们常说的永久代(Permanent Generation), 用于存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. HotSpot VM把GC分代收集扩展至方法区, 即使用Java 堆的永久代来实现方法区, 这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存, 而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型 的卸载, 因此收益一般很小)。
  • 运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版 本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。 Java 虚拟机对 Class 文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。

4 JVM 运行时内存

0

4.1 新生代

MinorGC 的过程(复制->清空->互换)

  • 1:eden、servicorFrom 复制到 ServicorTo,年龄+1
  • 2:清空 eden、servicorFrom
  • 3:ServicorTo 和 ServicorFrom 互换,最后,ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom 区。

4.2 老年代(面试细节:老年代什么时候回收?)

  • 主要存放应用程序中生命周期长的内存对象。
  • 老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行 了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足 够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间
  • MajorGC 采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没 有标记的对象。MajorGC 的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减少内存损耗,我们一般需要进行移动压缩(合并)或者标记出来方便下次直接分配。当老年代也满了装不下的 时候,就会抛出 OOM(Out of Memory)异常。

4.3 永久代

  • 指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被 放入永久区域,它和和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。所以这 也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常
  • 在 Java8 中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间 的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中而是使用 本地内存。 因此, 默认情况下, 元空间的大小仅受本地内存限制。 类的元数据放入 native memory, 字符串池和类的静态变量放入 java 堆中, 这样可以加载多少类的元数据就不再由 MaxPermSize 控制, 而由系统的实际可用空间来控制。

5 垃圾收集算法

0

可达性分析:不可达对象不等价于可回收对象, 不可达对象变为可回收对象至少要经过两次标记 过程。两次标记后仍然是可回收对象,则将面临回收。

  • 标记清除算法(Mark-Sweep):分为两个阶段,标注和清除,该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到可 利用空间的问题。
  • 复制算法(copying):为了解决 Mark-Sweep 算法内存碎片化的缺陷而被提出的算法(类似的思路比如:g1的多region模型)。按内存容量将内存划分为等大小 的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉。最大的问题是可用内存被压缩到了原 本的一半。且存活对象增多的话,Copying 算法的效率会大大降低。
  • 标记整理算法(Mark-Compact):标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。

6 分代收集理论(不是算法,类比还有分区收集算法):

新生代与复制算法-->一块较大的 Eden 空间和两个较小的 Survivor 空间(From Space, To Space)

老年代与标记复制算法-->每次只回收少量对象,因而采用 Mark-Compact 算法.

6.1 垃圾回收过程:(面试细节新生代什么时候回收?老年代什么时候回收?)

  • 1. JAVA 虚拟机提到过的处于方法区的永生代(Permanet Generation),它用来存储 class 类, 常量,方法描述等。对永生代的回收主要包括废弃常量和无用的类。

  • 2. 对象的内存分配主要在新生代的 Eden Space 和 Survivor Space 的 From Space(Survivor 目 前存放对象的那一块),少数情况会直接分配到老生代。
  • 3. 当新生代的 Eden Space 和 From Space 空间不足时就会发生一次 GC,进行 GC 后,Eden Space 和 From Space 区的存活对象会被挪到 To Space,然后将 Eden Space 和 From Space 进行清理。
  • 4. 如果 To Space 无法足够存储某个对象,则将这个对象存储到老生代。
  • 5. 在进行 GC 后,使用的便是 Eden Space 和 To Space 了,如此反复循环。
  • 6. 当对象在 Survivor 区躲过一次 GC 后,其年龄就会+1。默认情况下年龄到达 15 的对象会被 移到老生代中。

7 JAVA 四中引用类型

  • 强引用:当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收。强引用是造成 Java 内存泄漏的主要原因之 一。
  • 软引用:对于只有软引用的对象来说,当系统内存足够时它 不会被回收,当系统内存空间不足时它会被回收。
  • 弱引用:垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存。
  • 虚引用:虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态。

8 GC 分代收集算法 VS 分区收集算法(面试细节:G1还是基于分代理论吗?什么是分区理论?分区好处是啥?)

  • 分代收集算法:"分代收集"(Generational Collection)算法, 这种算法会根据 对象存活周期的不同将内存划分为几块, 如 JVM 中的 新生代、老年代、永久代,这样就可以根据 各年代特点分别采用最适当的 GC 算法.
  • 比如:在新生代-复制算法;在老年代-标记整理算法(因为对象存活率高、没有额外空间对它进行分配担保, 就必须采用“标记—清理”或“标 记—整理”算法来进行回收, 不必进行内存复制, 且直接腾出空闲内存.)
  • 分区收集算法:分区算法则将整个堆空间划分为连续的不同小区间, 每个小区间独立使用, 独立回收. 这样做的 好处是可以控制一次回收多少个小区间 , 根据目标停顿时间, 每次合理地回收若干个小区间(而不是 整个堆), 从而减少一次 GC 所产生的停顿。比如G1垃圾收集器;

9 GC 垃圾收集器

9.1 JDK1.6 中 Sun HotSpot 虚拟机的垃圾收集器如下:// 面试细节 CMS 一般搭配那个新生代收集器?ParNew + CMS/Serial

0

  • Serial 垃圾收集器(单线程、复制算法(client)):Serial 是一个单线程的收集器,进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。 java 虚拟机运行在 Client 模式下默认的新生代垃圾收集器。
  • ParNew 垃圾收集器(Serial+多线程(server)):实是 Serial 收集器的多线程版本,也使用复制算法,除了使用多线程进行垃 圾收集之外,其余的行为和 Serial 收集器完全一样。ParNew 垃圾收集器是很多 java 虚拟机运行在 Server 模式下新生代的默认垃圾收集器。
  • Parallel Scavenge 收集器:Parallel Scavenge 收集器也是一个新生代垃圾收集器,同样使用复制算法,也是一个多线程的垃 圾收集器,它重点关注的是程序达到一个可控制的吞吐量(即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间))自适应调节策略也是 ParallelScavenge 收集器与 ParNew 收集器的一个重要区别。
  • Serial Old 收集器(单线程标记整理算法 ):Serial Old 是 Serial 垃圾收集器年老代版本,它同样是个单线程的收集器,使用标记-整理算法, 这个收集器也主要是运行在 Client 默认的 java 虚拟机默认的年老代垃圾收集器。
  • Parallel Old 收集器(多线程标记整理算法):Parallel Old 收集器是 Parallel Scavenge 的年老代版本,使用多线程的标记-整理算法,在 JDK1.6开始提供。在 JDK1.6 之前,新生代使用 ParallelScavenge 收集器只能搭配年老代的 Serial Old 收集器,只 能保证新生代的吞吐量优先,无法保证整体的吞吐量,Parallel Old 正是为了在年老代同样提供吞吐量优先的垃圾收集器,
  • CMS 收集器(多线程标记清除算法):Concurrent mark sweep(CMS)收集器是一种年老代垃圾收集器,其最主要目标是获取最短垃圾回收停顿时间。和其他年老代使用标记-整理算法不同,它使用多线程的标记-清除算法。 最短的垃圾收集停顿时间可以为交互比较高的程序提高用户体验。有以下四个阶段:

阶段1 :初始标记->只是标记一下 GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。// 面试细节:初始标记为啥比重新标记快?

阶段2:并发标记-> 进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程

阶段3:重新标记->为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记 记录仍然需要暂停所有的工作线程// 面试细节:初始标记为啥比重新标记快?

阶段4:并发清除->清除 GC Roots 不可达对象,和用户线程一起工作,不需要暂停工作线程。由于耗时最长的并 发标记和并发清除过程中, 垃圾收集线程可以和用户现在一起并发工作, 所以总体上来看 CMS 收集器的内存回收和用户线程是一起并发地执行。

0

  • G1 收集器(分区收集理论): Garbage first 垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与 CMS 收集器,G1 收 集器两个最突出的改进是:// 面试细节 G1优点?

1. 基于标记-整理算法,不产生内存碎片。// 优点1 比cms好

2. 可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。// 优点2 精确控制停顿,低延迟垃圾回收 ,面试细节如何实现的精确停顿呢?

G1 收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域 的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间, 优先回收垃圾最多的区域。区域划分和优先级区域回收机制,确保 G1 收集器可以在有限时间获得最高的垃圾收集效率。

9.2 组合:

  • 新生代 Serial 与年老代 Serial Old 搭配垃圾收集过程图:(单-单)
  • 0

  • Scavenge/ParNew 与年老代 Serial Old 搭配垃圾收集过程图:(多-单)
  • 0

  • 新生代 Parallel Scavenge 和年老代 Parallel Old 收集器搭配运行过程图:(多-多)

0

10 JAVA IO/NIO

0

  • 阻塞 IO 模型:即在读写数据过程中会发生阻塞现象。当用户线程发出 IO 请求之后,内 核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出 CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除 block 状态。典型的阻塞 IO 模型的例子为:data = socket.read();如果数据没有就绪,就会一直阻塞在 read 方法。// 同步;
  • 非阻塞 IO 模型:事实上,在非阻塞 IO 模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞 IO 不会交出 CPU,而会一直占用 CPU。非阻塞 IO 就有一个非常严重的问题,在 while 循环中需要不断地去询问内核数据是否就 绪,这样会导致 CPU 占用率非常高// 其实还是同步;
  • 多路复用 IO 模型:多路复用 IO 模型是目前使用得比较多的模型,使用一个线程就可以管理多个 socket,系统不需要建立新的进程或者线程。会有一个线程不断去轮询多个 socket 的状态,只有当 socket 真正有读写事件时,才真正调用实际的 IO 读写操作。多路复用 IO 模式,通过一个线程就可以管理多个 socket,只有当 socket 真正有读写事件发生才会占用资源来进行实际的读写操作。因此,多路复用 IO 比较适合连 接数比较多的情况。在多路复用 IO 中,轮询每个 socket 状态是内核在进行的,这个效 率要比用户线程要高的多。多路复用 IO 模型是通过轮询的方式来检测是否有事件到达,并且对到达的事件 逐一进行响应。因此对于多路复用 IO 模型来说,一旦事件响应体很大,那么就会导致后续的事件 迟迟得不到处理,并且会影响新的事件轮询。// 其实还是同步;
  • 信号驱动 IO 模型:在信号驱动 IO 模型中,当用户线程发起一个 IO 请求操作,会给对应的 socket 注册一个信号函 数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到 信号之后,便在信号函数中调用 IO 读写操作来进行实际的 IO 请求操作。// 其实还是同步;
  • 异步 IO 模型(网络IO面试同步和异步的区别):异步 IO 模型才是最理想的 IO 模型。内核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程 发送一个信号,告诉它 read 操作完成了。用户线程完全不需要实际的整个 IO 操作是如何 进行的只需要先发起一个请求,当接收内核返回的成功信号时表示 IO 操作已经完成,可以直接 去使用数据了。也就说在异步 IO 模型中,IO 操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用 IO 函数进行具体的 读写。这点是和信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据 已经就绪,然后需要用户线程调用 IO 函数进行实际的读写操作;而在异步 IO 模型中,收到信号 表示 IO 操作已经完成,不需要再在用户线程中调用 IO 函数进行实际的读写操作。// 真正的异步

10.2 JAVA NIO

  • NIO 主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector。传统 IO 基于字节流和字 符流进行操作,而 NIO 基于 Channel 和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区 中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开, 数据到达)。因此,单个线程可以监听多个数据通道。
  • NIO 和传统 IO 之间第一个最大的区别是,IO 是面向流的,NIO 是面向缓冲区的。

10.2.2 NIO 的缓冲区

  • Java IO 面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何 地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。NIO 的缓冲导向方法不同。数据读取到一个它稍后处理的缓冲区,需要时可在 缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所 有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

10.2.3 NIO 的非阻塞

  • IO 的各种流是阻塞的。这意味着,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有 一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 NIO 的非阻塞模式, 使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可 用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以 继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞 IO 的空闲时间用于在其它通道上 执行 IO 操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

0

10.2.4 Channel

  • Channel 和 IO 中的 Stream(流)是差不多一个 等级的。只不过 Stream 是单向的,譬如:InputStream, OutputStream,而 Channel 是双向 的,既可以用来进行读操作,又可以用来进行写操作。

10.2.5 Buffer

  • 缓冲区,实际上是一个容器,是一个连续数组.
  • Channel 提供从文件、 网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer。常用的buffer ByteBuffer

10.2.6 Selector

  • Selector 类是 NIO 的核心类,Selector 能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的响应处理。这样一来,只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护 多个线程,并且避免了多线程之间的上下文切换导致的开销。

11 JVM 类加载机制,细节面试(加载过程)

类加载机制:加载,验证,准备,解析,初始化;验证,准备,解析属于连接;

0

  • 加载(面试细节类加载可以自定义吗?) :加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的 java.lang.Class 对 象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个 Class 文件获取,这里既 可以从 ZIP 包中读取(比如从 jar 包和 war 包中读取),也可以在运行时计算生成(动态代理), 也可以由其它文件生成(比如将 JSP 文件转换成对应的 Class 类)。
  • 连接-验证:

这一阶段的主要目的是为了确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

  • 连接-准备:面试细节,内存空间分配处于哪个阶段? final 值哪个阶段复制? static呢? 类初始化方法?

准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使 用的内存空间。注意这里所说的初始值概念,比如一个类变量定义为:public static int v = 8080;实际上变量 v 在准备阶段过后的初始值为 0 而不是 8080,将 v 赋值为 8080 的 put static 指令是程序被编译后,存放于类构造器方法之中但是public static final int v = 8080;在编译阶段会为 v 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 v 赋值为 8080。

  • 连接-解析:符号引用转换为直接引用

解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中 的:

CONSTANT_Class_info

CONSTANT_Field_info

CONSTANT_Method_info 等类型的常量。

什么是符号引用?定义在哪里?

符号引用: 符号引用与虚拟机实现的布局(内存布局)无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在 Java 虚拟机规范的 Class 文件格式中。

什么是直接引用?

直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有 了直接引用,那引用的目标必定已经在内存中存在。

  • 初始化:初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序代码。

12 类构造器 (面试细节:类初始化方法怎么生成的?怎么生成的?每个类都会生成吗?生成后都会执行吗?)

  • 初始化阶段是执行类构造器方法的过程,方法是由编译器自动收集类中的类变量的赋值操作(public static int v = 8080;)和静态语句块中的语句合并(static{})而成的。虚拟机会保证子方法执行之前,父类 的方法已经执行完毕,如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成()方法

注意以下几种情况不会执行类初始化

1. 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。

2. 定义对象数组,不会触发该类的初始化。

3. 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发该类的初始化。(面试细节:public static final int v = 8080;编译期间会存入调用类的常量池中。)

4. 通过类名获取 Class 对象,不会触发类的初始化。

5. 通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初 始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。

6. 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作。

13 类加载器

虚拟机设计团队把加载动作放到 JVM 外部实现,以便让应用程序决定如何获取所需的类,JVM 提 供了 3 种类加载器:

  • 启动类加载器(Bootstrap ClassLoader):负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath 参数指定路径中的,且被 虚拟机认可(按文件名识别,如 rt.jar)的类。
  • 扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOME\lib\ext 目录中的,或通过 java.ext.dirs 系统变量指定路径中的类 库。
  • 应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类库。

JVM 双亲委派模型(面试细节 SPI类,如何在Bootstrap ClassLoader 加载rt包的类时,加载自定义或者系统类呢?线程变量加载器?)

0

14 双亲委派 (面试细节:为什么要双亲委派?)

  • 当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父 类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中, 只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的 Class),子类加载器才会尝试自己去加载。
  • 采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载 器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object 对象(保证 Java 核心库的类型安全)。

0

15 OSGI(动态模型系统)

Open Service Gateway Initiative 是面向 Java 的动态模型系统,是 Java 动态化模块化系统的一系列规范。

  • 动态改变构造:OSGi 服务平台提供在多种网络设备上无需重启的动态改变构造的功能。为了最小化耦合度和促使这些耦合度可管理,OSGi 技术提供一种面向服务的架构,它能使这些组件动态地发现对方。

模块化编程与热插拔:

  • OSGi 旨在为实现 Java 程序的模块化编程提供基础条件,基于 OSGi 的程序很可能可以实现模块级的热插拔功能,当程序升级更新时,可以只停用、重新安装然后启动程序的其中一部分,这对企业级程序开发来说是非常具有诱惑力的特性。
  • OSGi 描绘了一个很美好的模块化开发目标,而且定义了实现这个目标的所需要服务与架构,同时也有成熟的框架进行实现支持。但并非所有的应用都适合采用 OSGi 作为基础架构,它在提供强大功能同时,也引入了额外的复杂度,因为它不遵守了类加载的双亲委托模型。
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页