数组中简单的值类型数组类型,每个数组是一个引用,引用到栈上的空间,引用类型,类类型的数组,每个数组成员任是一个引用,引用到堆上的空间,因为类的实例分配在堆上。 String和StringBuffer,StringBuilder的区别? 答:其实讨论String到底是可变还是不可变,本质上是值对象中的value[]字符数组可不可变,而不是对象的引用可不可变。关于StringBuilder和StringBuffer的效率问题,两者可谓是前世今生,StringBuilder的前世是StringBuffer,自1.5引入,StringBuilder的效率比StringBuffer稍微高,如果不考虑线程安全,StringBuilder应该是首选。JVM运行程序主要耗费在对象的创建和回收对象上。 第一次:客户端向服务器发送SYN报文(SYN一般是用来进行同步的)此时客户端进入了 SYN_SEND状态,服务器进入SYN_RECE等待确认状态。
第二次:服务器接收到SYN报文后同时向客户端发送接收确认报文,并带上ACK报文,(用于应答的)。
第三次:客户端接收到ACK报文并检查ACK报文是否正确,如果正确的话客户端再次发送ACK,服务器收到后确认验证通过,表示连接已经成功建立,可以发送数据包了。
由于TCP是全双工的,因此每个方向都必须单独进行关闭才可,这个原则是当一方完成数据发送任务后就能发送一个FIN的标志来结束这个发送通道,收到FIN后只意味着这一个方向上没有数据流动(没有需要的数据要发送了),一个TCP可以在收到一个FIN后仍然能发送数据。
第一次挥手:客户端向服务器发送FIN,用来告诉服务器你发的东西我收到了,我要关闭通道了。
第二次挥手:服务器从客户度收到FIN,并发回一个ACK报文(应答用的),确认收到客户端的消息。
第三次挥手:服务器关闭与客户端的连接,发送一个FIN给客户端。
要理解AOP我们可以对照着OOP(面向对象的思想来说) 面向对象是在万物皆是对象 利用面向对象的特性 继承 多态 抽象 封装 来构建对象,是一种自上而下的对象层次结构,但是细粒度到每个对象事务的内部,OOP就显得有些力不从心,考虑到系统的高聚集 低耦合,例如系统日志功能 权限判断 事务管理(日志)它几乎是水平散列分布在每个对象中,却与核心功能毫无关系。这样会导致了大量的代码重复,不利于各个模块的重用。
AOP思想:将通用的逻辑从代码业务逻辑中分离出来。是一种编程范式,也是一种编程思想,跟语言无关。
基于注解的方式:首先在一个类上@Aspect 代表的是一个切面类,@Pointcut 要切入的点是什么 一般用在方法上,还有@Advice 表示需要在方法的哪个时机进行切入。
例如:@pointcut("within(com.immoc.service.PointcutService)")或者@PointCut("within(com.immoc..*)")扫描这个包下面的所有子包及包下的所有方法。
答:注意要点:当“==”运算符的两个操作数都是包装器类型的引用,则是比较执行的是否是同于个对象,而如果两者比较中有一个操作数是表达式(含运算符)则比较的是数值(会自动触发拆箱的过程),
装箱:即调用包装类型valueOf(),拆箱即XXXValue()。XXX可对应到不同的包装类型。
注意:Integer,Short,Byte,Charater,Long这几个类的valueOf()实现是类似的,Double,Float的valueOf方法的实现是类似的。
答:首先我先不比较Session和Cookie的区别,先说一些关于Session的主要知识点,在服务器的集群中Session的安全和同步是最大的问题,一般是使用客户端cookie加密方式,用的较少,不推荐,另一种是Session的复制---参与集群的节点上的session状态需要同步到其他所有的节点上,只要是session的状态一经改变,session数据都要被复制到其余节点上,如果服务器集群数量过大,在session复制的过程中会消耗大量的带宽,是服务器的效率明显降低;另一种方式是session共享,将所有的session信息一台服务器进行统一的管理,
session是存放在服务器端的,cookie是存放在客户端的,Session作为两个两个设备之间的状态保持者,至少需要一方需要保持另一方的会话状态,我们可以把用户访问页面产生的session放到cookie里面,就是以cookie为中转站。你访问web服务器A,产生了session然后把它放到cookie里面,当你的请求被分配到B服务器时,服务器B先判断服务器有没有这个session,如果没有,再去看看客户端的cookie里面有没有这个session,如果也没有,说明session真的不存,如果cookie里面有,就把cookie里面的sessoin同步到服务器B,这样就可以实现session的同步了。
说明:这种方法实现起来简单,方便,也不会加大数据库的负担,但是如果客户端把cookie禁掉了的话,那么session就无从同步了,这样会给网站带来损失;cookie的安全性不高,虽然它已经加了密,但是还是可以伪造的。
由于Http是无状态的协议,但是多数需要保持应用状态的程序,需要保证客户端和服务端的交互状态一致,对于浏览器发起的请求,仅基于http协议,比如:客户端对服务器请求同一个URL,请求1次和一万次服务器是无法记住用户身份的,是无法识别出是否为同一个用户的请求,为了保持这种交互的状态,就需要采取一系列的措施:如:
Cookie,隐藏form表单域,Session,URL,Https。那就先说说Tomcat中session的用法。
第一:粘性Session,原理是把一个用户锁定在都一台服务器上,当用户第一次请求时,负载均衡器将用户的请求转发到了A服务器上,以后每次用户请求都会打在A服务器上,这就是Session的粘性原理,其现方式为在nginx的配置文件中这样配置:
server 192.168.0.1:8080 weight:1;
server 192.168.0.2:9090 weight:1;
参与集群的节点上的session状态需要同步到其他所有的节点上,只要是session的状态一经改变,session数据都要被复制到其余节点上,如果服务器集群数量过大,在session复制的过程中会消耗大量的带宽,是服务器的效率明显降低;另一种方式是session共享,将所有的session信息一台服务器进行统一的管理,
使用分布式的Redis和Memcache实现,两者必须是集群方案。也可细分为两种Session共享方式:
① 粘性Session共享: 链接地址 【实在没时间去深入理解了】
死锁检测算法:设置一张进程等待表【进程号等待资源号】和一张资源等待表【资源号和进程号】
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程进入对象的synchronized代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。
1、
互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放 。
2、
请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
3、
不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用。
4、
循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。
当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生。如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。当然这种方式需要你事先知道所有可能会用到的锁,然而总有些时候是无法预知的。
加上一个超时时间,若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。但是如果有非常多的线程同一时间去竞争同一批资源,就算有超时和回退机制,还是可能会导致这些线程重复地尝试但却始终得不到锁。
死锁检测即每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。
InnoDB是mysql一个重要的存储引擎,跟其他的存储引擎相比较,其特点是:
- 具有较好的事务支持,事务的原子性,支持四个事务隔离级别,多版本读。
- 行级锁定:通过索引实现,全表扫描仍然会表锁,注意间隙锁的影响。
- 读写阻塞与事务隔离级别相关。
- 整个表和主键以Cluster方式存储,组成一颗平衡树。
- 具有能缓存索引,也能缓存数据的性能
6. 所有的Secondary Index都会保存主键信息。 - 需要事务支持
- 行级锁对高并发有很好的适应能力,但是要确保查询是通过索引完成的。
- 数据更新较为频繁。
- 数据一致性要求较高。
- 硬件设备内存较大,可以使用InnoDB较高的缓存能力来提高内存利用率,尽量减少磁盘的IO。
- 读未提交:一个事务可以读取另一个未提交事务的数据。【read uncommited】。
- 读提交:一个事务只有能到另一个事务提交完之后才可读取该数据【read commited】。
- 重复读:一个事务在开启之时,不允许修改操作【Reattable read】。
- 序列化:事务串行执行,可避免 脏读,患读,不可重复读,是数据库事务的最高隔离级别,但是会影响数据库性能,代价过大【Serializable】。
表示同一个事务中多次读取同样的记录的结果是一致的。但在理论上,可重复读还是无法解决幻读问题,幻读(当某个事务在读取某个范围内的记录时另一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时产生幻读)
在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复 读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会 发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。 RPC 言外之意就是远程调用,也就是说两台服务器A和B,一个应用部署在A服务器上,想要调用B服务器上提供的方法,由于不在同一个内存空间内,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据,在RPC中所有的函数都有自己的一个ID,这个ID在所有的进程中是唯一的,客户端在做远程调用时必须带上这个ID,服务端和客户端都要维持这一个 函数 《---》ID的对应表名称,两者的表不一定完全相同,但是对应的方法名成和ID一定是相同的,当客户端需要调用远程的服务器上的函数时候,带上ID发起请求,服务器接到请求后也会查询相关表来确定相关的函数,确保调用的正确性。
#######################五月份更新##############################################
- 管道(pipe)以及有名管道:管道可用于有亲缘关系进程间通信,有名管道克服了管道没有名字的限制,因此具有管道的所有功能之外,它还允许无亲缘关系进程间通信。
- 信号(Signal)。
- 报文(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列和System V消息队列。
- 共享内存:使得多个进程间可访问同一块内存空间,是最快的IPC形式,往往是跟信号量结合使用,来达到进程间的同步以及互斥。
- 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
- 套接字(Socket):可用于不同机器之间的进程间通信。
通信是指线程间通过何种机制来交换信息,在命令式编程中,线程之间的通信有两种:共享内存和消息传递。
同步:同步是指程序用于控制不同的线程之间操作发生相对顺序的机制。java的并发采用的是共享内存的模型(在共享内存模型里,线程之间通过写-读内存中的公共状态来隐式通信,但是在消息传递模型里面,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信),java线程之间的通信总是隐式进行的。Java中典型的消息传递方式就是wait()和notify().
java中所有的
实例域,静态域和数组元素存储在堆内存中,堆内存在线程之间是共享的。局部变量,方法参数和异常处理参数不会再线程间共享,没有内存可见性问题。java线程之间的通信有java内存模型来控制,简称JMM,JMM决定一个线程对共享变量的写入适合对另一个线程可见,从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系,线程之间的共享变量存储在主内存中(Main memory),每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。JMM是一个抽象的事务,本身并不真实存在,涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
一个本地变量如果是原始类型,那么他完全会存储到栈区。
一个本地变量也可能是一个对象的引用,这种情况下,本地引用会存储在栈中,但是对象本身会存储在堆区。
对于一个对象的成员方法,这些方法中包含的本地变量,仍需要存储在栈区,即使所属对象在堆区。
对于一个对象的成员变量,不管他是原始类型还是包装类型,都会存储在堆区。
不管是什么内存模型,最终还是要运行在计算机的硬件上的,所以我们很有必要了解一下计算机的硬件模型,现在的计算机中一般都是有多个CPU,同时CPU会有多个核心,java在执行多线程的时,这些线程会在核心里面并行运行。在计算机的硬件内存模型中,绝大多数的数据会存储在计算机的主存中,CPU上会有一组寄存器,部分堆和栈中的数据会存储在CPU的寄存器中,CPU操作寄存器的速度要远远的高于操作主存,在CPU寄存器和主存之间还存在一层CPU Cache,CPU 的缓存分为一级缓存,二级缓存,CPU操作缓存的速度要稍微慢于寄存器,主存要比寄存器和缓存大很多。
很多回收器都是通过分代技术来进行实现的,所以我们很有必要了解一下回收器的各个生命周期所做的事情。
年轻代:该划分为三个区域,一个是原始区域(Eden)和Survivor(存活区),存活区根据功能又可划分为From和To两个区域,所有的新生对象会分配在Eden原始区,如果经过一次垃圾回收后,仍然存活的对象会存储在其中一个Survivor区,复制到另一个Survivor 区,当这个Survivor区也满的时候,从第一个区复制过来的并且仍然存活的对象会复制到年久代。绝大多数的垃圾回收是发生在年轻代。需要注意的是:两个Survivor并没有先后关系,一个Survivor可能同时来自Eden区和从前一个Survivor复制过来的对象,
而复制到年久区只有从第一个Survivor区中复制过来的对象。在年轻代发生的垃圾回收叫做Minor GC, 年久代:如果经过多次垃圾回收周期后仍然存活的对象会存放在年久代,或者刚开始分配的较大内存也可直接存储在年久代。
持久代:静态信息、存储类,方法和他们的描述信息,基本不会发生垃圾回收。
答案是:从root开始搜索,而且经过一次标记、清理后,仍然没有复活的对象。
新生代单线程收集器,标记清理都是单线程,优点是高效。
老年代单线程收集器,Serial收集器的老年代版本。
新生代收集器,可理解为Serial收集器的多线程版本,在多核环境下有着更优良的性能表现。
Parallel Scavenge收集器(停止-复制算法) 并行收集器,追求高吞吐量,高效利用CPU,吞吐量一般能到达99%。
Paralle Scavenge收集器的老年代版本,并行收集器,吞吐量优先。
CMS(Concurrent Mark Sweep)收集器(标记-清理算法) 高并发,低停顿,追求最短GC回收停顿时间,CPU占用较高,响应时间快,停顿时间短,多核CPU追求高响应时间的选择。
对象进行了分代处理,因此垃圾回收区域、时间也不一样,GC有两种类型的:Scavenge GC和Full GC。
Scavenge GC :当对象在申请Eden空间失败的时候,就会触发Scavenge GC对Eden区域进行垃圾回收,清理掉未存活的对象,并且把存活的对象移动到Survivor区,然后整理Survivor区域,这种方式是对Eden区域进行清理,是不会影响到年久代的。因为GC回收绝大多数都发生在Eden区域,频率较高,对回收的算法需要高效,速度快。
对整个堆进行垃圾回收,不管是年轻代还是持久代,速度相对要慢很多,因此尽量减少使用Full GC的次数,可能导致Full GC的情况:
- 年久代被写满;
- 持久代被写满
- System.gc()被显示调用
- 上一次GC之后Heap的各个域分配策略动态变化