JAVA BIO/NIO

同步

发起一个请求或任务,被调用者在未完成请求或任务前,不会返回结果。

需要一直等待该请求返回或任务完成反馈的结果,在这期间不能够去做其他的事情。

比如:你打电话给书店老板询问书籍,老板帮你去找书,你需要一直等待,等待书店老板给你回复。

异步

发起一个请求或任务之后,被调用者会理刻返回表示已经接受请求或任务,但是并没有返回结果,就接着去做别的事情,发出的请求或任务完成时,被调用者会返回结果。

比如:你打电话给书店老板询问书籍,你让他查到了再打电话给你,然后你挂断电话,期间你可以干其他事情,等到老本找到了书籍然后给你打电话。

阻塞

发起请求后,调用者需要一直等待结果返回,也就是当前的线程会被挂起,无法去做其他请求。

非阻塞

发起请求后,调用者不需要等待结果返回,可以去做别的事情。

BIO(Blocking I/O)(最传统的同步阻塞IO模型)

典型的同步阻塞IO模型:data = socket.read();

当应用程序发出请求时,先去判断内核中的数据是否准备完成,如果没有准备完成,该应用程序就会被阻塞(让出cpu资源),等到内核数据准备完成,将数据拷贝给应用程序,应用程序解除block状态

基于字节流和字符流操作,数据流的特点是单向性,要么只读、要么只写。

server端要为每一个连接建立一个线程,这样的好处是每个线程可以专注自身的I/O操作并且编程简单。但是这种模型并不适合连接数过多情况。

NIO(Non - Blocking I/O)(同步非阻塞IO模型)

当应用程序发出read请求后,不需要等待,它会马上得到来自内核的返回,如果返回的结果是error(数据没有准备好),那么应用程序就继续向内核发出请求,再次去确认,这样不停做循环,一旦内核准好数据同时应用程序发来请求,那么就将数据拷贝给应用程序,返回。

这样带来的问题:线程没有进入阻塞状态,它就不会让出cpu资源,导致cpu的占用率很高。

NIO的组成包括:Channel(通道)、 Buffer(缓冲区)、Selector

Channel

  • Channel和Stream()是同一个级别的,区别在于:Stream是单向的,而Channel是双向的,既可以用来读也可以用来写操作。

  • Channel的主要实现:

    1. FileChannel
    2. DatagramChannel
    3. SocketChannel
    4. ServerSocketChannel

    1对应文件IO、2对应文件UDP、3、4对应文件TCP(Server和 Client)

  • Buffer

    是一个容器,连续的数组用来存储数据。

    Channel提供从文件、网络读取数据的渠道,但是读写操作都必须由Buffer来操作。

    上面是一个从客户端向服务器端发送数据的过程。

    客户端发出数据经过Buffer写入传给Channel,读入数据经过Channel将数据读入Buffer传给服务端。

  • Selector

    是NIO的核心类,通过Selector去检测多个Channel中是否有数据的发生(读或写请求),如果对应的Channel上有真正的请求发生,那么就去处理该Channel上的请求。Selector本身也是一个线程,设定一个线程专门去管理多个Channel的请求任务,而不需要为每一个Channel去建立对应的线程,避免了多个线程之间的上下文切换,大大减少了系统的开销。

多路复用IO模型

此模型的本质还是NIO模型,在NIO中的通过Selector实现在一个线程轮询多个通道的数据,需要先将用户线程中需要轮询的socket注册到Selector中,用Selector去轮询多个socketChannel是否有请求到达,一旦请求到达,Selector.select返回,最后完成I/O数据的传输这个过程用户线程是处于阻塞状态的。注意:socket配置也是非阻塞的。

相比于NIO,多路复用IO用户线程首先需要在Reactor中注册一个事件处理器,然后Reactor(相当于上文提到的selector)负责轮询各个通道是否有新的数据到来,当有新的数据到来时,Reactor通过先前注册的事件处理器通知用户线程有数据可读,此时用户线程向内核发起读取IO数据的请求,用户线程阻塞直至数据读取完成。

多路复用IO模型效率高于NIO模型原因在于:NIO中socket轮询是在用户线程中的,而多路复用是在内核中。

信号驱动IO模型

当用户线程发起一个I/O请求时,给对应的socket注册一个信号函数,用户不会立刻得到结果(内核中数据还没有准备好),而是继续去做别的事情,当内核中数据准备号时,给用户发送一个信号给用户线程,用户线程通过在信号函数中调用I/O操作进行实际的读写操作。

异步IO模型

该模型是最理想模型。它实现的流程:当用户线程发起read操作之后,就去做它自己的事情了,内核接收到用户线程的请求后,立刻返回,表明该请求已经受理,这个过程不会对用户线程造成任何阻塞。因为内核在完成数据准备后就将数据拷贝给用户线程,返回用户线程信息表明数据已经传输完毕,read操作已经完成,不需要用户线程再去调用IO操作。只需要先发起一个请求,当接收内核返回的成功信号时表示 IO 操作已经完成,可以直接去使用数据了。

和信号驱动模型的差别就在这里,信号驱动模型在内核完成数据准备之后,告诉用户线程数据已经准备完毕,需要你自己来调用IO操作拿到数据。


Java IO

IO分类

  • 按照流的流向分,可以分为输⼊流和输出流;
  • 按照操作单元划分,可以划分为字节流和字符流;
  • 按照流的⻆⾊划分为节点流和处理流。

InputStream/Reader: 所有的输⼊流的基类,前者是字节输⼊流,后者是字符输⼊流。

OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么I/O流操作要分为字节流操作和字符流操作呢?

字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是⾮常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就⼲脆提供了⼀个直接操作字符的接⼝⽅便我们平时对字符进⾏流操作。


JVM类加载机制

Java程序运行时,必须经过编译和运行两个步骤。首先将后缀名为.java的源文件进行编译,最终生成后缀名为.class的字节码文件。然后Java虚拟机将编译好的字节码文件加载到内存(这个过程被称为类加载,是由加载器完成的),然后虚拟机针对加载到内存的java类进行解释执行,显示结果。

JVM类加载大致分为三个过程:加载、连接、初始化

类加载器

在类加载的过程中,只有加载阶段可以自定义类加载器,而其他阶段由JVM主导。

因此加载的阶段被放到了JVM外部实现,便于让应用程序决定如何获取所需的类。

JVM中提供了三种类加载器:

  • 启动类加载器(Bootstrap ClassLoader)

    最顶层的类加载器,由c++实现。

    负责加载:JAVA_HOME\lib 目录中的jar包或被 -Xbootclasspath 参数指定的路径中的所有类

  • 扩展类加载器(Extension ClassLoader)

    继承自:java.lang.ClassLoader

    负责加载 JAVA_HOME\lib\ext 目录中的,或通过 java.ext.dirs 系统变量指定路径中的类库。

  • 应用程序类加载器(Application ClassLoader/ System Class Loader ):

    (这里的Application ClassLoader 和System Class Loader 是同一个类加载器,只是叫法不同)

    继承自:java.lang.ClassLoader

    ⾯向我们⽤户的加载器,负责加载当前应⽤classpath下的所有jar包和类。

可以通过继承 java.lang.ClassLoader实现自定义的类加载器,重写findClass方法加载指定路径上的class。

双亲委派模型

每一个类都有对应的类加载器。当类收到一个加载请求时,先去判断这个类是否已经被加载,被加载过的类会直接返回,否则尝试加载。

加载过程:类本身不会主动去加载,它会将请求委派给父类加载器loadClass()处理,父类则会委派给父类的父类,因此所有的请求都会传到顶层的类加载器中(Bootstrap ClassLoader),只有当父类中的加载器无法进行加载时,自己才会来处理类加载

注意:类加载器之间的“⽗⼦”关系也不是通过继承来体现的,是由“优先级”来决定

   //源码private final ClassLoader parent;protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// ⾸先,检查请求的类是否已经被加载过Class<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {if (parent != null) {//⽗加载器不为空,调⽤⽗加载loadClass()⽅法处理c = parent.loadClass(name, false);} else {//⽗加载器为空,使⽤启动类加载器BootstrapClassLoader 加载c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {//抛出异常说明⽗类加载器⽆法完成加载请求}if (c == null) {long t1 = System.nanoTime();//⾃⼰尝试加载c = findClass(name);// this is the defining class loader; record thestatssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}

好处:避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类)

比如:加载rs.jar包中的类java.lang.Object,无论加载器加载那个类,最终委派给启动类加载器进行类的加载,保证了不同的类加载器最终得到的是同一个Ojbect对象。

连接的过程在细分为:验证、准备、解析

  • 加载

    这个阶段会在内存中生成代表该类的java.lang.Class对象(类对象),作为方法区这个类的各种数据的入口。

    注意:获取类的过程并不一定要从Class文件中,也可以从jar包或war包中获取,也可以运行时计算生成(动态代理)。

  • 连接

    • 验证

      确保class文件的字节流中的信息符合JVM的要求,不会危害JVM的安全

    • 准备

      正式为类中的变量分配内存以及为其设置初始值阶段(在方法区中为这些类变量分配内存)。

      举例:

      public static int v = 8080;
      /*
      定义的静态变量 v
      这里初始化值的过程并不是直接将 8080 赋值给 v ,v 变量在准备阶段的初始化值是 0 ,
      将其赋值为 8080 的是put static 指令,该指令被编译后存放在类构造器<client>方法中。
      */public final static int v = 8080;
      /*
      如果声明静态变量被final关键字所修饰,在编译阶段生成的v 的ConstantValue属性,在准备阶段中v被赋值为 8080
      */
      
    • 解析

      JVM将常量池中符号引用替换直接引用的过程。

      • 符号引用

        一组符号所描述的引用目标,符号的形式是字面量。

        如:在Class文件中它以CONSTANT_Class_info、CONSTANT_Field_info、CONSTANT_Method_info等类型的常量出现。

        在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的变量(基本数据类型、局部变量、方法等等)实际地址因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language。

        各种JVM实现内存的布局可能不同,但是它们识别符号引用是一致的。

      • 直接引用

        指向目标的指针,相对偏移量(指向实例变量、实例方法的直接引用都是偏移量),间接定位到目标的句柄。

        直接引用与JVM布局有关,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。

        如果有了直接引用,那目标必定存在于内存中。

  • 初始化

    类加载的最后一个阶段,除了在类加载阶段可以自定义类加载器,其他的阶段都是由JVM主导控制。

    这个阶段才是正真的执行字节码文件,根据字节码文件的内容对类的各个字段进行赋值。

类构造器

初始化阶段是类构造器方法执行的过程,方法是由编译器收集类中的类变量赋值操作和静态代码块合成的,JVM会保证方法执行前其父类的方法已经执行完毕。如果一个类中没有静态变量、静态语句块,那么编译器可以不为这个类生成方法

Java对象创建的过程

对象的创建分为五个过程

  • 类加载检查

    当JVM接收到new指令时,首先会先去检查该指令能否在常量池中找到对应该类的符号引用,并检查该类是否已经被加载、连接,初始化过。如果没有先进行类的加载。

  • 分配内存

    在类加载检查完成后,在java堆中为新生的对象分配内存。

    分配内存方式有两种:指针碰撞空闲列表

    分配方式选择根据:java堆内存是否规整

    指针碰撞

    • 适用场景:java堆内存规整(没有内存碎片化)
    • GC收集器:Serial (单线程、复制算法)和 PerNew(多线程、复制算法)。
    • 原理:将内存分为两块,中间有一个分界值指针,用过的内存放一边,没有用过的内存放一边,将对象放入到没有用过的内存中

    空闲列表:

    • 适用场景:Java堆内存碎片化
    • GC收集器:CMS(多线程+标记清除算法)
    • 原理:将列表中没有用过的内存标记下来,找到适合新生对象大小的位置放入即可,更新列表。

    分配内存需要考虑到线程安全的问题:

    对象的创建很频繁,因此JVM需要保证线程的安全:

    • TLAB:为每个线程预先在Eden区分配一块内存,JVM给对象分配内存时先在TLAB中分配,当TLAB中内存不够或者使用完之后,就采用CAS+失败重试的方法。
    • CAS+失败重试:CAS 是乐观锁的一种实现方式。乐观锁:每次不加锁而是假设没有冲突去完成某个操作,如果因为冲突失败就重试,直到成功为止。此方法保证更新操作的原子性
  • 初始化零值

    内存分配完成后,JVM将分配到的内存空间初始化零值(不包含对象头),这一操作保证了对象中的成员变量在不赋初值就可以使用。

  • 设置对象头

    对象头中的信息包含:

markword(标记字段)

  • 对象的哈希码
  • 对象对应的GC分代年龄
  • 锁状态标志、线程持有的锁、

klass(Class对象指针)

  • 对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例

数组长度(只有数组对象有)
如果对象是一个数组, 那在对象头中还必须有一块数据用于记录数组长度(例如:int)。

  • 执行构造方法

    从JVM角度来看一个对象已经产生,从java程序角度来看,创建对象才开始,执行构造方法,按照意愿将对象初始化数据之后这个对象才能够真正得到使用。

图上有点小错误:分配内存中:采用指针碰撞是复制算法,不是标记整理

牛客上做到的题目:

(单选题)以下代码的输出结果是?public class B{public static B t1 = new B();public static B t2 = new B();{System. out.println( "构造块");}static{System.out.println("静态块");}public static void main( String[] args){B t = new B( );}}//正确答案:构造块、构造块、静态块、构造块
//现从主函数Main中入手,执行 B t = new B( );也就是创建对象,创建对象之前需要先进行类加载过程,而类加载的过程需要检查类是否进行加载,现在进行类的加载过程。
/*类中定义了静态域:静态变量,静态代码块,静态方法。
静态域执行的过程:按照其定义的变量、代码块、方法的顺序来。
因此这里先执行public static B t1 = new B();这个代码,而这也是创建对象的语句,创建对象之前也是需要进行类加载检查,而B类在前就已经加载过了,注意:类中的静态域只在类第一次加载的过程中执行,因此这里不会再在进行静态域的加载,跳过静态域到构造块和构造方法的执行过程,完成t1对象创建,因此控制台输出:构造块。
接着回到 public static B t2 = new B();的过程,继续创建对象t2,这个过程和t1是一样的因此,输出:构造块。当两个静态变量都完成时,接下来执行静态代码块,输出:静态块。
执行构造块和构造方法,最后创建对象t,输出:构造块。
*/

对象访问定位的两种方式

建立对象之后就是要调用对象干事,java中通过栈上的reference数据来操作堆上的具体对象。

  • 句柄

    在java堆中开辟一块内存用作句柄池,句柄本身是指向对象的实例数据和类型数据,引用是指向句柄。

  • 直接指针

通过指针直接指向java堆中对象的地址,堆中对象的布局有所改变,在对象的实例数据中存放指向对象类型数据的地址。

句柄访问对象的好处就是:当对象的地址改变时,引用的地址不要改变,需要改变的是指向对象的句柄。

缺点:通过句柄这个间接访问对象开销要高一些。

直接指针:访问的速度快于句柄,对象地址发生改变时,引用也要发生改变。

Java知识总结(五)相关推荐

  1. JAVA知识基础(五):深入理解final关键字

    1.final 关键字 final关键字主要用在三个地方:变量.方法.类. final修饰变量:final修饰一个类时,表明这个类不能被继承. final修饰方法:final修饰方法,方法不可以重写, ...

  2. java知识回顾_Java – 2012年回顾和未来预测

    java知识回顾 这篇文章将重点讨论2012年发生的大小事件,并展望2013年的一些未来预测.其中一些预测将是诚实的猜测,而另一些则将是诚实的猜测. 好吧,只要说我的"恶魔般"的一 ...

  3. java小球落体问题_[Java 编程基本功] (五) 小球落体, 发奖金, 1,2,3,4 可以组成多少个数...

    [Java 编程基本功] (五) 小球落体, 发奖金, 1,2,3,4 可以组成多少个数 第十三题 一球从 100 米高度自由落下, 每次落地后反跳回原高度的一半; 再落下, 求它在第 10 次落地时 ...

  4. Java学习笔记(五):一张图总结完JVM8基础概念

    Java学习笔记(五):一张图总结完JVM8基础概念 引文 最近在学习JVM的相关内容,好不容易把基础概念全部都学了一遍,却发现知识网络是零零散散的.迫不得已,只好再来一次总的归纳总结.为了更好的理解 ...

  5. java知识精华总结

    Java 知识--精华总结 一. -java 概述与基础知识-6 1.何为编程?-6 2.Java 语言概述,历史.特点- 6 3.什么是跨平台性?原理是什么?JVM- 7 4.Jre 和 Jdk 的 ...

  6. Java知识——精华总结

    Java知识--精华总结 一.java概述与基础知识 1.何为编程? 编程就是让计算机为解决某个问题而使用某种程序设计语言编写程序代码,并最终得到结果的过程. 为了使计算机能够理解人的意图,人类就必须 ...

  7. 黑马程序员入学Java知识——精华总结

    黑马程序员入学Java知识--精华总结 J2SE部分,Java高新技术部分,7K面试题部分等黑马入学要求的知识点总结! 一.黑马程序员-java概述与基础知识 6 1.何为编程? 6 2.Java语言 ...

  8. 最全面的Java面试题-----是你更好的掌握java知识

    最全面的Java面试题-----是你更好的掌握java知识 目录 l     概念题--- 1 一.        JAVA基础--- 1 1)      面向对象的特征--- 1 2)      什 ...

  9. Java知识体系最强总结(2021版)

    学习Java请关注B站 [黑马程序员] 文章目录 前言 第一阶段: Java基础 第二阶段: JavaWeb 第三阶段:Java开发框架 第四阶段:中间件&服务框架 第五阶段:企业级项目实战 ...

最新文章

  1. springmvc十六:视图解析
  2. JSONP实现原理-简析
  3. python调用rust_转 从20秒到0.5秒:一个使用Rust语言来优化Python性能的案例
  4. GridView控件日期格式化
  5. 在线圆周率查询下载工具
  6. lxml简单用法 解析网页
  7. rbw数字信号处理_基于FPGA的数字中频信号处理的设计与实现
  8. java 宿舍管理系统教学,java学校宿舍管理系统
  9. 最全最新cpu显卡天梯图_2018 年新近详细CPU、显卡天梯图
  10. 十六、 方差分析--使用Python进行双因素方差分析
  11. 启用 Windows 审核模式(Audit Mode),以 Administrator 账户来设置电脑的开箱体验
  12. 圆弧周长公式_弧长的计算公式是什么?
  13. word分栏排版时 插入全页宽的表格
  14. pip install 报错:ERROR: Exception: Traceback (most recent call last):..raise ValueError(“check_hostnam
  15. Technorati的后台数据库架构
  16. MySQL超市会员管理系统_PHP+MYSQL药店会员管理系统的设计与实现
  17. Python实现数字转人民币(大写汉字)源代码
  18. windows7隐藏桌面计算机,win7小技巧之隐藏桌面图标
  19. B40 - 基于STM32单片机的电热蚊香蓝牙控制系统
  20. 鸿蒙灵儿宠物奔驰法拉利,鄂P66666,从奔驰换成法拉利,车主还有限量版劳斯莱斯,中国只有三台...

热门文章

  1. 学计算机专业的河北好找工作吗,一个中专计算机毕业,在河北省找工作好找吗...
  2. FileZilla-FTP下载失败
  3. 2010年月最新剑侠世界(剑侠情缘叁)(金山)CDKey :72099025
  4. 玉林首创广西全区不动产登记与房产交易一体化信息平台
  5. 旋转机械(轴承等)故障诊断公开数据集
  6. 为什么我创建了一个计算机用户名 再打开计算机时 我打不开以前的文件,为什么word文件打不开?Word文档打不开的原因及解决方法...
  7. MySQL基本查询操作(学习日记)
  8. laravel中汉字转拼音包
  9. 雷电pygame代码
  10. 签收电子承兑汇票需要注意的事项有哪些