借着面试留点东西

1.barrier和latch区别,barrier更多的是等待其余线程(await),latch是在等待一个事件php

2.volatile适用于可见性,可是不要求一致性的地方,适用于新值不依赖于旧值html

3.读写锁,其实用的仍是一把锁,fairSync和unfairSync,读锁是共享锁,写锁是互斥锁前端

4.threadLocal,见jdk的实现,每一个threadLocal对应的thread有不一样的threadLocalMap,利用threadLocal作key,所以不一样的线程可以拿到不一样的值(Thread.threadLocals),web应用中threadLocal用完以后要把值清除,由于web中通常都是用线程池处理用户请求,若是不及时清除,可能会有脏数据java

5.sleep和yield的区别,sleep使得当前线程进入睡眠状态,释放锁,其余线程不管优先级能够得到锁,yield是让同优先级的线程得到锁python

6.避免aba问题,通常采用版本戳mysql

7.配置线程池时CPU密集型任务能够少配置线程数,大概和机器的cpu核数至关,可使得每一个线程都在执行任务IO密集型时,大部分线程都阻塞,故须要多配置线程数,2*cpu核数linux

有界队列和无界队列的配置需区分业务场景,通常状况下配置有界队列,在一些可能会有爆发性增加的状况下使用无界队列。android

8.CopyOnWriteArrayList这个容器适用于多读少写…读写并非在同一个对象上。在写时会大面积复制数组,因此写的性能差,在写完成后将读的引用改成执行写的对象ios

9.主要利用方法去,类加载过程装载(把字节码装载到内存,jvm最终生成class对象存在堆上)、链接(验证:验证装载的字节码是否符合规范、类型是否符合要求等。解析:给静态变量分配内存。准备:把常量池中的符号引用替换为常量引用)、初始化(给静态变量赋值)c++

10。使用B(B+)树是由于高度低,查询快O(h) = logd(N), d为内部节点出度,b+数比b树好的缘由是b+的非叶子节点只保存键值 不保存具体的data,因此可以使得出度更大(d更大),

dmax = floor(pagesize / (keySize + pointSize + dataSize))

11.mysql的explain,包含type,table,possible_keys,keys,keylength(最左前缀索引的时候可能有用),rows(检索的行)extra(其余信息using where, file sort等)

12,mysql最左前缀索引,只能使用一个范围查询的索引,索引严格意义上是顺序敏感的 可是查询优化器会作一些事情,使用函数或者表达式不能使用索引,若是组合索引缺乏部分值(好比三个组合,缺乏中间那个) 会explain 的type为range  过滤

13.volatile禁止指令冲排序。volatile的读不比普通变量慢,可是写会慢一些,由于要插入许多屏障 保障处理器不会乱序执行
14.事务隔离级别

串行化(SERIALIZABLE):全部事务都一个接一个地串行执行,这样能够避免幻读(phantom reads)。对于基于锁来实现并发控制的数据库来讲,串行化要求在执行范围查询(如选取年龄在10到30之间的用户)的时候,须要获取范围锁(range lock)。若是不是基于锁实现并发控制的数据库,则检查到有违反串行操做的事务时,须要滚回该事务。

可重复读(REPEATABLE READ):全部被Select获取的数据都不能被修改,这样就能够避免一个事务先后读取数据不一致的状况。可是却没有办法控制幻读,由于这个时候其余事务不能更改所选的数据,可是能够增长数据,由于前一个事务没有范围锁。

读已提交(READ COMMITED):被读取的数据能够被其余事务修改。这样就可能致使不可重复读。也就是说,事务的读取数据的时候获取读锁,可是读完以后当即释放(不须要等到事务结束),而写锁则是事务提交以后才释放。释放读锁以后,就可能被其余事物修改数据。该等级也是SQL Server默认的隔离等级。

读未提交(READ UNCOMMITED):这是最低的隔离等级,容许其余事务看到没有提交的数据。这种等级会致使脏读(Dirty Read)。

15. get/set注入,接口注入、构造器注入

16.spring重要的类 BeanFactory, ApplicationContext, BeanFactory提供了管理bean,加载配置文件,维护bean之间的关系等。ApplcationText除了以上功能还有国际化支持、事件传递等功能

17.tcp/ip

    • TCP协议和UDP协议的区别是什么
      • TCP协议是有链接的,有链接的意思是开始传输实际数据以前TCP的客户端和服务器端必须经过三次握手创建链接,会话结束以后也要结束链接。而UDP是无链接的
      • TCP协议保证数据按序发送,按序到达,提供超时重传来保证可靠性,可是UDP不保证按序到达,甚至不保证到达,只是努力交付,即使是按序发送的序列,也不保证按序送到。
      • TCP协议所需资源多,TCP首部需20个字节(不算可选项),UDP首部字段只需8个字节。
      • TCP有流量控制和拥塞控制,UDP没有,网络拥堵不会影响发送端的发送速率
      • TCP是一对一的链接,而UDP则能够支持一对一,多对多,一对多的通讯。
      • TCP面向的是字节流的服务,UDP面向的是报文的服务。
      • TCP介绍和UDP介绍
    • 请详细介绍一下TCP协议创建链接和终止链接的过程?
      • 助于理解的一段话
      • 两幅图(来源):
        • 创建链接:三次握手
        • image
        • 关闭链接:四次挥手
        • image
    • 三次握手创建链接时,发送方再次发送确认的必要性?
      • 主要是为了防止已失效的链接请求报文段忽然又传到了B,于是产生错误。假定出现一种异常状况,即A发出的第一个链接请求报文段并无丢失,而是在某些网络结点长时间滞留了,一直延迟到链接释放之后的某个时间才到达B,原本这是一个早已失效的报文段。但B收到此失效的链接请求报文段后,就误认为是A又发出一次新的链接请求,因而就向A发出确认报文段,赞成创建链接。假定不采用三次握手,那么只要B发出确认,新的链接就创建了,这样一直等待A发来数据,B的许多资源就这样白白浪费了。
    • 四次挥手释放链接时,等待2MSL的意义?
      • 第一,为了保证A发送的最有一个ACK报文段可以到达B。这个ACK报文段有可能丢失,于是使处在LAST-ACK状态的B收不到对已发送的FIN和ACK报文段的确认。B会超时重传这个FIN和ACK报文段,而A就能在2MSL时间内收到这个重传的ACK+FIN报文段。接着A重传一次确认。
      • 第二,就是防止上面提到的已失效的链接请求报文段出如今本链接中,A在发送完最有一个ACK报文段后,再通过2MSL,就可使本链接持续的时间内所产生的全部报文段都从网络中消失。

 

      • 常见的应用中有哪些是应用TCP协议的,哪些又是应用UDP协议的,为何它们被如此设计?
        • 如下应用通常或必须用udp实现?
          • 多播的信息必定要用udp实现,由于tcp只支持一对一通讯。
          • 若是一个应用场景中大可能是简短的信息,适合用udp实现,由于udp是基于报文段的,它直接对上层应用的数据封装成报文段,而后丢在网络中,若是信息量太大,会在链路层中被分片,影响传输效率。
          • 若是一个应用场景重性能甚于重完整性和安全性,那么适合于udp,好比多媒体应用,缺一两帧不影响用户体验,可是须要流媒体到达的速度快,所以比较适合用udp
          • 若是要求快速响应,那么udp听起来比较合适
          • 若是又要利用udp的快速响应优势,又想可靠传输,那么只能考上层应用本身制定规则了。
          • 常见的使用udp的例子:ICQ,QQ的聊天模块。
        • 以qq为例的一个说明(转载自知乎

登录采用TCP协议和HTTP协议,你和好友之间发送消息,主要采用UDP协议,内网传文件采用了P2P技术。总来的说: 
1.登录过程,客户端client 采用TCP协议向服务器server发送信息,HTTP协议下载信息。登录以后,会有一个TCP链接来保持在线状态。 
2.和好友发消息,客户端client采用UDP协议,可是须要经过服务器转发。腾讯为了确保传输消息的可靠,采用上层协议来保证可靠传输。若是消息发送失败,客户端会提示消息发送失败,并可从新发送。 
3.若是是在内网里面的两个客户端传文件,QQ采用的是P2P技术,不须要服务器中转。

image

 

 

18. shell进程间共享信息,利用命名管道、全局变量(export)、写文件

19. redis高可用,sentinel、zookeeper、keepalived

1,keepalived:经过keepalived的虚拟IP,提供主从的统一访问,在主出现问题时,经过keepalived运行脚本将从提高为主,待主恢复后先同步后自动变为主,该方案的好处是主从切换后,应用程序不须要知道(由于访问的虚拟IP不变),坏处是引入keepalived增长部署复杂性; 
2,zookeeper:经过zookeeper来监控主从实例,维护最新有效的IP,应用经过zookeeper取得IP,对Redis进行访问; 
3,sentinel:经过Sentinel监控主从实例,自动进行故障恢复,该方案有个缺陷:由于主从实例地址(IP&PORT)是不一样的,当故障发生进行主从切换后,应用程序没法知道新地址,故在Jedis2.2.2中新增了对Sentinel的支持,应用经过redis.clients.jedis.JedisSentinelPool.getResource()取得的Jedis实例会及时更新到新的主实例地址。 

 

笔者所在的公司先使用了方案1一段时间后,发现keepalived在有些状况下会致使数据丢失,keepalived经过shell脚本进行主从切换,配置复杂,并且keepalived成为新的单点,后来选用了方案3,使用Redis官方解决方案;(方案2须要编写大量的监控代码,没有方案3简便,网上有人使用方案2读者可自行查看)

20.MySQL默认操做模式就是autocommit自动提交模式。这就表示除非显式地开始一个事务,不然每一个查询都被当作一个单独的事务自动执行。咱们能够经过设置autocommit的值改变是不是自动提交autocommit模式。

21.jdbc优化。

使用链接池,合理设置min值和max值。
p 尽可能使用批量处理接口:
p 批量写入(addBatch,executeBatch)
p 批量查询 (inlist,fetchsize)。
p 减小事务数,合并没必要要事务。(autocommit=false)
p 选择Preparedstatement, Preparedstatementcache。
p 存储过程和匿名块能够减小网络传输,但会给数据库带来复
杂度。
p 其它层面的sql调优,如尽可能不要使用select *等。

 

算法相关:

 

我们试着一步一步解决这个问题(注意阐述中数列有序无序的区别):

 

    1. 直接穷举,从数组中任意选取两个数,断定它们的和是否为输入的那个数字。此举复杂度为O(N^2)。很显然,咱们要寻找效率更高的解法。
    2. 题目至关于,对每一个a[i],查找sum-a[i]是否也在原始序列中,每一次要查找的时间都要花费为O(N),这样下来,最终找到两个数仍是须要O(N^2)的复杂度。那如何提升查找判断的速度呢?答案是二分查找,能够将O(N)的查找时间提升到O(logN),这样对于N个a[i],都要花logN的时间去查找相对应的sum-a[i]是否在原始序列中,总的时间复杂度已降为O(N*logN),且空间复杂度为O(1)。(若是有序,直接二分O(N*logN),若是无序,先排序后二分,复杂度一样为O(N*logN+N*logN)=O(N*logN),空间总为O(1))。
    3. 有没有更好的办法呢?我们能够依据上述思路2的思想,a[i]在序列中,若是a[i]+a[k]=sum的话,那么sum-a[i](a[k])也必然在序列中,举个例子,以下:
      原始序列:一、 二、 四、 七、十一、15     用输入数字15减一下各个数,获得对应的序列为:
      对应序列:1四、1三、十一、八、四、 0      
      第一个数组以一指针i 从数组最左端开始向右扫描,第二个数组以一指针j 从数组最右端开始向左扫描,若是下面出现了和上面同样的数,即a[*i]=a[*j],就找出这俩个数来了。如上,i,j最终在第一个,和第二个序列中找到了相同的数4和11,,因此符合条件的两个数,即为4+11=15。怎么样,两端同时查找,时间复杂度瞬间缩短到了O(N),但却同时须要O(N)的空间存储第二个数组(@飞羽:要达到O(N)的复杂度,第一个数组以一指针i 从数组最左端开始向右扫描,第二个数组以一指针j 从数组最右端开始向左扫描,首先初始i指向元素1,j指向元素0,谁指的元素小,谁先移动,因为1(i)>0(j),因此i不动,j向左移动。而后j移动到元素4发现大于元素1,故而中止移动j,开始移动i,直到i指向4,这时,i指向的元素与j指向的元素相等,故而判断4是知足条件的第一个数;而后同时移动i,j再进行判断,直到它们到达边界)。
    4. 固然,你还能够构造hash表,正如编程之美上的所述,给定一个数字,根据hash映射查找另外一个数字是否也在数组中,只需用O(1)的时间,这样的话,整体的算法通上述思路3 同样,也能降到O(N),但有个缺陷,就是构造hash额外增长了O(N)的空间,此点同上述思路 3。不过,空间换时间,仍不失为在时间要求较严格的状况下的一种好办法。
    5. 若是数组是无序的,先排序(n*logn),而后用两个指针i,j,各自指向数组的首尾两端,令i=0,j=n-1,而后i++,j--,逐次判断a[i]+a[j]?=sum,若是某一刻a[i]+a[j]>sum,则要想办法让sum的值减少,因此此刻i不动,j--,若是某一刻a[i]+a[j]<sum,则要想办法让sum的值增大,因此此刻i++,j不动。因此,数组无序的时候,时间复杂度最终为O(n*logn+n)=O(n*logn),若原数组是有序的,则不须要事先的排序,直接O(n)搞定,且空间复杂度仍是O(1),此思路是相对于上述全部思路的一种改进。(若是有序,直接两个指针两端扫描,时间O(N),若是无序,先排序后两端扫描,时间O(N*logN+N)=O(N*logN),空间始终都为O(1))。(与上述思路2相比,排序后的时间开销由以前的二分的n*logn降到了扫描的O(N))。

 

 


21.rpc和rmi

远程对象方法调用并非新概念,远程过程调用 (RPC) 已经使用不少年了。远程过程调用被设计为在应用程序间通讯的平台中立的方式,它不理会操做系统之间以及语言之间的差别。即 RPC 支持多种语言,而 RMI 只支持 Java 写的应用程序。 [1]

另外 RMI 调用远程对象方法,容许方法返回 Java 对象以及基本数据类型。而 RPC 不支持对象的概念,传送到 RPC 服务的消息由外部数据表示 (External Data Representation, XDR) 语言表示,这种语言抽象了字节序类和数据类型结构之间的差别。只有由 XDR 定义的数据类型才能被传递, RPC 不容许传递对象。能够说 RMI 是面向对象方式的 Java RPC 。

 

RMI (Remote Method Invocation)

RMI 采用stubs 和 skeletons 来进行远程对象(remote object)的通信。stub 充当远程对象的客户端代理,有着和远程对象相同的远程接口,远程对象的调用实际是经过调用该对象的客户端代理对象stub来完成的,经过该机制RMI就比如它是本地工做,采用tcp/ip协议,客户端直接调用服务端上的一些方法。优势是强类型,编译期可检查错误,缺点是只能基于JAVA语言,客户机与服务器紧耦合。

RPC(Remote Procedure Call Protocol)

RPC使用C/S方式,采用http协议,发送请求到服务器,等待服务器返回结果。这个请求包括一个参数集和一个文本集,一般造成“classname.methodname”形式。优势是跨语言跨平台,C端、S端有更大的独立性,缺点是不支持对象,没法在编译器检查错误,只能在运行期检查。

 

22. java和c通信

字节序问题:这个是通信的大问题。。前面几篇文章也转载了查阅到的一些资料。总的来讲C通常使用的是小尾存储数据,而java使用大尾存储,所谓大 尾存储就是数据高字节在前,低字节在后存储。而网络中的数据则都是大尾存储。另字符串在传输过程当中不会发生变化,而int,long等数值类型的数据会经 过根据大小尾进行存储传输。因此当java与c进行通讯的时候,java一段数据基本不用进行大小尾转化,而c收到数据后要进行NToH转化,发送数据的 时候也要进行HToN数据转化。再加上字符串,打成包传输便可。

23.innodb 多版本一致性读

 MVCC多版本一致性读
在innodb中,建立一个新事务的时候,innodb会将当前系统中的活跃事务列表(trx_sys->trx_list)建立一个副本(read view),副本中保存的是系统当前不该该被本事务看到的其余事务id列表。当用户在这个事务中要读取该行记录的时候,innodb会将该行当前的版本号与该read view进行比较。
具体的算法以下:
1. 设该行的当前事务id为trx_id_0,read view中最先的事务id为trx_id_1, 最迟的事务id为trx_id_2。
2. 若是trx_id_0< trx_id_1的话,那么代表该行记录所在的事务已经在本次新事务建立以前就提交了,因此该行记录的当前值是可见的。跳到步骤6.
3. 若是trx_id_0>trx_id_2的话,那么代表该行记录所在的事务在本次新事务建立以后才开启,因此该行记录的当前值不可见.跳到步骤5。
4. 若是trx_id_1<=trx_id_0<=trx_id_2, 那么代表该行记录所在事务在本次新事务建立的时候处于活动状态,从trx_id_1到trx_id_2进行遍历,若是trx_id_0等于他们之中的某个事务id的话,那么不可见。跳到步骤5.
5. 从该行记录的DB_ROLL_PTR指针所指向的回滚段中取出最新的undo-log的版本号,将它赋值该trx_id_0,而后跳到步骤2.
6. 将该可见行的值返回。

 

24.IOC机制模拟:读配置文件、利用反射实例化

spring中的BeanHelper作这个事情,反射建立实例,而后赋值给对应的引用

25. aop实现方案,aspectj和spring aop ,spring aop运用了代理,对目标对象动态生成代理对象,包含目标对象全部方法,可是会有回调。

26.二叉树最近公共父节点

状况一:root未知,可是每一个节点都有parent指针
此时能够分别从两个节点开始,沿着parent指针走向根节点,获得两个链表,而后求两个链表的第一个公共节点,这个方法很简单,不须要详细解释的。

两个链表相交问题

状况二:节点只有左、右指针,没有parent指针,root已知
思路:有两种状况,一是要找的这两个节点(a, b),在要遍历的节点(root)的两侧,那么这个节点就是这两个节点的最近公共父节点;
二是两个节点在同一侧,则 root->left 或者 root->right 为 NULL,另外一边返回a或者b。那么另外一边返回的就是他们的最小公共父节点。
递归有两个出口,一是没有找到a或者b,则返回NULL;二是只要碰到a或者b,就马上返回。

 

27.java集合类

集合类

    • Set
      • HashSet
        • 优势: 
            后台实现一个hash table 加速get和contains方法。后台使用数组保存 
          缺点: 
           默认大小为16, 若是超过则须要从新申请内存空间,大小为原来的两倍,并把原来的数据内容复制到 
           新的内存空间中。 
           线程不安全(需经过Collections.synchronizedList方法设置) 
           加入的元素顺序会因其内部的hash排序而改变 

          注:一般缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor能够节省空间但相应的查找时间将增大,这会影响像get和put这样的操做。

      • LinkedHashSet
        • 优势: 
            后台实现一个hash table 加速get和contains方法。后台使用链表保存 
          缺点: 
           默认大小为16, 若是超过则须要从新申请内存空间,大小为原来的两倍,并把原来的数据内容复制到 
           新的内存空间中。 
           线程不安全(需经过Collections.synchronizedList方法设置) 
           加入的元素顺序会因其内部的hash排序而改变 

          注:一般缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor能够节省空间但相应的查找时间将增大,这会影响像get和put这样的操做。

      • TreeSet
        • 优势: 
            经过一个HashMap来实现数据的保存,内部实现红黑树数据结构,使全部元素按升序保存。 
            提供高效的get和contains方法,保存操做的效率为log(n) 
          缺点: 
           默认大小为16, 若是超过则须要从新申请内存空间,大小为原来的两倍,并把原来的数据内容复制到 
           新的内存空间中(来自HashMap)。 
           线程不安全(需经过Collections.synchronizedList方法设置) 
           加入的元素升级排序而改变 

          注:treeset对元素有要求,必须实现Comparable接口或是Comparator 接口) 

          注:一般缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor能够节省空间但相应的查找时间将增大,这会影响像get和put这样的操做。

      • CopyOnWriteArraySet
        • 优势: 
           针对于对Set操做的状况有不少变化时使用,优其是在高并发的状况不想使用同步控制锁时 
          缺点: 
           消耗比较大的资料,每次做更新操做时,都会从新Copy一块内存后,再作合并操做。
    • List
      • ArrayList
        • 优势: 
             使用数组,提供快速的get,add和iterate方法,占用比较小的内存空间 
          缺点: 
             线程不安全(需经过Collections.synchronizedList方法设置) 
             insert和remove操做,很是慢(须要移动数组元素来实现) 
             当size超过期,须要新建一个较大的数据(默认大小是10,增量是 (size * 3)/2 + 1, 
             且把原来的数据都复制到新的上面)

      • LinkedList
        • 优势: 
             使用链表结构,提供快速的add, insert, remove方法,占用比较小的内存空间 
          缺点: 
             线程不安全(需经过Collections.synchronizedList方法设置) 
             get操做,很是慢(须要从head一级级遍历查找)

      • Vector
        • 优势: 
           线程安全。 
          缺点: 
           相对于ArrayList效率要低。拥有ArrayList的缺点。
      • CopyOnWriteArrayList
        • 优势: 
           针对于对List操做的状况有不少变化时使用,优其是在高并发的状况不想使用同步控制锁时 
          缺点: 
           消耗比较大的资料,每次做更新操做时,都会从新Copy一块内存后,再作合并操做。
      • TreeList(apache commons-collections)提供
        • 优势: 
           基于二叉数  提供比较快速的get, add,insert,iterate,remove方法。其中get,add和iterate方法比ArrayList稍慢一点。 
          缺点: 
           相对于ArrayList和LinkedList占比较多的内存空间 
           线程不安全(需经过Collections.synchronizedList方法设置)

    • Map
      • ConcurrentHashMap
        • 优势: 
           基于二叉数  提供比较快速的get, add,iterate方法。默认大小的16. 
           它是线程安全 
          缺点: 
           若是大小超过设定的大小时,效率会很是低。它会从新申请内存空间(原来空间的两倍),同时把原来的值复制到新内存空间上。

 

 总结
  若是涉及到堆栈,队列等操做,应该考虑用List,对于须要快速插入,删除元素,应该使用LinkedList,若是须要快速随机访问元素,应该使用ArrayList。
  若是程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,若是多个线程可能同时操做一个类,应该使用同步的类。
  要特别注意对哈希表的操做,做为key的对象要正确复写equals和hashCode方法。
  尽可能返回接口而非实际的类型,如返回List而非ArrayList,这样若是之后须要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。

 

TreeMap:内部实现是红黑树,有序的map

 

28。jvm内存模型:jvm堆、方法区、虚拟机栈、程序计数器、本地方法栈

线程隔离数据区

程序计数器(Program Counter Register):
一小块内存空间,单前线程所执行的字节码行号指示器。字节码解释器工做时,经过改变这个计数器的值来选取下一条须要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都须要依赖这个计数器来完成。

JVM虚拟机栈(Java Virtual Machine Stacks):
Java方法执行内存模型,用于存储局部变量,操做数栈,动态连接,方法出口等信息。是线程私有的。

本地方法栈(Native Method Stacks):
为JVM用到的Native方法服务,Sun HotSpot 虚拟机把本地方法栈和JVM虚拟机栈合二为一。是线程私有的。

 

线程共享的数据区

方法区(Method Area):
用于存储JVM加载的类信息、常量、静态变量、即便编译器编译后的代码等数据。
运行时常量池(Runtime Constant Pool):
是方法区的一部分,用于存放编译器生成的各类字面量和符号引用,这部份内容将在类加载后存放到方法取得运行时常量池中。具有动态性,用的比较多的就是String类的intern()方法。
JVM堆( Java Virtual Machine Heap):
存放全部对象实例的地方。
新生代,由Eden Space 和大小相同的两块Survivor组成
旧生待,存放通过屡次垃圾回收仍然存活的对象

 

 

为了支持跨平台的特性,java语言采用源代码编译成中间字节码,而后又各平台的jvm解释执行的方式。字节码采用了彻底与平台无关的方式进行描述,java只给出了字节码格式的规范,并无规定字节码最终来源是什么,它能够是除了java语言外的其余语言产生,只要是知足字节码规范的,均可以在jvm中很好的运行。正由于这个特性,极大的促进了各种语言的发展,在jvm平台上出现了不少语言,如scala,groovy等

因为字节码来源并无作限制,所以jvm必须在字节码正式使用以前,即在加载过程当中,对字节码进行检查验证,以保证字节码的可用性和安全性。

 

1. jvm运行时内存结构划分

在正式介绍以前,先看看jvm内存结构划分:

结合垃圾回收机制,将堆细化:

在加载阶段主要用到的是方法区:

方法区是可供各条线程共享的运行时内存区域。存储了每个类的结构信息,例如运行时常量池(Runtime Constant Pool)、字段和方法数据、构造函数和普通方法的字节码内容、还包括一些在类、实例、接口初始化时用到的特殊方法

若是把方法的代码看做它的“静态”部分,而把一次方法调用须要记录的临时数据看作它的“动态”部分,那么每一个方法的代码是只有一份的,存储于JVM的方法区中;每次某方法被调用,则在该调用所在的线程的的Java栈上新分配一个栈帧,用于存放临时数据,在方法返回时栈帧自动撤销。

2. 类加载过程

jvm将类加载过程分红加载,链接,初始化三个阶段,其中链接阶段又细分为验证,准备,解析三个阶段。

 

上述三个阶段整体上会保持这个顺序,可是有些特殊状况,如加载阶段与链接阶段的部份内容(一部分字节码的验证工做)是交叉进行的。再如:解析阶段能够是推迟初次访问某个类的时候,所以它可能出如今初始化阶段以后。

2.1 装载

装载阶段主要是将java字节码以二进制的方式读入到jvm内存中,而后将二进制数据流按照字节码规范解析成jvm内部的运行时数据结构。java只对字节码进行了规范,并无对内部运行时数据结构进行规定,不一样的jvm实现能够采用不一样的数据结构,这些运行时数据结构是保存在jvm的方法区中(hotspot jvm的内部数据结构定义能够参见撒迦的博文借助HotSpot SA来一窥PermGen上的对象)。当一个类的二进制解析完毕后,jvm最终会在堆上生成一个java.lang.Class类型的实例对象,经过这个对象能够访问到该类在方法区的内容。

jvm规范并无规定从二进制字节码数据应该如何产生,事实上,jvm为了支持二进制字节码数据来源的可扩展性,它提供了一个回调接口将经过一个类的全限定名来获取描述此类的二进制字节码的动做开放到jvm的外部实现,这就是咱们后面要讲到的类加载器,若是有须要,咱们彻底能够自定义一些类加载器,达到一些特殊应用场景。因为有了jvm的支持,二进制流的产生的方式能够是:

(1) 从本地文件系统中读取

(2) 从网络上加载(典型应用:java Applet)

(3) 从jar,zip,war等压缩文件中加载

(4) 经过动态将java源文件动态编译产生(jsp的动态编译)

(5) 经过程序直接生成。

 

2.2 链接

链接阶段主要是作一些加载完成以后的验证工做,和初始化以前的准备一些工做,它细分为三个阶段。

2.2.1 验证

验证是链接阶段的第一步,它主要是用于保证加载的字节码符合java语言的规范,而且不会给虚拟机带来危害。好比验证这个类是否是符合字节码的格式、变量与方法是否是有重复、数据类型是否是有效、继承与实现是否合乎标准等等。按照验证的内容不一样又能够细分为4个阶段:文件格式验证(这一步会与装载阶段交叉进行),元数据验证,字节码验证,符号引用验证(这个阶段的验证每每会与解析阶段交叉进行)。

2.2.2 准备

准备阶段主要是为类的静态变量分配内存,并设置jvm默认的初始值。对于非静态的变量,则不会为它们分配内存。

在jvm中各种型的初始值以下:

int,byte,char,long,float,double 默认初始值为0

boolean 为false(在jvm内部用int表示boolean,所以初始值为0)

reference类型为null

对于final static基本类型或者String类型,则直接采用常量值(这其实是在编译阶段就已经处理好了)。

2.2.3 解析

解析过程就是查找类的常量池中的类,字段,方法,接口的符号引用,将他们替换成直接引用的过程。

a.解析过程主要针对于常量池中的CONSTANT_Class_info,CONSTANT_Fieldref_info,CONSTANT_Methodref_info及CONSTANT_InterfaceMethodref_info四种常量。

b. jvm规范并无规定解析阶段发生的时间,只是规定了在执行anewarray,checkcast,getfield,getstatic,instanceof,invokeinterface,invokespecial,invokespecial,invokestatic,invokevirtual,multinewaary,new,putfield,putstatic这13个指令应用于符号指令时,先对它们进行解析,获取它们的直接引用.

c. jvm对于每一个加载的类都会有在内部建立一个运行时常量池(参考上面图示),在解析以前是以字符串的方式将符号引用保存在运行时常量池中,在程序运行过程当中当须要使用某个符号引用时,就会促发解析的过程,解析过程就是经过符号引用查找对应的类实体,而后用直接引用替换符号引用。因为符号引用已经被替换成直接引用,所以后面再次访问时,无需再次解析,直接返回直接引用。

2.3 初始化

初始化阶段是根据用户程序中的初始化语句为类的静态变量赋予正确的初始值。这里初始化执行逻辑最终会体如今类构造器方法<clinit>()方中。该方法由编译器在编译阶段生成,它封装了两部份内容:静态变量的初始化语句和静态语句块。

2.3.1 初始化执行时机

jvm规范明确规定了初始化执行条件,只要知足如下四个条件之一,就会执行初始化工做

(1) 经过new关键字实例化对象、读取或设置类的静态变量、调用类的静态方法(对应new,getstatic,putstatic,invokespecial这四条字节码指令)。

(2) 经过反射方式执行以上行为时。

(3) 初始化子类的时候,会触发父类的初始化。

(4) 做为程序入口直接运行时的主类。

2.3.2 初始化过程

初始化过程包括两步:

(1) 若是类存在直接父类,而且父类没有被初始化则对直接父类进行初始化。

(2) 若是类当前存在<clinit>()方法,则执行<clinit>()方法。

须要注意的是接口(interface)的初始化并不要求先初始化它的父接口。(接口不能有static块)

2.3.3 <clinit>()方法存在的条件

并非每一个类都有<clinit>()方法,以下状况下不会有<clinit>()方法:

a. 类没有静态变量也没有静态语句块

b.类中虽然定义了静态变量,可是没有给出明确的初始化语句。

c.若是类中仅包含了final static 的静态变量的初始化语句,并且初始化语句采用编译时常量表达时,也不会有<clinit>()方法。

例子:

代码:

 

[java]  view plain copy print ?
 
  1. public class ConstantExample {  
  2.   
  3.     public static final int   a = 10;  
  4.     public static final float b = a * 2.0f;  
  5. }  

编译以后用 javap -verbose ConstantExample查看字节码,显示以下:

 

 

[java]  view plain copy print ?
 
  1. {  
  2. public static final int a;  
  3.   Constant value: int 10  
  4. public static final float b;  
  5.   Constant value: float 20.0f  
  6. public ConstantExample();  
  7.   Code:  
  8.    Stack=1, Locals=1, Args_size=1  
  9.    0:   aload_0  
  10.    1:   invokespecial   #15//Method java/lang/Object."<init>":()V  
  11.    4:   return  
  12.   LineNumberTable:   
  13.    line 120  
  14.   
  15.   LocalVariableTable:   
  16.    Start  Length  Slot  Name   Signature  
  17.    0      5      0    this       LConstantExample;  
  18.   
  19. }  

这里因为编译器直接10,看成常量来处理,看到是没有<clinit>()方法存在的。能够看成常量来处理的类型包括基本类型和String类型

 

对于其余类型:

 

[java]  view plain copy print ?
 
  1. public class ConstantExample1 {  
  2.   
  3.     public static final int   a = 10;  
  4.     public static final float b = a * 2.0f;  
  5.     public static final Date  c = new Date();  
  6. }  

这里虽然c被声明成final,可是仍然会产生<clinit>()方法,以下所示:

 

[java]  view plain copy print ?
 
  1. {  
  2. public static final int a;  
  3.   Constant value: int 10  
  4. public static final float b;  
  5.   Constant value: float 20.0f  
  6. public static final java.util.Date c;  
  7.   
  8. static {};  
  9.   Code:  
  10.    Stack=2, Locals=0, Args_size=0  
  11.    0:   new #17//class java/util/Date  
  12.    3:   dup  
  13.    4:   invokespecial   #19//Method java/util/Date."<init>":()V  
  14.    7:   putstatic   #22//Field c:Ljava/util/Date;  
  15.    10:  return  
  16.   LineNumberTable:   
  17.    line 190  
  18.    line 1410  

 

2.3.4 并发性

在同一个类加载器域下,每一个类只会被初始化一次,当多个线程都须要初始化同一个类,这时只容许一个线程执行初始化工做,其余线程则等待。当初始化执行完后,该线程会通知其余等待的线程。

 

2.4 在使用过程当中类,对象在方法区和堆上的分布状态

先上代码

 

[java]  view plain copy print ?
 
  1. public class TestThread extends Thread implements Cloneable {  
  2.   
  3.     public static void main(String[] args) {  
  4.         TestThread t = new TestThread();  
  5.         t.start();  
  6.     }  
  7. }  

 

上面这代码中TestThread及相关类在jvm运行的存储和引用状况以下图所示:



其中 t 做为TestThread对象的一个引用存储在线程的栈帧空间中,Thread对象及类型数据对应的Class对象实例都存储在堆上,类型数据存储在方法区,前面讲到了,TestThread的类型数据中的符号引用在解析过程当中会被替换成直接引用,所以TestThread类型数据中会直接引用到它的父类Thread及它实现的接口Cloneable的类型数据。

在同一个类加载器空间中,对于全限定名相同的类,只会存在惟一的一份类的实例及类型数据。实际上类的实例数据和其对应的Class对象是相互引用的。

 

3. 类加载器

上面已经讲到类加载器实际上jvm在类加载过程当中的装载阶段开放给外部使用的一个回调接口,它主要实现的功能就是:将经过一个类的全限定名来获取描述此类的二进制字节码。固然类加载器的优点远不止如此,它是java安全体系的一个重要环节(java安全体系结构,后面会专门写篇文章讨论),同时经过类加载器的双亲委派原则等类加载器和class惟一性标识一个class的方式,能够给应用程序带来一些强大的功能,如hotswap。

3.1 双亲委派模型

在jvm中一个类实例的惟一性标识是类的全限定名和该类的加载器,类加载器至关于一个命名空间,将同名class进行了隔离。

从jvm的角度来讲,只存在两类加载器,一类是由c++实现的启动类加载器,是jvm的一部分,一类是由java语言实现的应用程序加载器,独立在jvm以外。

jkd中本身定义了一些类加载器:

 

(1).BootStrap ClassLoader:启动类加载器,由C++代码实现,负责加载存放在%JAVA_HOME%\lib目录中的,或者通被-Xbootclasspath参数所指定的路径中的,而且被java虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库,即便放在指定路径中也不会被加载)类库到虚拟机的内存中,启动类加载器没法被java程序直接引用。

(2).Extension ClassLoader:扩展类加载器,由sun.misc.Launcher$ExtClassLoader实现,负责加载%JAVA_HOME%\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的全部类库,开发者能够直接使用扩展类加载器。

(3).Application ClassLoader:应用程序类加载器,由sun.misc.Launcher$AppClassLoader实现,负责加载用户类路径classpath上所指定的类库,是类加载器ClassLoader中的getSystemClassLoader()方法的返回值,开发者能够直接使用应用程序类加载器,若是程序中没有自定义过类加载器,该加载器就是程序中默认的类加载器。

参考ClassLoader源代码会发现,这些Class之间并非采用继承的方式实现父子关系,而是采用组合方式。

正常状况下,每一个类加载在收到类加载请求时,会先调用父加载器进行加载,若父加载器加载失败,则子加载器进行加载。

3.2 两种主动加载方式

在java中有两种办法能够在应用程序中主动加载类:

一种是Class类的forName静态方法

 

[java]  view plain copy print ?
 
  1. public static Class<?> forName(String className)   
  2.                 throws ClassNotFoundException   
  3. //容许指定是否初始化,而且指定类的类加载器  
  4. public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException   

另外一种就是ClassLoader中的loadClass方法

 

[java]  view plain copy print ?
 
  1.  protected synchronized Class<?> loadClass(String name, boolean resolve) //第二个参数表示是否在转载完后进行链接(解析)  
  2.     throws ClassNotFoundException  
  3.   
  4. public Class<?> loadClass(String name) throws ClassNotFoundException  

 

上面这两种方式是有区别的,以下例所示

 

[java]  view plain copy print ?
 
  1. public class InitialClass {  
  2.   
  3.     public static int i;  
  4.     static {  
  5.         i = 1000;  
  6.         System.out.println("InitialClass is init");  
  7.     }  
  8.   
  9. }  



 

[java]  view plain copy print ?
 
  1. public class InitClassTest {  
  2.   
  3.     public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {  
  4.         Class classFromForName = Class.forName("com.alibaba.china.jianchi.example.InitialClass",  
  5.                                                true,  
  6.                                                new URLClassLoader(  
  7.                                                                   new URL[] { new URL(  
  8.                                                                                       "file:/home/tanfeng/workspace/springStudy/bin/") },  
  9.                                                                   InitClassTest.class.getClassLoader()));  
  10.   
  11.         Class classFromClassLoader = (new URLClassLoader(  
  12.                                                          new URL[] { new URL(  
  13.                                                                              "file:/home/tanfeng/workspace/springStudy/bin/") },  
  14.                                                          InitClassTest.class.getClassLoader())).loadClass("com.alibaba.china.jianchi.example.InitialClass");  
  15.   
  16.     }  
  17. }  

经过运行能够考到用Class.forName()方法会将装载的类初始化,而ClassLoader.loadClass()方法则不会。

咱们常常会看到在数据库操做时,会用Class.forName()的方式加载驱动类,而不是ClassLoader.loadClass()方法,为什么要这样呢?

来看看mysql的驱动类实现,能够看到在类的初始化阶段,它会将本身注册到驱动管理器中(static块)。

 

[java]  view plain copy print ?
 
  1. package com.mysql.jdbc;  
  2. public class Driver extends NonRegisteringDriver implements java.sql.Driver {  
  3.   
  4.     static {  
  5.         try {  
  6.             java.sql.DriverManager.registerDriver(new Driver());  
  7.         } catch (SQLException E) {  
  8.             throw new RuntimeException("Can't register driver!");  
  9.         }  
  10.     }  
  11.       ... ...  
  12. }  

 

3.3 自定义类加载器的应用

3.3.1 Tomcat中类加载器分析

3.3.1.1 tomcat中经过自定义一组类加载器,解决了如下几个问题:

 

(1)部署在一个服务器上的两个Web应用程序自身所使用的Java类库是相互隔离的。

(2)部署在一个服务器上的两个Web应用程序能够共享服务器提供的java共用类库。

(3)服务器尽量的保证自身安全不受部署的Web应用程序影响。

(4)支持对JSP的HotSwap功能。

3.3.1.2 tomcat的目录结构

tomcat主要根据根据java类库的共享范围,分为4组目录:

(1)common目录:能被Tomcat和全部Web应用程序共享。
(2)server目录:仅能被Tomcat使用,其余Web应用程序不可见。
(3)Shared目录:能够被全部Web应用程序共享,对Tomcat不可见。
(4)WEB-INF目录:只能被当前Web应用程序使用,对其余web应用程序不可见。

3.3.1.3 tomcat自定义类加载器

 

这几个类加载器分别对应加载/common/*、/server/*、/shared/*和 /WEB-INF/*类库, 其中Webapp类加载器和Jsp类加载器会存在多个,每一个Web应用对应一个Webapp类加载器。

CommonClassLoader加载的类能够被CatalinaClassLoader和ShareClassLoader使用;CatalinaClassLoader加载的类和ShareClassLoader加载的类相互隔离; WebappClassLoader可使用ShareClassLoader加载的类,但各个WebappClassLoader间相互隔离;JspClassLoader仅能用JSP文件编译的class文件。

 

29.防止sql注入

  严格区分权限、使用参数化语句,不要直接嵌入在sql语句中、增强验证等

 

30. 大文件交集,利用hash函数或者md5等 把url转化为不重复的整数,而后用bloomfilter过滤

 

 

31 jdk里的设计模式


 
 
 
 
 

细数JDK里的设计模式

原文出处:  javacodegeeks   译文出处:  deepinmind。欢迎加入 技术翻译小组

这也是篇老文了,相信不少人也看过。前面那些废话就不翻译了,直接切入正题吧~

结构型模式:

适配器模式:

用来把一个接口转化成另外一个接口。

  • java.util.Arrays#asList()
  • javax.swing.JTable(TableModel)
  • java.io.InputStreamReader(InputStream)
  • java.io.OutputStreamWriter(OutputStream)
  • javax.xml.bind.annotation.adapters.XmlAdapter#marshal()
  • javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal()
桥接模式:

这个模式将抽象和抽象操做的实现进行了解耦,这样使得抽象和实现能够独立地变化。

  • AWT (It provides an abstraction layer which maps onto the native OS the windowing support.)
  • JDBC

 

组合模式

使得客户端看来单个对象和对象的组合是同等的。换句话说,某个类型的方法同时也接受自身类型做为参数。

    • javax.swing.JComponent#add(Component)
    • java.awt.Container#add(Component)
    • java.util.Map#putAll(Map)
    • java.util.List#addAll(Collection)
    • java.util.Set#addAll(Collection)

 

装饰者模式:

动态的给一个对象附加额外的功能,这也是子类的一种替代方式。能够看到,在建立一个类型的时候,同时也传入同一类型的对象。这在JDK里随处可见,你会发现它无处不在,因此下面这个列表只是一小部分。

      • java.io.BufferedInputStream(InputStream)
      • java.io.DataInputStream(InputStream)
      • java.io.BufferedOutputStream(OutputStream)
      • java.util.zip.ZipOutputStream(OutputStream)
      • java.util.Collections#checkedList|Map|Set|SortedSet|SortedMap

 

门面模式:

给一组组件,接口,抽象,或者子系统提供一个简单的接口。

      • java.lang.Class
      • javax.faces.webapp.FacesServlet

 

享元模式

使用缓存来加速大量小对象的访问时间。

      • java.lang.Integer#valueOf(int)
      • java.lang.Boolean#valueOf(boolean)
      • java.lang.Byte#valueOf(byte)
      • java.lang.Character#valueOf(char)
代理模式

代理模式是用一个简单的对象来代替一个复杂的或者建立耗时的对象。

      • java.lang.reflect.Proxy
      • RMI

建立模式

抽象工厂模式

抽象工厂模式提供了一个协议来生成一系列的相关或者独立的对象,而不用指定具体对象的类型。它使得应用程序可以和使用的框架的具体实现进行解耦。这在JDK或者许多框架好比Spring中都随处可见。它们也很容易识别,一个建立新对象的方法,返回的倒是接口或者抽象类的,就是抽象工厂模式了。

      • java.util.Calendar#getInstance()
      • java.util.Arrays#asList()
      • java.util.ResourceBundle#getBundle()
      • java.sql.DriverManager#getConnection()
      • java.sql.Connection#createStatement()
      • java.sql.Statement#executeQuery()
      • java.text.NumberFormat#getInstance()
      • javax.xml.transform.TransformerFactory#newInstance()
建造模式(Builder)

定义了一个新的类来构建另外一个类的实例,以简化复杂对象的建立。建造模式一般也使用方法连接来实现。

      • java.lang.StringBuilder#append()
      • java.lang.StringBuffer#append()
      • java.sql.PreparedStatement
      • javax.swing.GroupLayout.Group#addComponent()
工厂方法

就是一个返回具体对象的方法。

      • java.lang.Proxy#newProxyInstance()
      • java.lang.Object#toString()
      • java.lang.Class#newInstance()
      • java.lang.reflect.Array#newInstance()
      • java.lang.reflect.Constructor#newInstance()
      • java.lang.Boolean#valueOf(String)
      • java.lang.Class#forName()
原型模式

使得类的实例可以生成自身的拷贝。若是建立一个对象的实例很是复杂且耗时时,就可使用这种模式,而不从新建立一个新的实例,你能够拷贝一个对象并直接修改它。

      • java.lang.Object#clone()
      • java.lang.Cloneable
单例模式

用来确保类只有一个实例。Joshua Bloch在Effetive Java中建议到,还有一种方法就是使用枚举。

      • java.lang.Runtime#getRuntime()
      • java.awt.Toolkit#getDefaultToolkit()
      • java.awt.GraphicsEnvironment#getLocalGraphicsEnvironment()
      • java.awt.Desktop#getDesktop()

行为模式

责任链模式

经过把请求从一个对象传递到链条中下一个对象的方式,直到请求被处理完毕,以实现对象间的解耦。

      • java.util.logging.Logger#log()
      • javax.servlet.Filter#doFilter()
命令模式

将操做封装到对象内,以便存储,传递和返回。

      • java.lang.Runnable
      • javax.swing.Action
解释器模式

这个模式一般定义了一个语言的语法,而后解析相应语法的语句。

      • java.util.Pattern
      • java.text.Normalizer
      • java.text.Format
迭代器模式

提供一个一致的方法来顺序访问集合中的对象,这个方法与底层的集合的具体实现无关。

      • java.util.Iterator
      • java.util.Enumeration
中介者模式

经过使用一个中间对象来进行消息分发以及减小类之间的直接依赖。

      • java.util.Timer
      • java.util.concurrent.Executor#execute()
      • java.util.concurrent.ExecutorService#submit()
      • java.lang.reflect.Method#invoke()
备忘录模式

生成对象状态的一个快照,以便对象能够恢复原始状态而不用暴露自身的内容。Date对象经过自身内部的一个long值来实现备忘录模式。

      • java.util.Date
      • java.io.Serializable
空对象模式

这个模式经过一个无心义的对象来代替没有对象这个状态。它使得你不用额外对空对象进行处理。

      • java.util.Collections#emptyList()
      • java.util.Collections#emptyMap()
      • java.util.Collections#emptySet()
观察者模式

它使得一个对象能够灵活的将消息发送给感兴趣的对象。

      • java.util.EventListener
      • javax.servlet.http.HttpSessionBindingListener
      • javax.servlet.http.HttpSessionAttributeListener
      • javax.faces.event.PhaseListener

 

状态模式

经过改变对象内部的状态,使得你能够在运行时动态改变一个对象的行为。

      • java.util.Iterator
      • javax.faces.lifecycle.LifeCycle#execute()
策略模式

使用这个模式来将一组算法封装成一系列对象。经过传递这些对象能够灵活的改变程序的功能。

      • java.util.Comparator#compare()
      • javax.servlet.http.HttpServlet
      • javax.servlet.Filter#doFilter()
模板方法模式

让子类能够重写方法的一部分,而不是整个重写,你能够控制子类须要重写那些操做。

      • java.util.Collections#sort()
      • java.io.InputStream#skip()
      • java.io.InputStream#read()
      • java.util.AbstractList#indexOf()
访问者模式

提供一个方便的可维护的方式来操做一组对象。它使得你在不改变操做的对象前提下,能够修改或者扩展对象的行为。

      • javax.lang.model.element.Element and javax.lang.model.element.ElementVisitor
      • javax.lang.model.type.TypeMirror and javax.lang.model.type.TypeVisitor

译者注:不少地方可能会存在争议,是不是某种模式其实并非特别重要,重要的是它们的设计能为改善咱们的代码提供一些经验。

相关文章
相关标签/搜索