深入Class文件结构

public class Message {
    public static void main(String[] args) {
        System.out.printf("junshan say:Hello Word!");
    }
}


Oolong的语法结构中以“.”符号开头表示这是一个基本的属性项,它对应到Java中就是表示一个Java的基本概念,如一个类、一个方法、一个属性、一个对象或者一个接口等。

类相关的指令

.source Message.java表示这个代码的源文件是Message.java

.class public Message表示这是一个类且公有的类名是Message

.super java/lang/Object表示这个类的父类是Object

方法的定义

.method public <init> ()V表示这是一个公有方法,没有参数,返回值类型是V(也就是void),<init>表示是构造函数

下面的.method public static main([Ljava/lang/String;)V表示的是main方法,它的参数是一个String类型的数组,其中[表示的是数组,而L表示的是一个类形式而不是基本数据类型,凡是L表示的类后面都会以;结尾,表示这个类的结束。

常量池

class文件中的常量池如下:



每个常量都是由三个字节来描述的。

第一个字节表示这个常量是什么类型的常量,JVM定义了12种类型的常量,每个种类的常量都会对应一个数值。

这些常量通常都是相互引用的,是一个常量引用了另一个常量。

UTF8常量类型

它可以存储多个字节长度的字符串值,如可以存储类名或者方法名等很长的一个字符串。

UTF8类型的常量由前两个字节来表示后面所存储的字符串的总字节数

01003c其中的3c表示后面所跟的字节长度有60个。

UTF8常量由三部分表示,包括这个常量是什么格式的,这个常量有多少个字节,后面就是这个常量实际的内容。

Fieldred、Methodref常量类型

为了描述Class中的属性项和方法。

0900100011 Fieldref类型常量

  • 前一个字节表示这个常量是Fieldref类型,所以是09
  • 后面两个字节表示的是该Fieldref是哪个类中的Field,存储的值是第几个常量的位置
  • 后面两个字节表示的是这个Fieldref常量的Name和Type,同样它也是指向NameAndType类型常量的索引

Class常量类型

Class常量表示的是该类的名称,它会指向另外一个UTF8类型的常量,该常量存储的是该类的具体名称

07表示的是Class类型的常量,后面的两个字节表示的是19个常量,而第19个常量正是一个UTF8类型的常量,该常量存储的是java/lang/Object,也就是该类的名称。

NameAndType常量类型

为了表示Methodref和Fieldref的名称和类型描述做进一步说明而存在的,名称通常又由UTF8常量类型来表示,而类型描述也由UTF8来表示,所以NameAndType类型是由一个字节的类型表示加上两个字节的UTF8的位置索引组成的。

  • 0007指向的是第7个常量,表示的是这个Methodref或者Fieldref的名称
  • 0008表示的是Methodref的返回类型或者Fieldref的参数类型

类信息

常量列表的后面就是关于这个类本身的信息描述了,如这个类的访问控制、名称和类型,以及是否有父类或者实现了某些接口等描述信息。

由两个字节表示这个类的访问控制描述

类访问控制的两个字节中实际上只使用了5个bit,其他的bit还没有定义,这5个bit中第一个bit表示的是该类是否是public的,为1的话就是public类,否则就是private类。所以对类的访问修饰只有两种,要么是public要么是private。

第5个bit表示的是该类是否是final类,1表示是,0表示否。

第6个bit描述该类是否含有invokespecial,也就是是否继承其他类,在Java中所有的类默认都继承了Object类。

第10个bit描述了该类是否是接口类,0表示不是接口类,1是接口类。

第12个bit表示该类是否是抽象类,0表示不是抽象类,1表示是抽象类。

后面两个字节0006是该类的类名称,它指向的是第6个常量,0004表示的是该父类的类名称,它指向的是第4个常量定义的名称,再后面的0000表示该类没有实现接口类。

2016/4/14 posted in  深入分析Java Web技术内幕

Javac编译原理

如何才能编译程序呢?

  • 首先,要读取源代码,一个字节一个字节地读进来,找出这些字节中哪些是我们定义的语法关键词。
    • 词法分析的结果就是从源码中找到一些规范化的Token流,就像人类语言中,给你一句话,你要能分辨出哪些是一个词语,哪些是标点符号
  • 接着,就是对这些Token流进行语法分析,检查这些关键词组合在一起是不是符合Java语言规范
    • 语法分析的结果就是形成一个符合Java语言规范的抽象语法树
  • 接下来是语义分析,判断语义是不是正确。主要工作是把一些难懂的、复杂的语法转化成更加简单的语法。
  • 最后,通过字节码生成器生成字节码,将会根据经过注解的抽象语法树生成字节码,就像将所有的中文词语翻译成英文单词后,按照英文语法组装成英文语句。

Javac工作原理分析

词法分析器

public class Cifa {
    int a;
    int c = a + 1;
}

Javac的主要词法分析器的默认实现类是com.sun.tools.javac.parser.Scanner,Scanner会逐个读取Java源文件的单个字符,然后解析出符合Java语言规范的Token序列

JavacParser规定了哪些词是符合Java语言规范规定的词具体读取和归类不同语法的操作由Scanner完成。Token规定了所有Java语言的合法关键词,Names用来存储和表示解析后的词法。

词法分析过程是在JavaParser的parseCompilationUnit方法中完成的。

从源文件的第一个字符开始,按照Java语法规范依次找出package、import、类定义,以及属性和方法定义等,最后构建一个抽象语法树。

词法分析器的分析结果就是将这个类中的所有关键词匹配到Token类的所有项中的任何一项。

语法分析器

词法分析器的作用就是将Java源文件的字符流转变成对应的Token流。而词法分析器是将词法分析器分析的Token流组建成更加结构化的语法树,也就是将一个个单词组装成一句话,一个完整的语句。哪些词语组合在一起是主语、哪些是谓语,要做进一步区分。

Javac的语法树使得Java源码更加结构化,这种结构化可以为后面的进一步处理提供方便。每个语法树上的节点都是com.sun.tools.javac.tree.JCTree的一个实例

JCTree类中有如下三个重要的属性项:

  • Tree tag: 每个语法节点都会用一个整形常数表示,并且每个节点类型的数值是在前一个的基础上加1。顶层节点TOPLEVEL是1,而IMPORT节点等于TOPLEVEL加1,等于2
  • pos:也是一个整数,它存储的是这个语法节点在源代码中的起始位置,一个文件的一个位置是0,而-1表示一个不存在的位置
  • type:表示的是这个节点是什么Java类型,如int、float等

下面以Yufa类为例,看看它的最后的语法树是什么:

public class Yufa {
    int a;
    private int c = a + 1;
    
    public int getC() {
        return c;
    }
    
    public void setC(int c) {
        this.c = c;
    }
}

这段代码对应的语法树是

语义分析器

前面介绍了将一个Java源文件先解析成一个一个的Token流,然后再经过语法分析器将Token流解析成更加结构化、可操作的一颗语法树。

将Java类中的符号输入到符号表中

主要由com.sun.tools.javac.comp.Enter类完成,这个类主要完成以下两个步骤。

  • 将所有类中出现的符号输入到类本身的符号表中,所有类符号、类的参数类型符号(泛型参数类型)、超类符号和继承的接口类型符号等都存储到一个未处理列表中
  • 将这个未处理列表中所有的类都解析到各自的类符号列表中

首先一个类中处理类本身会定义一些符号变量外,如类名称、变量名称和方法名称等,还有一些符号是引用其他类的,符号调用其他类的方法或者变量等,还有一些这个类可能会继承或者实现超类和接口等。这些符号都是在其他类中定义的。

第二个步骤就是按照递归向下的顺序解析语法树,将所有的符号都输入符号表中。

在Enter类解析这一步骤中,还有一个重要的步骤是添加默认的构造函数。

处理annotation

这个步骤是由com.sun.tools.javac.processing.JavaProcessingEnvironment类完成的。

标注

这个步骤最重要的是检查语义的合法性和进行逻辑判断,如:

  • 变量的类型是否匹配
  • 变量在使用前是否已经初始化
  • 能够推导出泛型方法的参数类型
  • 字符串常量的合并

还有一些类来协助

  • 检查语法树中的变量类型是否正确,如方法返回的类型是否与接收的引用值类型匹配
  • 检查变量、方法或者类的访问是否合法、变量是否是静态变量、变量是否已经初始化
  • 常量折叠,这里主要针对字符串常量,会将一个字符串常量中的多个字符串合并成一个字符串
  • 帮助推导泛型方法的参数类型等
public class Yufa {
    int a = 0;
    private int c = a + 1;
    private int d = 1 + 1;
    private String s = "hello " + "world";
}

经过Attr解析后,这个源码会变成如下:

public class Yufa {

    public Yufa() {
        super();
    }
    int a = 0;
    private int c = a + 1;
    private int d = 1 + 1;
    private String s = "hello world";
}

字符串s由两个"hello "和"world"合并成一个字符串

数据流分析

标注完成后就是com.sun.tools.javac.comp.Flow类完成数据流分析

  • 检查变量在使用前是否都已经正确赋值
  • 保证final修饰的变量不会被重复赋值
  • 方法的返回值类型都要确定
  • 所有Checked Exception都要捕获或者向上抛出
  • 所有的语句都要被执行到。这里会检查是否有语句出现在一个方法return的后面
进一步度对语法树进行语义分析

语义分析的最后一个步骤是执行com.sun.tools.javac.comp.Flow,这一步是进一步堆语法树进行语义分析,如消除一些无用的代码,永不真的条件判断将会去除。还有就是解除一些语法糖,如将foreach这种语法解析成标准的for循环形式。

  • 去掉无用的代码,如假的if代码块。
  • 变量的自动转换,如将int自动包装成Integer类型或者相反的操作等
  • 去除语法糖,将foreach的形式转化成更简单的for循环

代码生成器

接下来Javac会调用com.sun.tools.javac.jvm.Gen类遍历语法树生成最终的Java字节码

生成Java字节码需要经过两个步骤:

  • 将Java方法中的代码块转成符合JVM语法的命令形式,JVM的操作都是基于栈的,所有的操作都必须经过出栈和进栈来完成
  • 按照JVM的文件组织格式将字节码输出到以class为扩展名的文件中
public class Daima {
    public static void main(String[] args) {
        int rt = add(1, 2);
    }
    
    public static int add(Integer a, Integer b) {
        return a + b;
    }
}

这段代码调用一个函数,这个函数的作用就是将两个int类型的参数相加,然后将相加的结果返回给调用者。

加法表达式:必须先将两个操作数a和b放到操作栈,然后再利用加法操作符执行加法操作,将加法的结果放到当前栈的栈顶,最后将这个结果返回给调用者。

这个过程可以用如下方式描述:

  • 先计算左表达式结果,将左表达式结果转化成int类型
  • 将这个结果放入当前栈中
  • 再计算右表达式结果,将右表达式结果转化成int类型
  • 将这个结果放入当前栈中
  • 弹出‘+’操作符
  • 将操作结果置于当前栈栈顶

设计模式解析之访问者模式

访问者模式的结构

这种模式的基本想法如下:

  • 首先我们拥有一个由许多对象构成的对象结构,这些对象的类都拥有一个accept方法用来接受访问者对象
  • 访问者是一个接口,它拥有一个visit方法,这个方法对访问到的对象结构中不同类型的元素作出不同的反应
  • 在对象结构的一次访问过程中,我们遍历整个对象结构,对每一个元素都实施accept方法,在每一个元素的accept方法中回调访问者的visiti方法,从而使访问者得以处理对象结构的每一个元素。
  • 我们可以针对对象结构设计不同的实在的访问者类完成不同的操作。

  • 抽象访问者(Visitor):声明所有访问者需要的接口
  • 具体访问者(Concrete Visitor):实现抽象访问者声明的接口
  • 抽象节点元素(Element):提供一个接口能够接受访问者作为参数传递给节点元素
  • 具体节点元素(ConcreteElement):实现抽象节点元素声明的接口
  • 结构对象(Object Structure):提供一个接口能够访问到所有的节点元素,一般作为一个集合特有节点元素的引用
  • 客户端(Client):分别创建访问者和节点元素的对象,调用访问者访问变量节点元素
interface Visitor {
     void visit(Wheel wheel);
     void visit(Engine engine);
     void visit(Body body);
     void visit(Car car);
 }

 class Wheel {
     private String name;
     Wheel(String name) {
         this.name = name;
     }
     String getName() {
         return this.name;
     }
     void accept(Visitor visitor) {
         visitor.visit(this);
     }
 }
  
 class Engine {
     void accept(Visitor visitor) {
         visitor.visit(this);
     }
 }

 class Body {
     void accept(Visitor visitor) {
         visitor.visit(this);
     }
 }

 class Car {
     private Engine  engine = new Engine();
     private Body    body   = new Body();
     private Wheel[] wheels 
         = { new Wheel("front left"), new Wheel("front right"),
             new Wheel("back left") , new Wheel("back right")  };
     void accept(Visitor visitor) {
         visitor.visit(this);
         engine.accept(visitor);
         body.accept(visitor);
         for (int i = 0; i < wheels.length; ++ i)
             wheels[i].accept(visitor);
     }
 }

 class PrintVisitor implements Visitor {
     public void visit(Wheel wheel) {
         System.out.println("Visiting " + wheel.getName()
                             + " wheel");
     }
     public void visit(Engine engine) {
         System.out.println("Visiting engine");
     }
     public void visit(Body body) {
         System.out.println("Visiting body");
     }
     public void visit(Car car) {
         System.out.println("Visiting car");
     }
 }

 public class VisitorDemo {
     static public void main(String[] args) {
         Car car = new Car();
         Visitor visitor = new PrintVisitor();
         car.accept(visitor);
     }
 }
2016/4/6 posted in  深入分析Java Web技术内幕

深入分析`Java I/O`的工作机制

不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符,所以I/O操作的都是字节而不是字符。但是我们的程序中通常操作的数据都是字符形式的。

基于字节的I/O操作接口输入和输出分别是InputStreamOutputStream

写字符的I/O操作接口涉及的是write(char[] buf, int off, int len)

读字符的I/O操作是read(char[] buf, int off, int len)

字节与字符的转化接口

数据持久化或网络传输都是以字节进行的,所以必须要有字符到字节或者字节到字符的转化。

几种访问文件的方式

读取和写入文件I/O操作都调用操作系统提供的接口,因为磁盘设备是由操作系统管理的,应用程序要访问物理设备只能通过系统调用的方式来工作。

只要是系统调用就可能存在内核空间地址和用户空间地址切换的问题,这是操作系统为了保护系统本身的运行安全而将内核程序运行使用的内存空间和用户程序运行的内存空间隔离造成的。这样虽然保证了内核程序运行的安全性,但是也必然存在数据可能需要从内核空间向用户空间复制的问题

如果遇到非常耗时的操作,如磁盘I/O,数据从磁盘复制到内核空间,然后又从内核空间复制到用户空间,将会非常缓慢。这时操作系统为了加速I/O访问,在内核空间使用缓存机制,也就是将从磁盘读取的文件按照一定的组织方式进行缓存

标准访问文件方式

当应用程序调用read()接口时,操作系统检查内核的告诉缓存中有没有需要的数据。如果已经缓存了,那么就直接从缓存中返回;如果没有,从磁盘中读取,然后缓存在操作系统的缓存中。

当应用程序调用write()接口时,将数据从用户地址空间复制到内核地址空间的缓存中。这时,对用户程序来说,写操作就已经完成了,至于什么时候再写到磁盘中是有操作系统决定的,除非显式地调用sync同步命令

直接I/O方式

应用程序直接访问磁盘数据,而不经过操作系统内核数据缓冲区,这样做的目的就是减少一次从内核缓冲区到用户程序缓存的数据复制。

这种访问文件的方式通常是在对数据的缓存管理由应用程序实现的数据库管理程序中。(如数据库管理系统中,系统明确地知道应该缓存哪些数据,应该失效哪些数据,还可以对一些热点数据做预加载,提前将热点数据加载到内存,可以加速数据的访问效率;而操作系统并不知道哪些是热点数据,只是简单地缓存最近一次从磁盘读取的数据)

缺点:如果访问的数据不在应用程序缓存中,那么每次数据都会直接从磁盘加载。这种直接加载会非常缓慢

同步访问文件方式

数据的读取和写入都是同步操作的,它与标准访问文件方式不同的是,只有当数据被成功写到磁盘时才返回给应用程序成功标志

这种访问文件方式性能比较差,只有在一些对数据安全性要求比较高的场景中才会使用,而且通常这种操作方式的硬件都是定制的。

异步访问文件方式

当访问数据的线程发出请求之后,线程会接着去处理其他事情,而不是阻塞等待,当请求的数据返回后继续处理下面的操作。这种访问文件的方式可以明显地提高应用程序的效率,但是不会改变访问文件的效率。

内存映射方式

内存映射方式是指操作系统将内存中的某一块区域与磁盘中的文件关联起来,当要访问内存中一段数据时,转换为访问文件的某一段数据。这种方式的目的同样是减少数据从内核空间缓存到用户空间缓存的数据复制操作,因为这两个空间的数据是共享的。

Java访问磁盘文件

数据在磁盘中的唯一最小描述就是文件,也就是说上层应用程序只能通过文件来操作磁盘上的数据,文件也是操作系统和磁盘驱动器交互的最小单元。

Java中通常的File并不代表一个真实存在的文件对象,当你指定一个路径描述符时,它就会返回一个代表这个路径的一个虚拟对象,这个可能是一个真实存在的文件或者是一个包含多个文件的目录。

如何从磁盘读取一段文本字符:

当传入一个文件路径时,将会根据这个路径创建一个File对象来标识这个文件,然后根据这个File对象创建真正读取文件的操作对象,这时将会真正创建一个关联真实存在的磁盘文件的文件描述符FileDescriptor,通过这个对象可以直接控制这个磁盘文件。

由于我们需要读取的是字符格式,所以需要StreamDecoder类将byte解码为char格式。

Java序列化

Java序列化就是将一个对象转化成一串二进制表示的字节数组,通过保存或转移这些字节数据来达到持久化的目的。需要持久化,对象必须继承java.io.Serializable接口。

反序列化则是相反的过程,将这个字节数组再重新构造成对象。

网络I/O工作机制

TCP状态转化

1、CLOSED:起始点,在超时或者连接关闭时进入此状态
2、LISTEN:Server端在等待连接时的状态,Server端为此要调用Scok

影响网络传输的因素

将一份数据从一个地方正确地传输到另一个地方所需要的时间我们称为响应时间。影响这个响应时间的因素有很多。

  • 网络带宽
  • 传输距离
  • TCP拥塞控制

TCP传输是一个停-等-停-等协议,传输放和接受方的步调要一致,要达到这个步调一致就要通过拥塞控制来调节。TCP在传输时会设定一个窗口(BDP,Brandwidth Delay Product),这个窗口的大小是由带宽和RTTRound-Trip Time,数据在两端的来回时间,也就是响应时间)决定的。计算的公式是带宽(b/s) * RTT(s)。通过这个值可以得出理论上最优的TCP缓冲区的大小。

Java Socket的工作机制

Socket描述计算机之间完成相互通信的一种抽象功能。

打个比方,可以把Socket比作两个城市之间的交通工具,有了它,就可以在城市之间来回穿梭了、交通工具有多种,每种交通工具也有相应的交通规则。Socket也一样,也有多种。大部分情况我们使用的是基于TCP/IP的流套接字,它是一种稳定的通信协议。

主机A的应用程序要能和主机B的应用程序通信,必须通过Socket建立连接,而建立Socket连接必须由底层TCP/IP协议来建立TCP连接建立TCP连接需要底层IP协议来寻址网络中的主机。网络层使用的IP协议可以帮助我们根据IP地址来找到目标主机,但是一台主机上可能运行着多个应用程序,如何才能与指定的应用程序通信就要通过TCPUDP的地址,也就是端口号来指定了。

建立通信链路

当客户端要与服务端通信时,客户端首先要创建一个Socket实例,操作系统将为这个Socket实例分配一个没有被使用的本地端口号,并创建一个包含本地和远程地址和端口号的套接字数据结构,这个数据结构将一直保存在系统中直到这个连接关闭。

在创建Socket实例的构造函数正确返回之前,将要进行TCP的三次握手协议,TCP握手协议完成后,Socket实例对象将创建完成,否则将抛出IOException错误。

数据传输

当连接已经建立成功,服务端和客户端都会拥有一个Socket实例,每个Socket实例都有一个InputStreamOutputStream,并通过这两个对象来交换数据。

当创建Socket对象时,操作系统将会为InputStreamOutputStream分别分配一定大小的缓存区,数据的写入和读取都是通过这个缓存区完成的。

写入端将数据写到OutputStream对应的SendQ队列中,当队列填满时,数据将被转移到另一端InputStreamRecvQ队列中,如果这时RecvQ已经满了,那么OutputStreamwrite方法将会阻塞知道RecvQ队列有足够的空间容纳SendQ发送的数据。

NIO的工作方式

BIO带来的挑战

BIO即阻塞IO,不管是磁盘IO还是网络IO,数据在写入OutputStream或者从InputStream读取时都有可能会阻塞,一旦有阻塞,线程将会失去CPU的使用权。

NIO的工作机制

  • 这里的Channel可以比作某种具体的交通工具,如汽车或高铁;
  • 而Selector可以比作一个车站的车辆运行调度系统,它将负责监控每辆车的当前运行状态,是已经出站,还是在路上的。也就是它可以轮训每个Channel的状态。
  • Buffer可以比作车上的座位。信息已经封装在了Socket里面,对你是透明的。
public void selector() throws IOException {
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    Selector selector = Selector.open();
    ServerSocketChannel ssc = ServerSocketChannel.open();
    ssc.configureBlocking(false);
    ssc.socket().bind(new InetSocketAddress(8080));
    ssc.register(selector, SelectionKey.OP_ACCEPT);
    while (true) {
        Set selectedKeys = selector.selectedKeys();
        Iterator it = selectedKeys.iterator();
        while (it.hasNext()) {
            SelectionKey key = (SelectionKey) it.next();
            if ((key.readOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
                ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel();
                SocketChannel sc = ssChannel.accept();
                sc.configureBlocking(false);
                sc.register(selector, SelectionKey.OP_READ);
                it.remove();
            }
        }
    }
}
  • 调用Selector的静态工厂创建一个选择器
  • 创建一个服务端的Channel,绑定到一个Socket对象,并把这个通信信道注册到选择器上,把这个通信信道设置为非阻塞模式
  • 然后就可以调用SelectorselectedKeys方法来检查已经注册在这个选择器上的所有通信信道是否有需要的事件发生。
  • 如果有某个事件发生,将会返回所有的SelectionKey,通过这个对象Channel方法就可以取得这个通信信道对象,从而可以读取通信的数据
  • 而这里读取的数据是Buffer,这个Buffer是我们可以控制的缓冲器

  • Selector可以同时监听一组通信信道(Channel)上的IO状态,前提是这个Selector已经注册到这些通信信道中。
  • 选择器Selector可以调用select()方法检查已经注册的通信信道上IO是否已经准备好,如果没有一个信道IO状态有变化,那么select方法会阻塞等待或在超时后返回0。
  • 如果有多个信道有数据,那么将会把这些数据分配到对应的数据Buffer中。
  • 所以关键的地方是,有一个线程来处理所有连接的数据交互,每个连接的数据交互都不是阻塞方式,所以可以同时处理大量的连接请求。

Buffer的工作方式

Selector检测到通信信道IO有数据传输时,通过select()取得SocketChannel,将数据读取或写入Buffer缓冲区。

Buffer可以简单地理解为一组基本数据类型的元素列表,它通过几个变量来保存这个数据的当前位置状态,也就是有四个索引。

  • capacity:缓冲区数组的总长度
  • position:下一个要操作的数据元素的位置
  • limit:缓冲区数组中不可操作的下一个元素的位置,limit<=capacity
  • mark:用于记录当前position的前一个位置或者默认是0

我们通过ByteBuffer.allocate(11)方法创建一个11个byte的数组缓冲区,初始状态时,position为0,capactiylimit默认都是数组长度。

当我们写入5个字节时,位置变化如下:

这时,我们需要将缓冲区的5个字节数据写入Channel通信信道,所以我们调用byteBuffer.flip()方法

这时,底层操作系统就可以从缓冲区中正确读取这5个字节数据,并发送出去了。在下一次写数据之前,我们再调一下clear()方法,缓冲区的索引状态又回到初始位置。

当我们调用mark()时,它将记录当前position的前一个位置,当我们调用reset时,position将恢复mark记录下来的值。

通过Channel获取的IO数据首先要经过操作系统的Socket缓冲区再将数据复制到Buffer中,这个操作系统缓冲区就是底层的TCP协议关联的RecvQ或者SendQ队列。

从操作系统缓冲区到用户缓冲区复制数据比较耗性能,Buffer提供了另外一种直接操作操作系统缓冲区的方式,即ByteBuffer.allocateDirector(size),这个方法返回的DirectByteBuffer就是与底层存储空间关联的缓冲区,它通过Native代码操作非JVM堆的内存空间。

NIO的数据访问方式

FileChannel.transferXXX

FileChannel.transferXXX与传统的访问文件方式相比可以减少数据从内核到用户空间的复制,数据直接在内核空间中移动。

FileChannel.map

将文件按照一定大小映射为内存区域,当程序访问这个内存区域时将直接操作这个文件数据,这种方式省去了数据从内核空间向用户空间复制的损耗。这种方式适合对大文件的只读性操作,如大文件的MD5校验。

适配器模式装饰器模式的区别

  • 适配器模式的意义是要将一个接口转变成另外一个接口,它的目的是通过改变接口来达到重复使用的目的
  • 装饰器模式不是要改变被装饰对象的接口,而是要保持原有的接口,但是增强原有对象的功能,或者改变原有对象的处理方式而提升性能。
2016/3/25 posted in  深入分析Java Web技术内幕

深入Web请求过程

B/S => Browser / Server
C/S => Client / Server

如何发起一个请求

浏览器在建立Socket连接之前,必须根据地址栏里输入的URL的域名DNS解析出IP地址,再根据这个IP地址和默认80端口与远程服务器建立Socket连接,然后浏览器根据这个URL组装成一个get类型的HTTP请求头,通过outputStream.write()发送到目标服务器,服务器等待inputStream.read()返回数据,最后断开这个连接。

2016/3/25 posted in  深入分析Java Web技术内幕