offer来了(原理篇)学习笔记-第1章JVM

JVM

1.1 JVM的运行机制

JVM,Java Virtual Machine,是用于运行Java字节码的虚拟机,包括一套字符码指令集、一组程序寄存器、一个虚拟机栈、一个虚拟机堆、一个方法区和一个垃圾回收器。Java虚拟机包括一个类加载器子系统(class loader subsystem)、运行时数据区(Runtime Data Area), 执行引擎, 本地接口库(Native Interface Library)。其中本地接口库通过调用本地方法库(Native Method Library)与操作系统交互。

1.类加载器子系统用于将编译好的.class文件加载到JVM中。
2.数据区用于储存JVM运行过程中产生的数据,包括程序计数器、JAVA堆、Java栈、方法区、本地方法区。
3.执行引擎包括编译器(字节码->机器码)和垃圾回收器。
4.本地接口库用于调用操作系统的本地方法库完成具体指令操作。

一个Java进程开始运行后,虚拟机就开始实例化,多个进程启动会实例化多个虚拟机实例。进程退出或关闭,虚拟机实例消亡,多个虚拟机实例之间不能共享数据->!进程与线程差别!

Java程序具体的运行过程:

  1. Java源文件被编译器编译成字节码class文件。
  2. JVM将字节码文件编译成相应操作系统的机器码。
  3. 机器码调用对应操作系统的本地方法库执行相应方法。

1.2 多线程

在多核操作系统中,JVM中的线程与操作系统中的线程是相互对应的,调用操作系统的接口创建一个与之对应的原生线程;在JVM线程运行结束时,原生线程随之被收回,操作系统负责调度所有线程,并为其分配CPU时间片,在原生线程初始化完毕时,就会调用Java线程的run()执行该线程;在线程结束时,会释放原生线程和Java线程所对应的资源。

在JVM后台运行的线程主要有以下几个。

  • 虚拟机线程(JVM Thread):虚拟机线程在JVM到达安全点(SafePoint)时出现。
  • 周期性任务线程:通过定时器调度线程来实现周期性操作的执行。
  • GC线程:GC线程支持JVM中不同的垃圾回收活动。
  • 编译器线程:编译器线程在运行时将字节码动态编译成本地平台机器码,是JVM跨平台的具体实现。
  • 信号分发线程:接收发送到JVM的信号并调用JVM方法。

1.3 JVM的内存区域

**线程私有区域**的生命周期与线程相同,随着线程启动创建,随着线程结束而销毁。
  • 程序计数器是一块很小的内存空间,用于储存当前运行线程所执行字节码行号的指示器。唯一一个没有OOM(内存溢出)的区域。
  • 虚拟机栈是描述Java方法的执行过程的内存模型。栈帧(Stack Frame)用来记录方法的执行过程,方法被执行时虚拟机会为其创建一个与之对应的栈帧,方法的执行和返回对应栈帧在虚拟机栈中的入栈和出栈。栈帧储存了局部变量表、操作数栈、动态链接、返回地址等信息。动态链接,每个栈帧都保存了一个可以指向当前方法所在类的运行时常量池, 目的是当方法中需要调用其它方法的时候能够从运行时常量池中找到对应的符号引用, 然后将符号引用转换为直接引用然后就能直接调用对应的方法这就是动态链接,不是所有的方法调用都需要进行动态链接的。有一部分的符号引用会在类加载的解析阶段将符号引用转换为直接引用,这部分操作称之为静态解析。
  • 本地方法区和虚拟机栈的作用类似,区别是虚拟机栈为执行Java方法服务,本地方法栈为Native方法服务。

线程共享区域随虚拟机的启动而创建,随虚拟机的关闭而销毁。

  • Java堆,在JVM运行过程中创建的对象和产生的数据都被存储在堆中,堆是被线程共享的内存区域,也是垃圾收集器进行垃圾回收的最主要的内存区域。由于现代JVM采用分代收集算法,因此Java堆从GC(Garbage Collection,垃圾回收)的角度还可以细分为:新生代、老年代和永久代。
  • 方法区也被称为永久代,用于存储常量、静态变量、类信息、即时编译器编译后的机器码、运行时常量池等数据。JVM把GC分代收集扩展至方法区,即使用Java堆的永久代来实现方法区,这样JVM的垃圾收集器就可以像管理Java堆一样管理这部分内存。永久带的内存回收主要针对常量池的回收和类的卸载,因此可回收的对象很少。

直接内存也叫作堆外内存,它并不是JVM运行时数据区的一部分,但在并发编程中被频繁使用。JDK的NIO模块提供的基于Channel与Buffer的I/O操作方式就是基于堆外内存实现的,NIO模块通过调用Native函数库直接在操作系统上分配堆外内存,然后使用DirectByteBuffer对象作为这块内存的引用对内存进行操作,Java进程可以通过堆外内存技术避免在Java堆和Native堆中来回复制数据带来的资源占用和性能消耗,因此堆外内存在高并发应用场景下被广泛使用(Netty、Flink、HBase、Hadoop都有用到堆外内存)。!直接内存的含义!

1.4 JVM运行时内存

JVM的运行时内存也叫作JVM堆,从GC的角度可以将JVM堆分为新生代、老年代和永久代。其中新生代默认占1/3堆空间,老年代默认占2/3堆空间,永久代占非常少的堆空间。

新生代又分为Eden区、SurvivorFrom区和SurvivorTo区,Eden区默认占8/10新生代空间,SurvivorFrom区和SurvivorTo区默认分别占1/10新生代空间。(书中有错,应该是Survivor区)

JVM新创建的对象(除了大对象外)会被存放在新生代,默认占1/3堆内存空间。由于JVM会频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。**新生代的GC过程叫作MinorGC,采用复制算法实现。**通常对象在年龄达到15时,将被移到老年代。

  • Eden区:Java新创建的对象首先会被存放在Eden区,如果新创建的对象属于大对象,则直接将其分配到老年代。对象大小的定义可设置,通常为2kb-128kb。
  • To区:保留上一次MinorGC时的幸存者。
  • From区:将上一次MinorGC时的幸存者作为这一次MinorGC的被扫描者。

老年代主要存放有长生命周期的对象和大对象。老年代的GC过程叫作MajorGC。在老年代,对象比较稳定,MajorGC不会被频繁触发。在进行MajorGC前,JVM会进行一次MinorGC,在MinorGC过后仍然出现老年代空间不足或无法找到足够大的连续空间分配给新创建的大对象时,会触发MajorGC进行垃圾回收,释放JVM的内存空间。MajorGC采用标记清除算法/标记整理,该算法首先会扫描所有对象并标记存活的对象,然后回收未被标记的对象,并释放内存空间。因为要先扫描老年代的所有对象再回收,所以MajorGC的耗时较长。MajorGC的标记清除算法容易产生内存碎片。在老年代没有内存空间可分配时,会抛出Out Of Memory异常。

永久代指内存的永久保存区域,主要存放Class和Meta(元数据)的信息。Class在类加载时被放入永久代。永久代和老年代、新生代不同,GC不会在程序运行期间对永久代的内存进行清理,这也导致了永久代的内存会随着加载的Class文件的增加而增加,在加载的Class文件过多时会抛出Out Of Memory异常,比如Tomcat引用Jar文件过多导致JVM内存不足而无法启动。在Java 8中永久代已经被元数据区(也叫作元空间)取代。元数据区的作用和永久代类似,二者最大的区别在于:元数据区并没有使用虚拟机的内存,而是直接使用操作系统的本地内存。因此,元空间的大小不受JVM内存的限制,只和操作系统的内存有关。在Java 8中,JVM将类的元数据放入本地内存(Native Memory)中,将常量池和类的静态变量放入Java堆中,这样JVM能够加载多少元数据信息就不再由JVM的最大可用内存(MaxPermSize)空间决定,而由操作系统的实际可用内存空间决定。

1.5 垃圾回收与算法

Java采用引用计数法可达性分析来确定对象是否应该被回收。其中,引用计数法容易产生循环引用的问题,可达性分析通过根搜索算法(GC Roots Tracing)来实现。根搜索算法以一系列GC Roots的点作为起点向下搜索,在一个对象到任何GC Roots都没有引用链相连时,说明其已经死亡。

  • 计数法:在Java中如果要操作对象,就必须先获取该对象的引用,因此可以通过引用计数法来判断一个对象是否可以被回收。在为对象添加一个引用时,引用计数加1;在为对象删除一个引用时,引进计数减1;如果一个对象的引用计数为0,则表示此刻该对象没有被引用,可以被回收。引用计数法容易产生循环引用问题。循环引用指两个对象相互引用,导致它们的引用一直存在,而不能被回收。

  • 可达性分析:为了解决引用计数法的循环引用问题,Java还采用了可达性分析来判断对象是否可以被回收。具体做法是首先定义一些GC Roots对象,然后以这些GC Roots对象作为起点向下搜索,如果在GC roots和一个对象之间没有可达路径,则称该对象是不可达的。不可达对象要经过至少两次标记才能判定其是否可以被回收,如果在两次标记后该对象仍然是不可达的,则将被垃圾收集器回收。

1.5.1 Java中常用的垃圾回收算法

Java中常用的垃圾回收算法有标记清除(Mark-Sweep)、复制(Copying)、标记整理(Mark-Compact)和分代收集(Generational Collecting)这4种垃圾回收算法。(补充优缺点)

分区算法将整个堆空间划分为连续的大小不同的小区域,对每个小区域都单独进行内存使用和垃圾回收,这样做的好处是可以根据每个小区域内存的大小灵活使用和释放内存。
分区收集算法可以根据系统可接受的停顿时间,每次都快速回收若干个小区域的内存,以缩短垃圾回收时系统停顿的时间,最后以多次并行累加的方式逐步完成整个内存区域的垃圾回收。如果垃圾回收机制一次回收整个堆内存,则需要更长的系统停顿时间,长时间的系统停顿将影响系统运行的稳定性。

1.6 Java中的4种引用类型

在Java中一切皆对象,对象的操作是通过该对象的引用(Reference)实现的,Java中的引用类型有4种,分别为强引用、软引用、弱引用和虚引用

  • 强引用:在Java中最常见的就是强引用。在把一个对象赋给一个引用变量时,这个引用变量就是一个强引用。有强引用的对象一定为可达性状态,所以不会被垃圾回收机制回收。因此,强引用是造成Java内存泄漏(Memory Link)的主要原因。
  • 弱引用:软引用通过SoftReference类实现。如果一个对象只有软引用,则在系统内存空间不足时该对象将被回收。
  • 弱引用:弱引用通过WeakReference类实现,如果一个对象只有弱引用,则在垃圾回收过程中一定会被回收。
  • 虚引用:虚引用通过PhantomReference类实现,虚引用和引用队列联合使用,主要用于跟踪对象的垃圾回收状态。

1.7 垃圾收集器

Java堆内存分为新生代和老年代:新生代主要存储短生命周期的对象,适合使用复制算法进行垃圾回收;老年代主要存储长生命周期的对象,适合使用标记整理算法进行垃圾回收。因此,JVM针对新生代和老年代分别提供了多种不同的垃圾收集器,针对新生代提供的垃圾收集器有Serial、ParNew、Parallel Scavenge,针对老年代提供的垃圾收集器有Serial Old、Parallel Old、CMS,还有针对不同区域的G1分区收集算法。(每个收集器的详细内容,未整理,详情可看书)

1.8 Java网络编程模型

网络模型主要分为阻塞IO模型、非阻塞IO模型、多路复用IO模型、信号驱动IO模型、异步IO模型。Java中的网络编程模型BIO,NIO和AIO

  • 阻塞I/O模型是常见的I/O模型,在读写数据时客户端会发生阻塞。阻塞I/O模型的工作流程为:在用户线程发出I/O请求之后,内核会检查数据是否就绪,此时用户线程一直阻塞等待内存数据就绪;在内存数据就绪后,内核将数据复制到用户线程中,并返回I/O执行结果到用户线程,此时用户线程将解除阻塞状态并开始处理数据。典型的阻塞I/O模型的例子为data = socket.read(),如果内核数据没有就绪,Socket线程就会一直阻塞在read()中等待内核数据就绪。
  • 非阻塞I/O模型指用户线程在发起一个I/O操作后,无须阻塞便可以马上得到内核返回的一个结果。如果内核返回的结果为false,则表示内核数据还没准备好,需要稍后再发起I/O操作。一旦内核中的数据准备好了,并且再次收到用户线程的请求,内核就会立刻将数据复制到用户线程中并将复制的结果通知用户线程。在非阻塞I/O模型中,用户线程需要不断询问内核数据是否就绪,在内存数据还未就绪时,用户线程可以处理其他任务,在内核数据就绪后可立即获取数据并进行相应的操作。
while(true){
  data  =  socket.read();if(data == true){//1:内核数据就绪
      //获取并处理内核数据
      break;}else{   //2:内核数据未就绪,用户线程处理其他任务
  }}
  • 多路复用IO模型是多线程并发编程用得较多的模型,Java NIO就是基于多路复用I/O模型实现的。在多路复用I/O模型中会有一个被称为Selector的线程不断轮询多个Socket的状态,只有在Socket有读写事件时,才会通知用户线程进行I/O读写操作。因为在多路复用I/O模型中只需一个线程就可以管理多个Socket(阻塞I/O模型和非阻塞1/O模型需要为每个Socket都建立一个单独的线程处理该Socket上的数据),并且在真正有Socket读写事件时才会使用操作系统的I/O资源,大大节约了系统资源。Java NIO在用户的每个线程中都通过selector.select()查询当前通道是否有事件到达,如果没有,则用户线程会一直阻塞。而多路复用I/O模型通过一个线程管理多个Socket通道,在Socket有读写事件触发时才会通知用户线程进行I/O读写操作。因此,多路复用I/O模型在连接数众多且消息体不大的情况下有很大的优势。尤其在物联网领域比如车载设备实时位置、智能家电状态等定时上报状态且字节数较少的情况下优势更加明显,一般一个经过优化后的16核32GB服务器能承载约10万台设备连接。非阻塞I/O模型在每个用户线程中都进行Socket状态检查,而在多路复用I/O模型中是在系统内核中进行Socket状态检查的,这也是多路复用I/O模型比非阻塞I/O模型效率高的原因。多路复用I/O模型通过在一个Selector线程上以轮询方式检测在多个Socket上是否有事件到达,并逐个进行事件处理和响应。因此,对于多路复用I/O模型来说,在事件响应体(消息体)很大时,Selector线程就会成为性能瓶颈,导致后续的事件迟迟得不到处理,影响下一轮的事件轮询。在实际应用中,在多路复用方法体内一般不建议做复杂逻辑运算,只做数据的接收和转发,将具体的业务操作转发给后面的业务线程处理。
  • 信号驱动I/O模型中,在用户线程发起一个I/O请求操作时,系统会为该请求对应的Socket注册一个信号函数,然后用户线程可以继续执行其他业务逻辑;在内核数据就绪时,系统会发送一个信号到用户线程,用户线程在接收到该信号后,会在信号函数中调用对应的I/O读写操作完成实际的I/O请求操作。
  • 异步I/O模型中,用户线程会发起一个asynchronous read操作到内核,内核在接收到synchronous read请求后会立刻返回一个状态,来说明请求是否成功发起,在此过程中用户线程不会发生任何阻塞。接着,内核会等待数据准备完成并将数据复制到用户线程中,在数据复制完成后内核会发送一个信号到用户线程,通知用户线程asynchronous读操作已完成。在异步I/O模型中,用户线程不需要关心整个I/O操作是如何进行的,只需发起一个请求,在接收到内核返回的成功或失败信号时说明I/O操作已经完成,直接使用数据即可。**在异步I/O模型中,I/O操作的两个阶段(请求的发起、数据的读取)都是在内核中自动完成的,最终发送一个信号告知用户线程I/O操作已经完成,用户直接使用内存写好的数据即可,不需要再次调用I/O函数进行具体的读写操作,因此在整个过程中用户线程不会发生阻塞。**在信号驱动模型中,用户线程接收到信号便表示数据已经就绪,需要用户线程调用I/O函数进行实际的I/O读写操作,将数据读取到用户线程;而在异步I/O模型中,用户线程接收到信号便表示I/O操作已经完成(数据已经被复制到用户线程),用户可以开始使用该数据了。异步I/O需要操作系统的底层支持,在Java 7中提供了Asynchronous I/O操作。

Java的IO

在整个Java.io包中最重要的是5个类和1个接口。5个类指的是File、OutputStream、InputStream、Writer、Reader,1个接口指的是Serializable

  • Java NIO的实现主要涉及三大核心内容:Selector(选择器)Channel(通道)和Buffer(缓冲区)。Selector用于监听多个Channel的事件,比如连接打开或数据到达,因此,一个线程可以实现对多个数据Channel的管理。传统I/O基于数据流进行I/O读写操作;而Java NIO基于Channel和Buffer进行I/O读写操作,并且数据总是被从Channel读取到Buffer中,或者从Buffer写入Channel中。

Channel和I/O中的Stream(流)类似,只不过Stream是单向的(例如InputStream、OutputStream),而Channel是双向的,既可以用来进行读操作,也可以用来进行写操作。NIO中Channel的主要实现有:FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel,分别对应文件的I/O、UDP、TCP I/O、Socket Client和Socker Server操作。

Buffer实际上是一个容器,其内部通过一个连续的字节数组存储I/O上的数据。在NIO中,Channel在文件、网络上对数据的读取或写入都必须经过Buffer。客户端在向服务端发送数据时,必须先将数据写入Buffer中,然后将Buffer中的数据写到服务端对应的Channel上。服务端在接收数据时必须通过Channel将数据读入Buffer中,然后从Buffer中读取数据并处理。在NIO中,Buffer是一个抽象类,对不同的数据类型实现不同的Buffer操作。常用的Buffer实现类有:ByteBuffer、IntBuffer、CharBuffer、LongBuffer、DoubleBuffer、FloatBuffer、ShortBuffer。

Selector用于检测在多个注册的Channel上是否有I/O事件发生,并对检测到的I/O事件进行相应的响应和处理。因此通过一个Selector线程就可以实现对多个Channel的管理,不必为每个连接都创建一个线程,避免线程资源的浪费和多线程之间的上下文切换导致的开销。同时,Selector只有在Channel上有读写事件发生时,才会调用I/O函数进行读写操作,可极大减少系统开销,提高系统的并发量。

Java NIO和传统I/O(BIO)的最大区别如下。

  1. I/O是面向流的,NIO是面向缓冲区的:在面向流的操作中,数据只能在一个流中连续进行读写,数据没有缓冲,因此字节流无法前后移动。而在NIO中每次都是将数据从一个Channel读取到一个Buffer中,再从Buffer写入Channel中,因此可以方便地在缓冲区中进行数据的前后移动等操作。该功能在应用层主要用于数据的粘包、拆包等操作,在网络不可靠的环境下尤为重要。
  2. 传统I/O的流操作是阻塞模式的NIO的流操作是非阻塞模式的。在传统I/O下,用户线程在调用read()或write()进行I/O读写操作时,该线程将一直被阻塞,直到数据被读取或数据完全写入。NIO通过Selector监听Channel上事件的变化,在Channel上有数据发生变化时通知该线程进行读写操作。对于读请求而言,在通道上有可用的数据时,线程将进行Buffer的读操作,在没有数据时,线程可以执行其他业务逻辑操作。对于写操作而言,在使用一个线程执行写操作将一些数据写入某通道时,只需将Channel上的数据异步写入Buffer即可,Buffer上的数据会被异步写入目标Channel上,用户线程不需要等待整个数据完全被写入目标Channel就可以继续执行其他业务逻辑。

Java NIO具体的代码实现见MyServer和Client代码文件。

1.9 JVM的类加载机制

JVM的类加载分为7(5)个阶段:加载、连接(验证、准备、解析)、初始化、使用、卸载。

  • 1.加载:指JVM读取Class文件,并且根据Class文件描述创建java.lang.Class对象的过程。类加载过程主要包含将Class文件读取到运行时区域的方法区内,在堆中创建java.lang.Class对象,并封装类在方法区的数据结构的过程,在读取Class文件时既可以通过文件的形式读取,也可以通过jar包、war包读取,还可以通过代理自动生成Class或其他方式读取。
  • 2.验证:主要用于确保Class文件符合当前虚拟机的要求,保障虚拟机自身的安全,只有通过验证的Class文件才能被JVM加载。
  • 3.准备:是在方法区中为类变量分配内存空间并设置类中变量的初始值。初始值指不同数据类型的默认值,这里需要注意:final类型的变量和非final类型的变量在准备阶段的数据初始化过程不同。
public static long value = 1000;

在以上代码中,静态变量value在准备阶段的初始值是0,将value设置为1000的动作是在对象初始化时完成的,因为JVM在编译阶段会将静态变量的初始化操作定义在构造器中。静态变量在准备时期为初始值,初始化时期赋值。

public static final long value = 1000;

则JVM在编译阶段后会为final类型的变量value生成其对应的ConstantValue属性,虚拟机在准备阶段会根据ConstantValue属性将value赋值为1000。静态final变量在准备时期赋值。

  • 4.解析:JVM会将常量池中的符号引用替换为直接引用。
  • 5.初始化:主要通过执行类构造器的client方法为类进行初始化。client方法是在编译阶段由编译器自动收集类中静态语句块和变量的赋值操作组成的。JVM规定,只有在父类的client方法都执行成功后,子类中的client方法才可以被执行。在一个类中既没有静态变量赋值操作也没有静态语句块时,编译器不会为该类生成client方法。
    ◎ 常量在编译时会将其常量值存入使用该常量的类的常量池中,该过程不需要调用常量所在的类,因此不会触发该常量类的初始化。
    ◎ 在子类引用父类的静态字段时,不会触发子类的初始化,只会触发父类的初始化。
    ◎ 定义对象数组,不会触发该类的初始化。
    ◎ 在使用类名获取Class对象时不会触发类的初始化。
    ◎ 在使用Class.forName加载指定的类时,可以通过initialize参数设置是否需要对类进行初始化。
    ◎ 在使用ClassLoader默认的loadClass方法加载类时不会触发该类的初始化。

类加载器和双亲委派

JVM提供了3种类加载器,分别是启动类加载器、扩展类加载器和应用程序类加载器

  • 启动类加载器:负责加载Java_HOME/lib目录中的类库,或通过-Xbootclasspath参数指定路径中被虚拟机认可的类库。
  • 扩展类加载器:负责加载Java_HOME/lib/ext目录中的类库,或通过java.ext.dirs系统变量加载指定路径中的类库。
  • 应用程序类加载器:负责加载用户路径(classpath)上的类库。

除了上述3种类加载器,我们也可以通过继承java.lang.ClassLoader实现自定义的类加载器。

双亲委派
JVM通过双亲委派机制对类进行加载。双亲委派机制指一个类在收到类加载请求后不会尝试自己加载这个类,而是把该类加载请求向上委派给其父类去完成,其父类在接收到该类加载请求后又会将其委派给自己的父类,以此类推,这样所有的类加载请求都被向上委派到启动类加载器中。若父类加载器在接收到类加载请求后发现自己也无法加载该类(通常原因是该类的Class文件在父类的类加载路径中不存在),则父类会将该信息反馈给子类并向下委派子类加载器加载该类,直到该类被成功加载,若找不到该类,则JVM会抛出ClassNotFoud异常。

具体流程:
(1)将自定义加载器挂载到应用程序类加载器。
(2)应用程序类加载器将类加载请求委托给扩展类加载器。
(3)扩展类加载器将类加载请求委托给启动类加载器。
(4)启动类加载器在加载路径下查找并加载Class文件,如果未找到目标Class文件,则交由扩展类加载器加载。
(5)扩展类加载器在加载路径下查找并加载Class文件,如果未找到目标Class文件,则交由应用程序类加载器加载。
(6)应用程序类加载器在加载路径下查找并加载Class文件,如果未找到目标Class文件,则交由自定义加载器加载。
(7)在自定义加载器下查找并加载用户指定目录下的Class文件,如果在自定义加载路径下未找到目标Class文件,则抛出ClassNotFoud异常。

双亲委派机制的核心是保障类的唯一性和安全性。例如在加载rt.jar包中的java.lang.Object类时,无论是哪个类加载器加载这个类,最终都将类加载请求委托给启动类加载器加载,这样就保证了类加载的唯一性。如果在JVM中存在包名和类名相同的两个类,则该类将无法被加载,JVM也无法完成类加载流程。

OSGI(Open Service Gateway Initiative)
OSGI是Java动态化模块化系统的一系列规范,旨在为实现Java程序的模块化编程提供基础条件。基于OSGI的程序可以实现模块级的热插拔功能,在程序升级更新时,可以只针对需要更新的程序进行停用和重新安装,极大提高了系统升级的安全性和便捷性。

OSGI提供了一种面向服务的架构,该架构为组件提供了动态发现其他组件的功能,这样无论是加入组件还是卸载组件,都能被系统的其他组件感知,以便各个组件之间能更好地协调工作。

OSGI不但定义了模块化开发的规范,还定义了实现这些规范所依赖的服务与架构,市场上也有成熟的框架对其进行实现和应用,但只有部分应用适合采用OSGI方式,因为它为了实现动态模块,不再遵循JVM类加载双亲委派机制和其他JVM规范,在安全性上有所牺牲。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值