垃圾回收算法
1.标记-清除算法
标记-清除算法是最基本的算法,和他的名字一样,分为两个步骤,一个步骤是标记需要回收的对象。在标记完成后统一回收被标记的对象。这个算法两个问题。一个是效率问题,标记和清除的效率不高。第二个问题是标记-清除之后会有大量不连续的碎片空间,如果我们需要更大的连续内存就必须GC。
2.复制算法
复制算法,不同于标记-清除,复制算法大多数用于新生代,它需要大小相等的两块内存,每次只使用一块内存,当GC的时候会把这块内存存活的对象复制到另外一块内存上面,解决了时间效率和空间碎片问题。在新生代中会把他分为三个内存一个Eden 两个Survivor默认是8比1,开始会使用一个Eden和一个Surivivor装载内存,清除时会将这两个保留的对象都保存另外在Survivor中,并且年龄加1(以后提升为老年代),如果超出了Survivor中的限制会用老年代的内存担保。
3.标记-整理算法
主要特点是,解决了碎片问题。标记整理算法,标记过程和第一算法一样,但是他处理的时候会让存活的对象向一边移动解决了空间碎片问题,用于老年代的处理。
4.分代收集算法
分代收集算法不是新思想,只是把上面的算法结合起来了。
AtomicInteger实现原理
AtomicInteger使用value来保存值,value是volatile的,保证了可见性。
对于get方法直接返回value,对于自增一或者添加值使用了CAS自旋锁,使用了一个死循环,如果cas返回为true就可以退出循环。对于CAS 全称是compare and swap比较和交换,CAS需要三个操作数,一个是变量内存地址,一个是excepted过期值,一个是现在要更新的值,我们操作的时候仅当V符合旧预期的值的时候才能更新我们新的。对于它的自增的操作,首先是卸载一个for循环里面然后获得当前的值,给这个值+1,然后进行cvs要是失败会再次Get()最新值再次写.
synchronized和lock的区别
主要有三个区别1、用法区别,性能区别,锁机制的区别。
(1)对于用法区别:synchronized可以在方法中上使用也可以在特定代码块中使用,括号中表示需要锁的对象,如果在方法上就是对该对象的锁,如果是在类的方法上就是类的锁,使用Lock必须自己用代码显示申明何时开启锁,何时关闭锁。synchronized是jvm的底层实现,而Lock是由代码执行。
(2)对于性能的区别:ReentrantLock 功能上要多于synchronized,多了锁投票,定时锁等。如果在小规模的竞争上synchronized效率比较高,如果在大规模的竞争上synchronize就比较低而Lock基本不变、
(3)锁的机制也不同:synchronized获得锁和释放锁都是在块中,都是自动释放,不会引起死锁,而Lock需要自己定位释放,不然会引起死锁。在Lock中也使用了tryLock方法用非阻塞的方式获取锁。
在lock中用一个锁变量和队列维护同步。
gc停顿原因,如何降低GC停顿
原因:gc停顿的意思就像是在整个分析期间冻结在某个时间点上,具体的原因是防止在分析的时候,对象引用关系还在不断的变化,如果没有GC停顿很有可能分析不准确。
如何降低:在Serial的老年代垃圾收集器中,会把所有线程的暂停,停下来收集哪些是死亡对象。在CMS和G1中都采取了初始标记、并发标记、短暂GC停顿重新标记,初始标记会直接记录能GC ROOTS 关联的对象,在并发标记的时候有一个线程来标记,这个时候对象的发生的变化都会记录下来,在重新标记的时候会修正,这样就会降低GC停顿时间
jvm如何调优,参数怎么调?如何利用工具分析jvm状态
?
合理的分配内存,分配栈和堆的内存,在堆中我们还可以详细划分新生代和老年代的内存比例,在新生代中我们也可以划分Eden和Surivior的内存比例(调该比例大小),合理的划分内存区域大小,可以帮助我们jvm调优,我们采取合适的垃圾回收器,比如在新生代启用serial垃圾回收器,在老年代采用cms并发标记,可以降低GC停顿,当然也可以尝试去采用G1垃圾回收器
jvm中类加载过程
类加载到类被卸载过程包括7个阶段
1.加载 通过类的全限定名把类文件的二进制流加入进来,通过这个字节流(这个二进制流也是我们代理类的方法),然后通过这个二进制流把静态存储结构转化为运行时方法区的结构(不包括类变量,类变量在准备阶段),在内 存中生成一个Class对象,作为方法区访问的入口。
2.验证 验证是验证Class文件的字节流包含的信息是否符合当前虚拟机的要求规范,防止恶意攻击、
3.准备 在方法区为类变量分配内存和设置初始值,这个时候的初始值是数据的0值,不是我们定义的值,如果是常量的话准备阶段就会设置为我们定义的值
4.解析 将符号引用(这里的符号引用指的是字面量的形式,只需要无歧义地定位到目标)替换为直接变量
5.初始化 类初始化 阶段是我们加载过程的最后一步,执行类构造器,合并static语句,有static的顺序决定。
6.使用
7.卸载
Spring中bean的加载机制,bean生成具体步骤
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
spring中Bean的加载机制其实就是IOC容器的初始化,比如我们这里定义了一个IOC容器,BeanFactroy的子类ClassXMLPathApplicationContext,在他的构造函数中我们会把xml路径写进去这个步骤就是定位,接下来就是BeanDefiniton的载入,在构造函数当中有一个refresh()的函数,这个就是载入BeanDefinition的接口,这个方法进去之后是一个同步代码代码块,把之前的容器销毁和关闭创建了一个BeanFatroy,就像对我们的容器重新启动一样,然后我们对BeanDefiniton载入和解析解析完毕之后会把beanDefinition和beanName放入BeanFactory的HashMap中维护。在这里Bean已经被创建完成,然后我们就像IOC容器索要Bean,如果是第一次索要会触发依赖注入,会递归的调用gebBean实现依赖出入.
讲下java锁的原理
对于synchronized关键字,在jvm中在编译的时候在同步块的前后形成监视进入和监视退出两个字节码,这两个字节码都需要一个引用类型的参数来指定要锁定和解锁的对象,如果指定了的话就使用指定的对象,如果没有指定看是类方法还是对象方法来决定,在执行监视进入的指令的时候,会判断能否进入,进入成功之后会把锁计数器加1,如果不成功就会继续等待和其他的线程竞争,出锁的时候会把锁计数器减1变为0,也就是释放了锁。在这里要说明一点java的线程时映射到系统原生线程之上,如果要阻塞或者唤醒一个线程都需要操作系统帮忙,这就需要从用户态转换到核心态中,因此状态转换需要耗费很多的处理器时间。有可能比用户的代码执行时间还长。在jdk1.6之后对synchronized优化是非常的号的,比如锁粗化,锁自旋,锁消除。轻量级锁和偏向锁。
而对于ReentrantLock是代码上的实现
线程和进程的区别
进程是一段正在执行的程序,线程也叫作”轻量级进程“,他是程序执行的最小单元,一个进程可以有多个线程,各个线程之间共享程序的内存空间(比如说堆空间)及一些进程级的资源,进程和进程之间不能共享内存只能共享磁盘文件,线程也有4中状态:就绪,运行,挂起,死亡。
(新版本)进程是程序执行时的一个实例,从内核的观点看,进程的目的就是担当分配系统资源的基本单位。
线程是进程的一个执行流,是cpu调度和分配的基本单位,它是比进程更小的能独立运行的基本单位。一个进程由几个线程组成,线程和同属一个进程的其他的线程共享进程所拥有的全部资源。
进程-资源分配的最小单位。线程-程序执行的最小单位。
进程由独立的空间地址,线程没有单独的地址空间 同一进程内的共享进程的地址空间,只有自己独立的堆栈和局部变量。对于我们来说实现一个多线程的任务比实现一个多进程的任务好,
为什么分配线程比分配一般的对象更需要花费更大的代价?
首先他的资源非常“节俭”。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段,堆栈段和数据段,这是一种“昂贵”的多任务工作方式。而运行一个进程中的多个进程,他们彼此之间使用相同的地址空间,共享进程内的大部分数据,启动一个进程的时间远大于一个线程的时间。
(1)地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间;
(2)资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
(3)线程是处理器调度的基本单位,但进程不是.
Spring AOP是怎么实现
首先简单说一下Spring AOP几个比较重要的名词:
通知:定义在连接点做什么,为切面增强提供织入接口,意思就是说增强的内容以及增强的方式
切点:PointCut:决定通知应用于什么方法上或者类上。(点:方法或者类)
通知器:连接切点和通知结合起来,可以让我们知道那个通知应用于哪个结点之上,这个结合为应用使用Ioc容器配置AoP应用
开始我们使用AOP的时候是用得java编写的切面通知使用XML配置,后面我们摒弃了采用@AspectJ注解对其进行标注
然后AOP的实现原理有一个ProxyFactoryBean(代理工厂),这个ProxyFactoryBea是在Spring Ioc环境之中,创建AOP应用的最底层。在ProxyFactoryBean中我们会配置好通知器Advisor,在ProxyFactory需要为目标对象生成代理对象。ProxyFactory有一个getObject方法,在我们IOC容器中如果获取这个bean会自动调用这个方法,首先第一步初始化通知器链,通知器链只会初始化一次,使用标志位判断,遍历通知器,把所有通知器加入拦截器链,接下来就是代理对象的生成,利用目标对象以及拦截器我们可以正确的生成代理对象,这里生成代理对象有两种方法一种是jdk一种是cglib,在得到AopProxy代理对象之后,我们首先会根据配置来对拦截器是否与当前的调用方法想匹配,如果当前方法匹配就会发挥作用,他会遍历Proxy代理对象中设置拦截器链的所有拦截器,拦截器调用完成之后才是目标对象的调用,这个时候会有一个注册机制,在拦截器中运行的拦截器,会注册,我们就不需要再判断。Aop的源代码中也大量使用了IOC容器,比如从IOC中找到通知器。
SpringMVC的主要流程
首先我们会在web.xml中配置DispathcerServlet,这个就是SpringMVC的入口,DispathcerServlet的父类FrameWorkServlet首先会初始化WebApplicationContext,DispacherServlet初始化了9个组件,初始完毕后我们开始进入,FramkeworkServlet中做了三件事一个是调用doService模板方法具体处理请求。将当前所有的请求都合并在一个方法里面和我们的httpServlet做法有点不同,在DispathcerServlet中有一个doService的方法,其中调用了doDispatch这也是最核心的首先根据request找到Handler,根据Handler找到了HderAdapter,用HandlerAdapter处理Handler其中包含一些参数的处理,处理完成后就行方法调用之后得到结果然后把View渲染给用户或者把数据发给用户。
详细版本:
1.输入一个网址,比如http请求,首先我们tomcat服务器,会对请求创建出我们request和response,然后就交给我们对应的servlet处理。
创建线程方式
实现runnable接口重写run方法,继承Thread,利用线程池来创建。
想让所有线程都等到一个时刻同时执行有哪些方法
CountDownLatch:CountDownLatch首先我们在构造函数当中传入了一个标志值,然后在需要阻塞的地方调用await(),直到其他线程把countDown减少完。这个是不可重用的。
CyclicBarrier:和他的名字一样栅栏,我们对他的构造函数传入一个栅栏值,在需要阻塞的地方调用await的时候我们就对其基础值加一,直到等于栅栏值。调用CylicBarrier的reset方法可以对他进行重置。
Semaphore信号量:Semaphore可以同时控制访问的线程个数,如果需要这个资源我们会acquire()以阻塞的方式去请求,如果没有可用的信号量,就等待,release释放信号量,这个机制有点类似于锁。
在jdk1.7中提供了一个同步器Phaser,作用和countdownLatch,CyclicBarrier类似,但PHaser的使用方式更为灵活。使用register注册方法递增计数器,使用arriveAndDeregister()方法来递减计数器,使用arriveAndAwaitAdvane()方法阻塞线程,当计数器归0唤醒。
volatile的作用
参看博客:http://blog.csdn.net/libing13820393394/article/details/48582999
第一:volatile是Java虚拟机提供的最轻量级的同步机制,使变量对所有的线程可见,保证了可见性,但是并不能保证它的原子性。
第二个:禁止指令重排序优化。普通变量仅仅保证在该方法所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量赋值操作的顺序与程序代码中的执行一样。从硬件的方面来说,并不是指令任意重拍,他只是把多条指令不安程序规定顺序分发给电路处理单元,比如说23+5 23之间是有依赖,5就可以排到他们前面。volatile会帮助我们加入内存屏障防止重排序。volatile读操作性能消耗与普通变量几乎没区别,写操作会慢一些,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。
注意:对于volatile修饰的变量,jvm只是保证从主内存加载到线程的工作的内存是最新的
谈一谈java内存模型
(1)java虚拟机规范试图定义一种JAVA内存模型来屏蔽掉各种硬件和操作系统的内存访问的差异。
(2)java内存模型的主要目标是定义程序中各个变量的访问规则,这里的变量不包含局部变量和方法参数,而是指的是实例字段、静态字段、和构成数组对象的元素。
(3)java内存模型规定了所有的变量都存储在主内存中,而线程内的局部变量在自己的工作内存中,并且还有被该线程使用到的变量的主内存 的副本拷贝,线程对变量的操作(读取、赋值)都在工作内存中进行,不能直接读写主内存的变量,不同的线程无法直接访问对方工作内存的变量,线程键的变量值的传递需要通过主内存来完成,在内存模型中比较重要的就是工作线程和主内存的交互。
内存之间的交互:
java内存模型定义的操作:
Lock (锁定)
Unlock(解锁)
Read(读取)
Load(载入)
Use(使用)
Assign(赋值)
Store(存储)
Write(写入)
变量从主内存到工作内存:按照顺序执行read load操作
变量从工作内存到主内存:按照顺序执行Store write操作
重排序:
包括:编译器优化重排序、指令级并行重排序、内存系统重排序
什么时候使用LinkedList?
首先分写LinkedList和ArrayList的不同,在经常插入和删除的时候,在实现栈和队列的时候,不适合随机查找元素。
Object有哪些方法(九大方法),clone是深复制还是浅复制,finalize一般在什么时候使用:
wait,notify,notifyall,clone,getclass,toString,equals,hashcode,finalize。
1、Clone()方法
private保护方法,实现对象的浅复制,只有类实现了Clonable接口才可以调用该方法,否则抛出CloneNotSupportException。clone是浅复制,复制完成后其中的变量引用还是和以前的一样,如果要实现深复制需要我们把所有的变量引用都递归复制一次,然后再赋值。(或者额使用序列化,也可以实现深拷贝)如果我们要自己实现clone()方法必须要实现克隆接口clonable。
2、Equals()方法
在object中与==是一样的,子类一般需要重写该方法
3、hashCode()方法
该方法用于哈希查找,重写了equals方法一般都要重写hashcode方法,这个方法在一些具有哈希功能的collection中使用
4、getClass()方法
final方法,获得运行时的类型
5、Wait()方法
使得当前的线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。Wait方法会一直等待,直到获得锁(到了睡眠的时间间隔也会唤醒自己)或者被中断掉。
调用该方法,当前的线程会进入到睡眠的状态,直到调用该对象的notify方法、notifyAll方法、调用interrupt中断该线程,时间间隔到了。
6、Notify()方法
唤醒在该对象上的等待的某个线程
7、notifyAll()方法
唤醒在该对象上的等待到所有的线程
8、toString()方法
把对象转换成string类型进行输出
9、finalize()方法
finalize在我们垃圾回收器回收这个对象的时候工作,可以做一些后续的工作,即进行一些必要的清理和清除的工作,比如说关闭流。当然我们也可以在这个里面对我们即将被回收的对象逃出回收。这里需要注意的是系统只会调用一次finalize()方法。但是一般我们不推荐使用这个方法,因为这个方法是为了对开始C和C++程序员的一种妥协,因为C中有析构函数,这个方法运行代价高,不确定大,我们还是会推荐使用try{}finally,他做的方法try{}finally都可以做。
如何管理线程(主要介绍各种线程池的实现)
使用线程池来管理线程
在Java中实现多种线程池
我们使用executors工厂产生我们的线程池,当线程池达到负载的时候会在我们线程池管理的Runnable阻塞队列中等待,不会像线程那样竞争CPU
第一种 newFixedThreadPool,和它的名字一样这是一个固定线程池,我们可以设置基本大小也就是我们没有任何任务执行的时候的大小,最大大小,只有在工作队列满了才能达到最大大小。
第二种 newCachedThreadPool这种线程池把大小设置为Integer.MAX_VALUE,基本大小设置为0,空闲超时设置1分钟,这种线程池可以无限扩展,并且当需求降低时会自动收缩。
第三种 newSingleThreadPool这种线程池把基本大小设置为1,最大大小都设置为1,只允许同一时刻一个线程。
固定线程池和单线程池固定默认使用的是阻塞队列无界的LinkedBlockingQueue,在这个阻塞队列中可以无限增长。但是对于我们的newCachedThreadPool来说他的线程池是无限大的,不需要阻塞等待,我们这里使用的是SynchronousQueue来避免排队,其实这个东西不是一个队列,是直接在线程之间进行移交,当线程池的大小小于所需要的时候,要么创建一个要么拒绝一个。我们一般在使用的时候可以扩展,使用使用信号量来控制提交速率。
如何让线程A等待线程B结束
1.使用join方法可以等待A线程结束,或者单线程池中 阻塞队列的方式让A先获得单线程池的线程,然后B一直阻塞,知道A释放自己的线程。
如何优化jvm参数
,首先设置堆的大小,一般设置xmx和xms大小相同,如果老年代容易溢出可以扩充老年代,也要适当的调整永久代大小,选择自己合适的收集器,调整新生代对象年龄阀值等。
什么是守护线程
线程会分为两种:
普通线程和守护线程。在JVM启动时创建的所有线程中,除了主线程其他都是守护线程,比如说垃圾回收器就是守护线程,当普通线程全部退出的时候守护线程也会退出,我们自己也可以手动设置手动线程在线程启动之前,但是我们应该尽可能少使用守护线程,因为我们很少有操作可以在不进行清理就可以安全地抛弃,比如说I/O操作。
TCP如何控制拥塞
拥塞控制就是防止过多的数据注入网络中,这样可以使网络中的路由器或链路不致过载。
发送方维持一个叫做拥塞窗口cwnd(congestion window)的状态变量。
为了防止cwnd增长过大引起网络拥塞,还需设置一个慢开始门限ssthresh状态变量。ssthresh的用法如下:
当cwnd<ssthresh时,使用慢开始算法。也就是乘法算法
当cwnd>ssthresh时,改用拥塞避免算法。也就是加法算法
当cwnd=ssthresh时,慢开始与拥塞避免算法任意。
当出现拥塞的时候就把心的门限值设为此时窗口大小的一般,窗口大小设置为1,再重新执行上面的步骤。
当收到连续三个重传的时候这就需要快重传和快恢复了,当收到连续三个重传 这个时候发送方就要重传自己的信息,然后门限减半但是这个时候并不是网络阻塞,窗口只会减半执行拥塞避免算法。
ThreadLoacl?
我们使用ThreadLocal为每个使用该类型的变量提供了一个独立的副本,具体的实现是在每个线程中保存了一个ThreadLocalMap,这个ThreadLoaclMap会在我们第一次使用ThreadLoal中的set方法创建出来,set方法就是保存在ThreadLocalMap中,该变量为key,值为value,get方法也从这个HashMap中找。
OSI网络模型?
网卡在哪一层(物理层)
交换机在哪一层(链路层)
路由器在哪一层(网络层)
传输TCP
会话 SQL
表示 IMG
html在应用层
HTTP1.0和Http1.1区别?
1.0默认是多次tcp连接多次请求,然后增加了keep alive功能,但是必须在request Header手动增加Connection:keepalive
1.1是一次tcp连接多次请求,新的persistence功能
POST和GET方法的区别?
长的说:
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
也就是说,GET只需要汽车跑一趟就把货送到了,而POST得跑两趟,第一趟,先去和服务器打个招呼“嗨,我等下要送一批货来,你们打开门迎接我”,然后再回头把货送过去。
因为POST需要两步,时间上消耗的要多一点,看起来GET比POST更有效。因此Yahoo团队有推荐用GET替换POST来优化网站性能。但这是一个坑!跳入需谨慎。为什么?
-
GET与POST都有自己的语义,不能随便混用。
-
据研究,在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。
-
并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。
1.get是从服务器上获取数据,post是向服务器传送数据。
2.get是通过URL来传递数据,POST是通过表单传递,因此get数据限制在1024k,而POST没有限制
3.在java服务器端get是通过request.qureySting post通过request.getParameterNames和reque.getParameterValue
4.get是安全的,幂等的 POST即不安全又不幂等(多次操作和一次操作一样)
在rest中设计的话,一般get用来查询数据,POST用来添加数据,PUT用来更新数据,Delete用来删除数据
filter执行顺序?
多个filter的执行顺序是web.xml中的配置顺序
影响SQL执行效率的因素?
1.is null和is not null
2.通配符的like
3.order by
4.not
5.in和exists
GBK和UTF-8的区别
GBK包含全部中文字符; UTF-8则包含全世界所有国家需要用到的字符。
GBK的文字编码是双字节来表示的,即不论中、英文字符均使用双字节来表示,只不过为区分中文,将其最高位都定成1。
至于UTF-8编码则是用以解决国际上字符的一种多字节编码,它对英文使用8位(即一个字节),中文使用24位(三个字节)来编码。对于英文字符较多的论坛则用UTF-8节省空间。
UTF8是国际编码,它的通用性比较好,外国人也可以浏览论坛 GBK是国家编码,通用性比UTF8差,不过UTF8占用的数据库比GBK大~
stringBuffer和StringBuilder组
1.三者在执行速度方面的比较:StringBuilder > StringBuffer > String
看servlet和Filter的实现原理
StringBuffer是线程安全的,St不是线程安全的,内部的实现是使用char数
什么是rest
一次网站访问的全过程:
楼主提到TCP/IP分层的时候用的是网络接口层,那么楼主的TCP/IP分层概念应该是:应用层、传输层、网络层、网络接口层(包含了七层模型中的数据链路层和物理层)。
我尝试回答一下楼主的问题,希望大家继续拍砖,如果访问www.163.COM这个网站,那么他的主要过程应该是:
一、主机向DNS服务器发起域名解析请求,以得到相对应的IP地址
二、应用层应用HTTP协议发送数据
三、数据到达传输层封装成数据段,主机使用1024以后的随机源端口号,目标端口号为80
四、数据段到达网络层封装成数据包,加入主机源IP地址和目标IP地址
五、数据包到达网络接口层首先封装成数据帧,加入源MAC地址和目标MAC地址(注:此目标MAC地址为本地网关的MAC地址,源和目的MAC地址在数据转发的过程中,会由路由器不断的改变)。封装后将数据转换为物理层的数据流,通过互联网发送至目标服务器。
什么时候抛出InvalidMonitorStateException异常?为什么?
调用 wait ()/notify ()/notifyAll ()中的任何一个方法时,如果当前线程没有获得该对象的锁,
那么就会抛出 IllegalMonitorStateException 的异常
也就是说程序在没有执行对象的任何同步块或者同步方法时,
仍然尝试调用 wait ()/notify ()/notifyAll ()时。由于该异常是 RuntimeExcpetion 的子类,
所以该异常不一定要捕获(尽管你可以捕获只要你愿意
作为 RuntimeException,此类异常不会在 wait (),notify (),notifyAll ()的方法签名提及。
Collections.synchronizedXX 方法的原理
返回了一个同步容器,在这个同步容器中的所有方法都有一个锁为当前对象或者指定锁的同步块,用这种阻塞同步的方法可以让我们容器同步
什么是Future
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
1.cancel方法用来取消任务
2.isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
3.isDone()表示是否完成
4.get()获得执行结果,这个方法会一直阻塞
5.在时间范围内获取执行结果
FutureTask是Future的实现类
说出数据连接池的工作机制是什么?
J2EE服务器启动时会建立一定数量的池连接,并一直维持不少于此数目的池连接。
调用:客户端程序需要连接时,池驱动程序会返回一个未使用的池连接并将其表记为 忙。如果当前没有空闲连接,池驱动程序就新建一定数量的连接,新建连接的数量有配置参数决定。
释放:当使用的池连接调用完成后,池驱动程序将此连接表记为空闲, 其他调用就可以使用这个连接。
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被 使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超 过最大连接数量时,这些请求将被加入到等待队列中。
数据库连接池的最小连接数和最大连接数的设置要考虑到下列几个因素:
-
最小连接数是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费;
-
最大连接数是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求将被加入到等待队列中,这会影响之后的数据库操作。
-
如果最小连接数与最大连接数相差太大,那么最先的连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接。不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,它将被放到连接池中等待重复使用或是空闲超时后被释放。
存储过程和函数的区别
存储过程是用户定义的一系列sql语句的集合,涉及特定表或其它对象的任务,用户可以调用存储过程,而函数通常是数据库已定义的方法,它接收参数并返回某种类型的值并且不涉及特定用户表。
事务是什么?
事务是作为一个逻辑单元执行的一系列操作。
游标的作用?如何知道游标已经到了最后?
游标用于定位结果集的行,通过判断全局变量@@FETCH_STATUS可以判断是否到了最后,通常此变量不等于0表示出错或到了最后。
系统进程间通信的方式
管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
命名管道 (named pipe) : 命名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
jvm调优:内存溢出和内存泄露:
溢出解决:
1.在代码中减少不必要的实例构造
2.设置堆和永久代的大小 -xms堆最小 -xmx堆最大
内存泄露:
内存泄露不能通过配置解决代码的问题。比如资源在使用完毕后没有释放,一些对象存在无效引用我们不能回收。
http和https的区别
http协议是无状态的明文传输,Https而SSL+HTTP协议构建的可进行加密传输。https的服务器必须向CA申请一个证明服务器用途的证书,而客户端通过该证书确认服务器,所以银行都是https,所有的通讯都是在密钥加密的情况下,而密钥则是通过证书交换,所以第三方拦截的数据没有密钥也没有用。
SSL用以保障在Internet上数据传输之安全,利用数据加密(Encryption)技术,可确保数据在网络上之传输过程中不会被截取及窃听。
虚拟机性能监控状况
jps:显示系统内所有进程的信息。
jstat:收集虚拟机各种运行状态信息的命令行工具。 -gc,监视java堆 -class 就是监视类加载,还可以监视编译状况。
jinfo:java配置信息工具。
jmap:用于生成堆转储快照 有些选项只能在linux下才能看见。
jhat:配合jmap。
jstack:堆栈追踪。
Servlet生命周期
1.加载:在Servlet容器启动的时候会通过类加载器加载我们的Servlet的Class文件。
2.创建:在创建过程的时候如果没有在web.xml文件中使用load-on-startup我们在第一次访问我们的Servlet的时候会初始化实例,如果配置了这个并且大于1会在容器启动的时候就创建。
3.初始化:init()初始化的方法只会被调用一次。在我们实例化被创建之后就会执行初始化。
4.处理客户请求:service()在Servlet的service方法中会根据不同的http方法来调用。
5.卸载:destroy()当我们Servlet需要被卸载的时候就会调用我们的destory()方法,随后垃圾回收器会回收。
Minor GC和FULL GC
当我们需要向新生代中分配内存时出现不足的情况:会出现Minor GC,在新生代中 都是朝生夕灭的对象,频率比较高。Minor GC发生在新生代。
FULL GC指在老年代发生的GC,一般情况下出现了FULL GC都会伴随着一次Minor GC。
为什么MinorGC 和FULL GC速度有差距呢?
在Minor GC中使用的copy算法,在FullGC中使用的是标记清除 或者 标记整理算法。
copy算法是用空间换时间 mark(标记)和copy(复制)是一个动作。
但是mark-sweap或mark-compact都是分为两个阶段,先标记再清除是两步骤。
所以Minro GC速度会快于FullGC。
JVM调优问题
对于JVM调优的重点是垃圾回收和内存管理。
垃圾回收我们可以使用我们的cms垃圾回收器。
对于内存管理有:
永久代溢出、栈溢出、堆溢出的情况
永久代溢出:
针对永久代溢出在JVM默认情况只有64M的永久代大小,很多东西都需要我们永久代区内存,尤其是使用Spring等框架的时候会有cglib的动态字节码生成class,都会存储在我们的永久代。所以我们需要扩充永久代防止内存溢出。
堆溢出:
对于堆溢出我们也是比较常见的比如说我们使用容器的时候没有释放内存很有可能就会导致堆溢出,需要动态扩展。
栈溢出:
对于栈我们也可以设置提高。
单例模式
(1)恶汉式的单例模式
利用静态static的方式进行实例化,在类被加载时就会创建实例。
/**
- 饿汉式实现单例模式
*/
public class Singleton {
private static Singleton instance = new Singleton();//在类加载时实例单例对象
private Singleton() {//私有的构造器
}
public static Singleton getInstance() {//返回一个实例对象
return instance;
}
}
(2)懒汉式实现单例模式
在被第一次引用时才去创建对象。
/**
* 懒汉式实现单例模式
*/
public class Singleton {
private static Singleton instance;//创建私有的静态变量
private Singleton() {//私有的构造函数
}
// synchronized方法,多线程情况下保证单例对象唯一
public static synchronized Singleton getInstance() {
//如果实例对象为空,就重新去实例化
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
分析:这中方法的实现,效率不高,因为该方法定义为同步的方法。
(3)双重锁实现的单例模式double check
/**
* DCL实现单例模式
*/
public class Singleton {
private static valotile Singleton instance = null;//这里要加入valotile关键字,避免指令重排序, 可能先赋值但是没有分配内存
private Singleton() {
}
public static Singleton getInstance() {
// 两层判空,第一层是为了避免不必要的同步
// 第二层是为了在null的情况下创建实例
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
分析:资源的利用率较高,在需要的时候去初始化实例,而且可以保证线程的安全,该方法没有去进行同步锁,效率比较好。
(4)静态内部类实现单例模式
/**
* 静态内部类实现单例模式
*/
public class Singleton {
private Singleton() {
}
//返回实例的方法
public static Singleton getInstance() {
return SingletonHolder.instance;
}
/**
* 静态内部类
*/
private static class SingletonHolder {
//静态私有的实例对象
private static Singleton instance = new Singleton();
}
}
分析:第一次加载类时不会去初始化instance,只有第一次调用getInstance()方法时,虚拟机才会加载内部类,初始化instance
可以保证线程的安全,单例对象的唯一,延迟了单例的初始化。
(5)枚举单例
/**
* 枚举实现单例模式
*/
public enum SingletonEnum {
INSTANCE;//直接定义一个实例对象
public void doSomething() {
System.out.println("do something");
}
}
分析:枚举实例的创建是线程安全的,即使反序列化也不会生成新的实例,在任何的情况下都是单例的。
设计模式6大原则
1.单一职责。2.里氏替换 3.依赖导致4接口隔离5.迪米特法则6.开闭原则。
XML和JSON优缺点
XML的优点
A.格式统一,符合标准;
B.容易与其他系统进行远程交互,数据共享比较方便。
C.可读性高
XML的缺点
A.XML文件庞大,文件格式复杂,传输占带宽;
B.服务器端和客户端都需要花费大量代码来解析XML,导致服务器端和客户端代码变得异常复杂且不易维护;
C.客户端不同浏览器之间解析XML的方式不一致,需要重复编写很多代码;
D.服务器端和客户端解析XML花费较多的资源和时间。
JSON的优缺点
JSON的优点:
A.数据格式比较简单,易于读写,格式都是压缩的,占用带宽小;
B.易于解析,客户端JavaScript可以简单的通过eval()进行JSON数据的读取;
C.支持多种语言,包括ActionScript, C, C#, ColdFusion, Java, JavaScript, Perl, PHP, Python, Ruby等服务器端语言,便于服务器端的解析;
D.在PHP世界,已经有PHP-JSON和JSON-PHP出现了,偏于PHP序列化后的程序直接调用,PHP服务器端的对象、数组等能直接生成JSON格式,便于客户端的访问提取;
E.因为JSON格式能直接为服务器端代码使用,大大简化了服务器端和客户端的代码开发量,且完成任务不变,并且易于维护。
JSON的缺点
A.没有XML格式这么推广的深入人心和喜用广泛,没有XML那么通用性;
B.JSON格式目前在Web Service中推广还属于初级阶段。
C:可读性低。
四种读取XML文件读取的办法
1.DOM生成和解析XML文档
为XML文档的已解析版本定义了一组接口。解析器读入整个文档,然后构建一个驻留内存的树结构。
优点:整个文档树在内存中,便于操作;支持删除,修改,重新排列等。
缺点: 把整个文档调入内存,存在很多无用的节点,浪费了时间和空间。
2.SAX为解决DOM
1、边读边解析,应用于大型XML文档
2、只支持读
3、访问效率低
4、顺序访问
3.DOM4J生成和解析XML文档(解析工具) 性能最好 SUM的JAXM也大量采用DOM4J
HIBERNATE采用DOM4J
虽然DOM4J代表了完全独立的开发结果,但最初,它是JDOM的一种智能分支。它合并了许多超出基本XML文档表示的功能,包括集成的XPath支持、XML Schema支持以及用于大文档或流化文档的基于事件的处理。它还提供了构建文档表示的选项,它通过DOM4J API和标准DOM接口具有并行访问功能。从2000下半年开始,它就一直处于开发之中。
为支持所有这些功能,DOM4J使用接口和抽象基本类方法。DOM4J大量使用了API中的Collections类,但是在许多情况下,它还提供一些替代方法以允许更好的性能或更直接的编码方法。直接好处是,虽然DOM4J付出了更复杂的API的代价,但是它提供了比JDOM大得多的灵活性。
在添加灵活性、XPath集成和对大文档处理的目标时,DOM4J的目标与JDOM是一样的:针对Java开发者的易用性和直观操作。它还致力于成为比JDOM更完整的解决方案,实现在本质上处理所有Java/XML问题的目标。在完成该目标时,它比JDOM更少强调防止不正确的应用程序行为。
DOM4J是一个非常非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件。如今你可以看到越来越多的Java软件都在使用DOM4J来读写XML,特别值得一提的是连Sun的JAXM也在用DOM4J.
4.JDOM
JDOM
优点:①是基于树的处理XML的Java API,把树加载在内存中
②没有向下兼容的限制,因此比DOM简单
③速度快,缺陷少
④具有SAX的JAVA规则
缺点:
①不能处理大于内存的文档
②JDOM表示XML文档逻辑模型。不能保证每个字节真正变换。
③针对实例文档不提供DTD与模式的任何实际模型。
④不支持与DOM中相应遍历包。
最适合于:JDOM具有树的便利,也有SAX的JAVA规则。在需要平衡时使用
如何防止Sql注入
有两种办法
1.第一种消毒,通过正则匹配过滤请求数据中可能注入的SQL。
2.使用预编译手段preparedStatemengt。
DB第一范式,第二范式,第三范式
第一范式:没一列属性不可再分,没有多值属性
第二范式:在符合第一范式的基础上,存在主键
第三范式:在符合第二范式的基础上,非关键字独立于其他的非关键字,并且依赖关键字。不能存在传递依赖。
public、protected、private、默认权限
private:用于修饰类和方法,只允许该类访问。
默认:只允许在同一个类和同一个包中进行访问。
protected:用于修饰类和方法,允许该类和子类访问以及同一个包中访问。
public:用于修饰类和方法,允许该包下面和其他包的访问,即在全局范围都可以访问。
数据库事务
事务的特性:
原子性:事务是不可再分的;
一致性:事务的实行前后,数据库的状态保持一致;
隔离性:事务的并发访问,事务之间的执行互不干扰;
持久性:事务结束后数据永久保存在数据库中。
什么是脏读?
脏读就是一个事务读取了该数据并且对该数据做出了修改,另一个事务也读取了该修改后的数据但是前一个事务并没有提交,这是脏数据。
读取到保存在数据库内存中的数据。
什么是不可重复读?
一个事务:在同一个事务中读取同一数据,得到的内容不同。一个事务读取另外一个事务更新的数据,导致二次的查询的数据不一致。
什么是幻读?
幻读是当事务不独立发生的。比如一个事务删除了所有数据,另一个事务又插入了一条,那么第一个事务的用户会发现表中还没有修改的数据行。一个事务读取到另外一个事务提交的数据,导致查询的结果不一致的问题。
数据库的隔离级别:
Read uncommitted:未提交读:三中都有可能发生
Read committed :已提交读 避免脏读
Repeated read:重复读:避免脏读 不可重复读
Serializable:串行化读 都可以避免
WebService到底是什么
一言以蔽之:WebService是一种跨编程语言和跨操作系统平台的远程调用技术。
所谓跨编程语言和跨操作平台,就是说服务端程序采用java编写,客户端程序则可以采用其他编程语言编写,反之亦然!跨操作系统平台则是指服务端程序和客户端程序可以在不同的操作系统上运行。
所谓远程调用,就是一台计算机a上的一个程序可以调用到另外一台计算机b上的一个对象的方法,譬如,银联提供给商场的pos刷卡系统,商场的POS机转账调用的转账方法的代码其实是跑在银行服务器上。再比如,amazon,天气预报系统,淘宝网,校内网,百度等把自己的系统服务以webservice服务的形式暴露出来,让第三方网站和程序可以调用这些服务功能,这样扩展了自己系统的市场占有率,往大的概念上吹,就是所谓的SOA应用。
其实可以从多个角度来理解WebService,从表面上看,WebService就是一个应用程序向外界暴露出一个能通过Web进行调用的API,也就是说能用编程的方法通过Web来调用这个应用程序。我们把调用这个WebService的应用程序叫做客户端,而把提供这个WebService的应用程序叫做服务端。从深层次看,WebService是建立可互操作的分布式应用程序的新平台,是一个平台,是一套标准。它定义了应用程序如何在Web上实现互操作性,你可以用任何你喜欢的语言,在任何你喜欢的平台上写Web service ,只要我们可以通过Web service标准对这些服务进行查询和访问。
WebService平台需要一套协议来实现分布式应用程序的创建。任何平台都有它的数据表示方法和类型系统。要实现互操作性,WebService平台必须提供一套标准的类型系统,用于沟通不同平台、编程语言和组件模型中的不同类型系统。Web service平台必须提供一种标准来描述Web service,让客户可以得到足够的信息来调用这个Web service。最后,我们还必须有一种方法来对这个Web service进行远程调用,这种方法实际是一种远程过程调用协议(RPC)。为了达到互操作性,这种RPC协议还必须与平台和编程语言无关。
java中锁的优化
1.减少锁持有的时间,可以减少其它线程的等待时间,不能让一个线程一直控制着某个锁不释放,导致竞争加剧。
2.减少锁的粒度,合适的锁的代码块,可以减少竞争,控制锁的范围。
3.锁分离,将锁安功能划分,比如读写锁,读读不互斥,读写互斥,写写互斥,保证了线程的安全,提高了性能。比如阻塞队列中的take和put
4.锁粗化,如果对同一个锁不停的进行请求,同步和释放,这个消耗是非常的大的,所以适当的时候可以粗化。
5.锁消除,编译器可以帮助我们优化比如一些代码根本不需要锁。
虚拟机内的锁优化
1.偏向锁:偏向当前已经占有锁的线程,在无竞争的时候,之前获得锁的线程再次获得锁时,会判断是否偏向锁指向我,那么该线程将不用再次获得锁,直接进入同步块。
2.轻量级锁:偏向锁失败后,利用cas补救补救失败就会升级为重量级锁。
3.自旋锁:会做空操作,并且不停地尝试拿到这个锁。
java中一亿个数找前10000个最大的
先利用Hash法去重复,去除大量的之后 然后等量的分成100份 用小顶堆 来获得10000个,再把所有的1万个都合在一起就OK
java中线程的状态
java中的线程的状态有5种(新建、就绪、运行、阻塞、结束)
1.新建:创建后尚未启动的线程处于这种状态,新建出一个线程对象。
2.就绪状态:当针对该对象掉用了start()方法,该线程等待获取CPU的使用权
3.运行状态:在就绪状态下,获得了CPU处于运行状态。
4.阻塞:
等待阻塞:运行的线程执行wait方法,JVM会把该线程放入等待池
同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他的线程锁占用,则jvm会把该线程放入锁池中。
其他阻塞:运行的线程在执行sleep()方法或者join()方法时,或者发出IO请求,JVM会把线程置为阻塞状态。
5.结束:
也就是我们的死亡,表明线程结束。
Maven的生命周期
maven有三套相互独立的生命周期
1.clean生命周期
pre-clean,clean,post-clean
2.default生命周期 构建项目
1.validate:验证工程是否正确,所有需要的资源是否可用
2.compile:编译项目源代码
3.test:使用合适的单元框架来测试已编译的源代码。
4.Package:把已编译的代码打包成可发布的格式,jar。
4)Package:把已编译的代码打包成可发布的格式,比如jar。
5)integration-test:如有需要,将包处理和发布到一个能够进行集成测试的环境。
6)verify:运行所有检查,验证包是否有效且达到质量标准。
7)install:把包安装到maven本地仓库,可以被其他工程作为依赖来使用。
8)Deploy:在集成或者发布环境下执行,将最终版本的包拷贝到远程的repository,使得其他的开发者或者工程可以共享。
site生命周期:建立和发布项目站点,phase如下
1)pre-site:生成项目站点之前需要完成的工作
2)site:生成项目站点文档
3)post-site:生成项目站点之后需要完成的工作
4)site-deploy:将项目站点发布到服务器
数据库索引
什么是索引?
(1)索引是对记录集多个字段进行排序的方法。
(2)也是一个数据结构,在一张表中为一个字段创建索引,将创建另外一个数据结构,包含字段的数值以及指向相关记录的指针,就可以对该数据结构进行二分法排序,当需要查询时就可以降低时间复杂度。
优势:快速存取数据;保证数据记录的唯一性;实现表和表之间的参照完整性;在使用order by group by子句进行数据的检索时,利用索引可以减少排序和分组的时间。
弊端:建立索引表也是会需要额外的空间。
索引的工作原理:
在对表中记录进行搜索时并不是对表中的数据进行全部的扫描遍历,而是查看在索引中定义的有序列,一旦在索引中找到了要查询的记录,就会得到一个指针,它会指向相应的表中数据所保存的位置。
索引的类型:
(1)聚集索引:数据页在物理上的有序的存储,数据页的物理顺序是按照聚集索引的顺序进行排列。在聚集索引中数据页聚集索引的叶子节点,数据页之间通过双向的链表形式相连接,实际的数据存储在叶节点中。
(2)非聚集索引:叶子节点不存放具体的数据页信息,只存放索引的键值。非聚集索引的叶子节点包含着指向具体数据的指针,数据页之间没有连接,是相对独立的。
(3)唯一索引:在整个表中仅仅会出现一次(主键约束/UNIQUE)
(4)非唯一索引:在提取数据时允许出现重复的值。
(5)单一索引和组合索引
哪些情况下索引会失效?
1.条件中有or但是前后没有同时使用索引
2.多列索引,不是使用前面部分
3.like查询是以%开头
4.字符类型应该加单引号 防止转换为int类型
数据库查询优化(Sql)
1、应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
2、对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
3、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=0
4、尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num=10 or num=20
可以这样查询:
select id from t where num=10
union all
select id from t where num=20
5、下面的查询也将导致全表扫描:(不能前置百分号)
select id from t where name like ‘�c%’
若要提高效率,可以考虑全文检索。
6、in 和 not in 也要慎用,否则会导致全表扫描,如:
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
7、如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:
select id from t where num=@num
可以改为强制查询使用索引:
select id from t with(index(索引名)) where num=@num
8、应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
应改为:
select id from t where num=100*2
9、应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)=’abc’–name以abc开头的id
select id from t where datediff(day,createdate,’2005-11-30′)=0–’2005-11-30′生成的id
应改为:
select id from t where name like ‘abc%’
select id from t where createdate>=’2005-11-30′ and createdate<’2005-12-1′
10、不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
11、在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使 用,并且应尽可能的让字段顺序与索引顺序相一致。
12、不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(…)
13、很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
14、并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段 sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
15、索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。
16.应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
17、尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会 逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
18、尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
19、任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
20、尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
21、避免频繁创建和删除临时表,以减少系统表资源的消耗。
22、临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使 用导出表。
23、在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
24、如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
25、尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
26、使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
27、与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时 间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
28、在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。
29、尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
30、尽量避免大事务操作,提高系统并发能力。查询sql语句中哪些比较慢
1.慢查询日志,一般设置查询超过两秒就记录
2.processlist:显示哪些线程正在运行.
3.explain关键字可以让我们更好的优化
数据库设计优化:
1.反范式设计,尽量是单表,可以高效利用索引
2.可以使用查询缓存,Mysql也会自带查询缓存
3.尽量查询通过搜索引擎查不通过我们
4.key,value数据库
锁的优化策略
1.读写分离,锁分离
2.减少锁持有时间,可以减少其他的锁的持有时间
3.以正确的顺序获得和释放锁
4.适当的锁的范围扩大或者缩小,控制锁的粒度
Spring Bean中作用域
singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例。
prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例。
request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效。
session:对于每次HTTP Session,使用session定义的Bean都将产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效。
Global session:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效。
java中启定时任务
1.利用sleep特性//休眠
2.time和timerTask//定时器
3.ScheduledExecutorService service.scheduleAtFixedRate(runnable, 10, 1, TimeUnit.SECONDS);
//任务调度服务
操作系统如何进行分页调度
用户程序的地址空间被划分成若干固定大小的区域,称为“页”,相应地,内存空间分成若干个物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一块中,实现了离散分配。
linux内核的三种主要调度策略:
1,SCHED_OTHER 分时调度策略,
2,SCHED_FIFO实时调度策略,先到先服务
3,SCHED_RR实时调度策略,时间片轮转
TCP和UDP相关
TCP通过什么方式提供可靠性:
1.超时重发,发出报文段要是没有收到及时的确认,会重发。
2.数据包的校验,也就是校验首部数据和。
3.对失序的数据重新排序
4.进行流量控制,防止缓冲区溢出
5.快重传和快恢复
6.TCP会将数据截断为合理的长度
TCP和UDP的区别:
1.UDP是无连接的,TCP必须三次握手建立连接
2.UDP是面向报文,没有拥塞控制,所以速度快,适合多媒体通信要求,比如及时聊天,支持一对一,一队多。多对一,多对多。
3.TCP只能是一对一的可靠性传输
TCP的RPC,在协议栈的下层,能够灵活的对字段进行定制,减少网络传输字节数,降低网络开销,提高性能,实现更大的吞吐量和并发数。但是实现代价高,底层复杂,难以得到开源社区的支持,难以实现跨平台
集群调优
1.load
load是被定义为特定时间间隔内运行队列中的平均线程数,uptime查看,一般load不大于3,我们认为负载是正常的,如果每个CPU的线程数大于5,表示负载就非常高了。
2.CPU利用率
查看cpu的消耗的情况命令:top | grep Cpu
查看磁盘的剩余空间命令:df -h
查看系统的内存的使用情况:free -m
心跳检测方法
1.使用ping命令
对于full gc导致不响应,网络攻击这种 ping展示不明确
2.使用curl
访问我们的自测地址
3.对不同的功能使用curl检测,在response中加入状态头,表示正常
可以计算qps通过28原则
innodb存储引擎通过预写事务日志的方式保障事务的原子性,也就是在写入数据之前,先将数据操作写入日志,这种成为预写日志
轻量级锁认为在程序运行过程中,绝大部分的锁,在整个同步周期内都是不存在竞争的,利用cas操作避免互斥开销。
偏向锁是jdk1.6中引入的一项优化,甚至可以省掉CAS操作,偏向锁偏向第一个获得他锁的线程,如果在接下来执行过程中,这个锁没有被其他线程获取,则持有偏向锁的线程永远不需要同步。
GC调优
查看GC日志,根据GC日志来优化
我们可以通过jps找到我们虚拟机正在运行的进程。参数 通过jps -help了解。
Jstat -gc 或者 -gcutil 查看堆使用情况-class 配合Jps得到进程
BTrace原理利用hotspot虚拟中的热替换,把代码动态的替换到java程序内,可在不需要重启的时候就可以排除问题
JConsole
我们也可以使用JConsole来分析这是一个图形化的,比较容易让我们操作
使用VisualVM 进行远程连接 使用JMX方式,也有修改tomcat的catalina就行了
内部类去访问外部变量,为什么需要加final?
题目有点问题,并不是所有的外部变量才加final,我们的内部类访问我们的成员变量就不需要加final,但是访问局部变量就必须要加final,因为方法(main方法)结束我们栈帧也就销毁了,但是我们内部类在堆中并没有被销毁,如果引用了成员变量,这时候被销毁了肯定是不行的,所以我们就需要成员变量设置为final,让其在方法(main方法)结束时不会被销毁。
泛型
泛型的作用:在我们没有泛型的时候,我们通过对Object的引用来对参数的任意化,任意化有个缺点就是要做显示的强制类型转换,强制转换有一个不好的地方是运行的时候才会报错,泛型的好处实在编译的时候检查类型安全,所以泛型的特点就是简单安全,泛型的原理是类型擦除,java的泛型是伪泛型,在编译期间,所有的泛型信息都会被擦除掉。在生成的java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器编译的时候去掉。比如List信息在编译后都是List。
nginx和apache的对比
1.nginx相对于apache来说
(1)轻量级占用的内存少;
(2)抗并发,nginx是异步非阻塞,apache是阻塞的,在高并发的情况下,nginx的性能优;
(3)高度模块化的设计,编写模块相对简单;
(4)社区活跃,各种高性能模块有。
适合场景:apache适合于动态的请求,而负载均衡和静态请求适合nginx,nginx采用的是epoll,并且有自己的一套sendfile系统,减少在内存中的赋值次数。
2.apache 相对于nginx 的优点:
(1)rewrite ,比nginx 的rewrite 强大 ;
(2)模块超多,基本想到的都可以找到 ;
(3)少bug ,nginx 的bug 相对较多 ;
(4)超稳定 。
线程、进程的共享和独立
共享的部分:
1.进程代码段
2.进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)
3.进程打开的文件描述符、
4.信号的处理器、
5.进程的当前目录
6.进程用户ID与进程组ID
线程独有的内容包括:
1.线程ID
2.寄存器组的值
3.线程的堆栈
4.错误返回码
5.线程的信号屏蔽码
最大的优势就是线程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,线程的性能优势就越明显。
第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在线程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
简单的说一下nginx的优点
1.作为高性能的web服务器:相比Apache,Nginx使用更少的资源,支持更多的并发连接,体现更高的效率,这点让Nginx受到虚拟主机提供商的欢迎。一个Nginx实例能够轻松支持高达5万并发。
2.作为负载均衡服务器:Nginx即可以在内部直接支持PHP,也可以支持作为HTTP代理服务器对外进行服务,独有的send files系统,减少文件复制次数。
3.作为邮件代理服务器:也比Apache好很多。
4.Nginx安装简单,配置文件非常简洁。启动容易,7*24小时几乎不间断,可以进行热更新。
BIO
在BIO中读和写都是同步阻塞的,阻塞的时间取决于对方I/O线程的处理速度和网络的传输速度。本质上来讲,我们是无法保证生产环境的网络状况和对端的应用程序可以足够快,应用程序是不应该依赖对方的处理速度,它的可靠性就非常差。BIO就算用线程池实现,要是所有可用线程都被阻塞到故障点中,后续的所有I/O消息都将在队列中排队。
NIO
(1)提供了高速,面向块的I/O。
(2)在NIO中所有数据都是用缓冲区来处理的,也就是使用我们的jvm中的direct memory(直接内存)。缓冲区是一个数组,但是缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化访问以及维护读写位置等信息。
(3)在NIO中channel(通道)也是特别重要的他是我们数据读写的通道,一般来说流比如inputStream和outputStream都是单向的,而通道是双向的,是全双工的。
(4)多路复用器Selector也是比较重要的,掌握它对于我们的NIO编程来说是比较重要的。多路复用器提供选择已经就绪的任务的能力。Selector会不断轮训注册在其上的Channel,如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,通过Selection Key可以获取就绪Channel的集合,进行后续的I/o操作。我们只需要一个线程就可以管理我们多个客户端。
垃圾收集器
1.Serial收集器
Serial收集器是JAVA虚拟机中最基本、历史最悠久的收集器,在JDK 1.3.1之前是JAVA虚拟机新生代收集的唯一选择。Serial收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅是说明它只会使用一个 CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。
Serial收集器到JDK1.7为止,它依然是JAVA虚拟机运行在Client模式下的默认新生代收集器。它也有着优于其他收集器的地方:简单而高 效(与其他收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集 效率。在用户的桌面应用场景中,分配给虚拟机管理的内存一般来说不会很大,收集几十兆甚至一两百兆的新生代(仅仅是新生代使用的内存,桌面应用基本上不会 再大了),停顿时间完全可以控制在几十毫秒最多一百多毫秒以内,只要不是频繁发生,这点停顿是可以接受的。所以,Serial收集器对于运行在 Client模式下的虚拟机来说是一个很好的选择。
-
Parallel(并行)收集器
这是 JVM 的缺省收集器。就像它的名字,其最大的优点是使用多个线程来通过扫描并压缩堆。串行收集器在GC时会停止其他所有工作线程(stop-the- world),CPU利用率是最高的,所以适用于要求高吞吐量(throughput)的应用,但停顿时间(pause time)会比较长,所以对web应用来说就不适合,因为这意味着用户等待时间会加长。而并行收集器可以理解是多线程串行收集,在串行收集基础上采用多线 程方式进行GC,很好的弥补了串行收集的不足,可以大幅缩短停顿时间(如下图表示的停顿时长高度,并发比并行要短),因此对于空间不大的区域(如 young generation),采用并行收集器停顿时间很短,回收效率高,适合高频率执行。
3.CMS收集器
CMS(Concurrent Mark Sweep)收集器是基于“标记-清除”算法实现的,它使用多线程的算法去扫描堆(标记)并对发现的未使用的对象进行回收(清除)。整个过程分为6个步骤,包括:
初始标记(CMS initial mark)
并发标记(CMS concurrent mark)
并发预清理(CMS-concurrent-preclean)
重新标记(CMS remark)
并发清除(CMS concurrent sweep)
并发重置(CMS-concurrent-reset)
其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一 般会比初始标记阶段稍长一些,但远比并发标记的时间短。其他动作都是并发的。
需要注意的是,CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。由于CMS并发清理阶段用户线程还在运行着,伴随程序的运行自然还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次 收集中处理掉它们,只好留待下一次GC时再将其清理掉。这一部分垃圾就称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,即还需要预留足够的 内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运作使 用。在默认设置下,CMS收集器在老年代使用了68%的空间后就会被激活,这是一个偏保守的设置,如果在应用中老年代增长不是太快,可以适当调高参数 -XX:CMSInitiatingOccupancyFraction的值来提高触发百分比,以便降低内存回收次数以获取更好的性能。要是CMS运行期 间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置 得太高将会很容易导致大量“Concurrent Mode Failure”失败,性能反而降低。
try catch有return 在字节码中 可以看到会出现Goto跳转行数,跳转到finally中的return
Tomcat总结构
最外层的Server提供接口访问内部的Service服务。
Service服务的作用就是把Connector 在Connetor中监听端口和Container连接起来,方便我们的操纵控制。
在tomcat中生命周期是统一的由Lifecycle来管理的
在lifecycle中有两个方法比较重要start和stop,调用server的start会去调用自己下面所有service的start方法
Connector最重要的功能就是接受连接请求然后分配线程处理
在Container中 有4个级别Engine,Host,Context,warpper,这四个组件不是平行的,而是父子关系,Engine 包含 Host,Host 包含 Context,Context 包含 Wrapper。通常一个 Servlet class 对应一个 Wrapper,如果有多个 Servlet 就可以定义多个 Wrapper,如果有多个 Wrapper 就要定义一个更高的 Container 了,如 Context。在Host中有个value比较重要,类似于一个管道,和拦截器链差不多,我们在中间可以进行一些处理。
在Engine中只能添加子容器Host,不能添加父容器.Engine下可以配置多个虚拟主机Virtual Host,每个虚拟主机都有一个域名
当Engine获得一个请求时,它把该请求匹配到某个Host上,然后把该请求交给该Host来处理Engine有一个默认虚拟主机,当请求无法匹配到任何一个Host上的时候,将交给该默认Host来处理
一个Host就类似于一个虚拟主机,用来管理应用。代表一个Virtual Host,虚拟主机,每个虚拟主机和某个网络域名Domain Name相匹配
每个虚拟主机下都可以部署(deploy)一个或者多个Web App,每个Web App对应于一个Context,有一个Context path
当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理
匹配的方法是“最长匹配”,所以一个path==""的Context将成为该Host的默认Context
所有无法和其它Context的路径名匹配的请求都将最终和该默认Context匹配。
Context 代表 Servlet 的 Context,它具备了 Servlet 运行的基本环境,理论上只要有 Context 就能运行 Servlet 了。简单的 Tomcat 可以没有 Engine 和 Host。
Context 最重要的功能就是管理它里面的 Servlet 实例,Servlet 实例在 Context 中是以 Wrapper 出现的,还有一点就是 Context 如何才能找到正确的 Servlet 来执行它呢?
一个Context对应于一个Web Application,一个Web Application由一个或者多个Servlet组成
Context在创建的时候将根据配置文件CATALINAHOME/conf/web.xml和CATALINAHOME/conf/web.xml和WEBAPP_HOME/WEB-INF/web.xml载入Servlet类
当Context获得请求时,将在自己的映射表(mapping table)中寻找相匹配的Servlet类
如果找到,则执行该类,获得请求的回应,并返回.
线程池参数
JDK1.5中引入了强大的concurrent包,其中最常用的莫过了线程池的实现。ThreadPoolExecutor(线程池执行器),它给我们带来了极大的方便,但同时,对于该线程池不恰当的设置也可能使其效率并不能达到预期的效果,甚至仅相当于或低于单线程的效率。
ThreadPoolExecutor类可设置的参数主要有:
(1)corePoolSize 基本大小
核心线程数,核心线程会一直存活,即使没有任务需要处理。当线程数小于核心线程数时,即使现有的线程空闲,线程池也会优先创建新线程来处理任务,而不是直接交给现有的线程处理。
核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。
(2)maxPoolSize 最大大小
当线程数大于或等于核心线程corePoolSize,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。如果线程数已等于maxPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池会拒绝处理任务而抛出异常。
keepAliveTime 大于coolPoolSize的 会退出
当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。
allowCoreThreadTimeout 是否退出核心线程
是否允许核心线程空闲退出,默认值为false。
queueCapacity
任务队列容量。从maxPoolSize的描述上可以看出,任务队列的容量会影响到线程的变化,因此任务队列的长度也需要恰当的设置。
线程池按以下行为执行任务
当线程数小于核心线程数时,创建线程。
当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
当线程数大于等于核心线程数,且任务队列已满
若线程数小于最大线程数,创建线程
若线程数等于最大线程数,抛出异常,拒绝任务
系统负载
参数的设置跟系统的负载有直接的关系,下面为系统负载的相关参数:
tasks,每秒需要处理的最大任务数量
tasktime,处理每个任务所需要的时间
responsetime,系统允许任务最大的响应时间,比如每个任务的响应时间不得超过2秒。
参数设置
corePoolSize:
每个任务需要tasktime秒处理,则每个线程每钞可处理 1/tasktime个任务。系统每秒有tasks个任务需要处理,则需要的线程数为:tasks/(1/tasktime),即 taskstasktime个线程数。假设系统每秒任务数为100~1000,每个任务耗时0.1秒,则需要1000.1至1000*0.1,即 10~100个线程。那么corePoolSize应该设置为大于10,具体数字最好根据8020原则,即80%情况下系统每秒任务数,若系统80%的情 况下第秒任务数小于200,最多时为1000,则corePoolSize可设置为20。
queueCapacity:
任务队列的长度要根据核心线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为(corePoolSize/tasktime)*responsetime: (20/0.1)*2=400,即队列长度可设置为400。
队列长度设置过大,会导致任务响应时间过长,切忌以下写法:
LinkedBlockingQueue queue = new LinkedBlockingQueue();
这实际上是将队列长度设置为Integer.MAX_VALUE,将会导致线程数量永远为corePoolSize,再也不会增加,当任务数量陡增时,任务响应时间也将随之陡增。
maxPoolSize:
当系统负载达到最大值时,核心线程数已无法按时处理完所有任务,这时 就需要增加线程。每秒200个任务需要20个线程,那么当每秒达到1000个任务时,则需要(1000-queueCapacity)* (20/200),即60个线程,可将maxPoolSize设置为60。
keepAliveTime:
线程数量只增加不减少也不行。当负载降低时,可减少线程数量,如果一个线程空闲时间达到keepAliveTiime,该线程就退出。默认情况下线程池最少会保持corePoolSize个线程。
allowCoreThreadTimeout:
默认情况下核心线程不会退出,可通过将该参数设置为true,让核心线程也退出。
以上关于线程数量的计算并没有考虑CPU的情况。若结合CPU的情况,比如,当线程数量达到50时,CPU达到100%,则将 maxPoolSize设置为60也不合适,此时若系统负载长时间维持在每秒1000个任务,则超出线程池处理能力,应设法降低每个任务的处理时间 (tasktime)。
使用ThreadLocal解决SimpleDateFormat,
privatestatic ThreadLocal threadLocal = new ThreadLocal() { @Override protected DateFormat initialValue() { returnnew SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”); } };
或者推荐Joda-Time处理时间比较推荐
在java线程中有6个状态也就是Thread中的枚举类
NEW,RUNNABLE,WAITING,TIME_WAITING,BLOCKED,TERMINATED
应该在try{} catch{}中重新设置中断状态,因为发出中断异常被退出了。
java内存模型
jvm规范定了jvm内存模型来屏蔽掉各种操作系统,虚拟机实现厂商和硬件的内存访问差异,确保java程序在所有操作系统和平台上能够实现一次编写,到处运行的效果。
为什么IP协议也能够进行数据的不可靠传输,还需要Udp
1.我们需要端口号来实现应用程序间的区分。
2.UDP校验和可以实现传输层的校验,虽然UDP协议不具备纠错能力,但是可以对出错的数据包进行丢弃,而IP的校验只是在校验IP报头,而不是整个数据包,整个数据包的校验是在传输层完成的,如果出错了,就会把出错的数据包丢弃。这也就是为什么需要有传输层
进程通信中管道和共享内存谁的速度快?
1.管道通信方式的中间介质是文件,通常称这种文件为管道文件。两个进程利用管道进行通信,一个进程为写进程,另一个进程为读进程。写进程通过往管道文件中写入信息,读进程通过读端从管道文件中读取信息,这样的方式进行通信。
2.共享内存是最快的可用IPC形式,他通过把共享的内存空间映射到进程的地址空间,进程间的数据传递不在通过执行任何进入内核的系统调用,节约了时间。java内存模型就采用的是共享内存的方式。各个进程通过公共的内存区域进行通信。
java线程池shutdown和shutdownNow的区别?
Shutdown()方法
当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException(拒绝执行异常)异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。
shutdownNow()方法
根据JDK文档描述,大致意思是:执行该方法,线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。
它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。
Integer的装箱和拆箱的基本原理
Integer包装类是Java最基本的语法糖优化,如果我们写一段程序,通过反编译就会看到,通过的是Integer.valueOf()或者Interger.intValue()来进行转换的。
Integer和int比较会自动拆箱,
当Integer和integer比较的时候是不会自动拆箱的,除非遇到了算术符,才会调用intValue()方法去拆箱。并且在Integer中的equals是不会处理数据类型转换关系的,使用时是需要慎用,在equals方法中判断为Integer类的才会执行真正的判断流程也就是拆箱去判断,所以不会处理数据类型转换比如Long。如果是基本类型比较,编译器会隐形的把int转换为long,就可以得出正确的结论。
为什么不推荐使用resume和suspend??
因为在使用suspend()去挂起线程的时候,suspend在导致线程暂停的同时,不会去释放任何锁的资源。必须要等待resume()操作,被挂起的线程才能继续。如果我们resume()操作意外地在suspend()前就执行了,那么被挂起的线程可能很难有机会被继续执行。并且,更严重的是:所占用的锁不会被释放,因此可能会导致整个系统工作不正常
yield是谦让,调用后会使当前线程让出CPU,但是注意的地方让出CPU并不表示当前线程不执行了。当前线程在让出CPU后,还会进行CPU资源的争夺,有可能刚刚一让马上又进。
数据库事务的隔离级别
1.未提交读 都不能解决
2.已提交读 能解决脏读
3.可重复读 能解决脏读,不可重复读
4.序列化读 能解决脏读,不可重复读,幻读
Docker和虚拟机的比较
1.传统的虚拟机在宿主机操作系统上面会利用虚拟机管理程序去模拟完整的一个虚拟机操作系统,docker只是在操作系统上的虚拟化,直接复用本地主机的操作系统,非常轻量级。
docker启动速度一般在秒级,虚拟机启动速度一般在分钟级。
2.对于资源的使用一般是mb,一台机器可以有上千个docker容器,但是虚拟机占用资源为GB,只能支持几个。
3.性能:接近原生,由于又虚拟化了一层所以效率低。
4.docker采用类似git的命令学习升本低,指令简单。
5.虚拟机隔离性是完全隔离,容器是安全隔离
lucence组件
每一个词都会有一个倒排表,多个可以合并
为了标识webSocket:会在请求头中写一个upgrade:webSocket
与HTTP比较
同样作为应用层的协议,WebSocket在现代的软件开发中被越来越多的实践,和HTTP有很多相似的地方,这里将它们简单的做一个纯个人、非权威的比较:
相同点
都是基于TCP的应用层协议。
都使用Request/Response模型进行连接的建立。
在连接的建立过程中对错误的处理方式相同,在这个阶段WS可能返回和HTTP相同的返回码。
都可以在网络中传输数据。
不同点
WS使用HTTP来建立连接,但是定义了一系列新的header域,这些域在HTTP中并不会使用。
WS的连接不能通过中间人来转发,它必须是一个直接连接。
WS连接建立之后,通信双方都可以在任何时刻向另一方发送数据。f
WS连接建立之后,数据的传输使用帧来传递,不再需要Request消息。
WS的数据帧有序。
微服务架构
首先看一下微服务架构的定义:微服务(MSA)是一种架构风格,旨在通过将功能分解到各个离散的服务中以实现对解决方案的解耦。它有如下几个特征:
小,且只干一件事情。
独立部署和生命周期管理。
异构性
轻量级通信,RPC或者Restful。
BIO通信模型图:
BIO以是一客户端一线程,一个Acceptor线程来接受请求,之后为每一个客户端都创建一个新的线程进行链路处理,最大的问题缺乏弹性伸缩能力,客户端并发访问量增加后,线程数急剧膨胀。可以用线程池缓解但是还是不行。
NIO非阻塞IO解决了这个问题,一个线程就可以管理多个Socket.
在NIO中有三个需要我们了解Buffer,Channel,Selector。
Buffer:NIO是面向缓冲区,IO是面向流的。缓冲区实质上一个数组
,缓冲区不仅仅是一个数组,缓冲区提供了对数据结构化访问以及维护读写位置等信息。
通道Channel:
Channel是一个通道,网络数据通过Channel读取和写入。通道与流的不同之处在于通道是双向的,流是一个方向上移动,通道可以用于读写操作,特别是在UNIX网络编程模型底层操作系统的通道都是全双工的,同时支持读写操作。
Selector:多路复用器NIo编程的基础,多路复用器提供选择就绪任务的 能力。简单来说Selector会不断注册在其上的Channel,如果某个Channel上面发生读或者写时间,这个Channel就处于就绪状态,会被Selector轮询出来,通过SelectionKey,一个多路复用器可以同时轮询多个Channel,JDK使用了epoll代理传统select实现,所以没有最大连接句柄fd的限制,意味着一个线程负责Selector的轮询,就可以接入成千上万的客户端
select/poll 和 epoll: select poll顺序扫描fd,就绪就返回fd。epoll则是 采用事件驱动,用回调的方式返回fd。
面向对象设计七大原则
- 单一职责原则(Single Responsibility Principle)
每一个类应该专注于做一件事情。
- 里氏替换原则(Liskov Substitution Principle)
超类存在的地方,子类是可以替换的。
- 依赖倒置原则(Dependence Inversion Principle)
实现尽量依赖抽象,不依赖具体实现。
- 接口隔离原则(Interface Segregation Principle)
应当为客户端提供尽可能小的单独的接口,而不是提供大的总的接口。
- 迪米特法则(Law Of Demeter)
又叫最少知识原则,一个软件实体应当尽可能少的与其他实体发生相互作用。
- 开闭原则(Open Close Principle)
面向扩展开放,面向修改关闭。
- 组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP)
尽量使用合成/聚合达到复用,尽量少用继承。原则: 一个类中有另一个类的对象。
拦截器和过滤器的区别
强类型:不允许隐形转换
弱类型:允许隐形转换
静态类型:编译的时候就知道每一个变量的类型,因为类型错误而不能做的事情是语法错误.
动态类型:编译得时候不知道每一个变量的类型,因为类型错误而不能做的事情是运行时错误
编译语言和解释语言:解释性编程语言,每个语句都是执行的时候才翻译而且是一句一句的翻译就很低。
编译的语言就只需要一次 就可以了
继承Thread 和 接口Runnabel的区别
主要是继承和实现接口的两个区别,如果只想重写run方法就可以使用,如果不重写其他方法 就使用runnable,如果使用实现接口的实现,让自己方便管理线程以及让线程复用,可以使用线程池去创建。
hash算法(特点、哈希函数构造、解决冲突的策略)
哈希表的概念:
哈希表就是一种以 键-值(key-indexed) 存储数据的结构,我们只要输入待查找的值即key,即可查找到其对应的值。
哈希表的实现思路:
如果所有的键都是整数,那么就可以使用一个简单的无序数组来实现:将键作为索引,值即为其对应的值,这样就可以快速访问任意键的值。这是对于简单的键的情况,我们将其扩展到可以处理更加复杂的类型的键。对于冲突的情况,则需要处理地址的冲突问题。所以,一方面要构造出良好的哈希函数,对键值集合进行哈希,另外一方面需要设计出良好的解决冲突的算法,处理哈希碰撞的冲突。
哈希表的查找步骤:
(1)使用哈希函数将被查找的键转换为数组的索引。在理想的情况下,不同的键会被转换为不同的索引值,但是在有些情况下我们需要处理多个键被哈希到同一个索引值的情况。所以哈希查找的第二个步骤就是处理冲突
(2)处理哈希碰撞冲突。有很多处理哈希碰撞冲突的方法,本文后面会介绍拉链法和线性探测法。
哈希表的思想:
是一个在时间和空间上做出权衡的经典例子。如果没有内存限制,那么可以直接将键作为数组的索引。那么所有的查找时间复杂度为O(1);如果没有时间限制,那么我们可以使用无序数组并进行顺序查找,这样只需要很少的内存。哈希表使用了适度的时间和空间来在这两个极端之间找到了平衡。只需要调整哈希函数算法即可在时间和空间上做出取舍。
哈希表的工作步骤:
-
哈希(Hash)函数是一个映象,即将关键字的集合映射到某个地址集合上,它的设置很灵活,只要这个地址集合的大小不超出允许范围即可;
-
由于哈希函数是一个压缩映象,因此,在一般情况下,很容易产生“冲突”现象,即: key1!=key2,而 f (key1) = f(key2)。键不同,但是对应的取值相同。
3). 只能尽量减少冲突而不能完全避免冲突,这是因为通常关键字集合比较大,其元素包括所有可能的关键字,而地址集合的元素仅为哈希表中的地址值。
哈希函数的构造方法:
1、直接地址法
以数据元素的关键字k本身或者他的线性的函数作为它 的哈希地址,也就是H(k)=k,或者H(k)=a*k+b;
适用的场景:地址集合的大小==关键字的集合。
2、数字分析法
取数据元素关键字中某些取值较均匀的数字位作为哈希地址的方法
适用的场景:能预先估计出全体关键字的每一位上各种数字出现的频度。
3、折叠法
将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位),这方法称为折叠法
适用场景:关键字的数字位数特别多。
4、平方取中法
先取关键字的平方,然后根据可使用空间的大小,选取平方数是中间几位为哈希地址。
适用场景:通过取平方扩大差别,平方值的中间几位和这个数的每一位都相关,则对不同的关键字得到的哈希函数值不易产生冲突,由此产生的哈希地址也较为均匀。
5、减去法
6、基数转换法
7、除留余数法
8、随机数法
9、随机乘数法
10、旋转法
构造哈希哈希函数的原则:
1、计算哈希函数的时间
2、关键子的长度
3、哈希表的长度
4、关键字的分布的情况
5、记录查找频率
哈希函数的冲突解决的方法:
1、开放地址法
这种方法也称再散列法,其基本思想是:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。这种方法有一个通用的再散列函数形式:
Hi=(H(key)+di)% m i=1,2,…,n
其中H(key)为哈希函数,m 为表长,di称为增量序列。增量序列的取值方式不同,相应的再散列方式也不同。主要有以下三种:
l 线性探测再散列
dii=1,2,3,…,m-1
这种方法的特点是:冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。
l 二次探测再散列
di=12,-12,22,-22,…,k2,-k2 ( k<=m/2 )
这种方法的特点是:冲突发生时,在表的左右进行跳跃式探测,比较灵活。
l 伪随机探测再散列
di=伪随机数序列。
2、再哈希法
这种方法是同时构造多个不同的哈希函数:
Hi=RH1(key) i=1,2,…,k
当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间。
3、链地址法
这种方法的基本思想是将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。
4、建立公共溢出区
这种方法的基本思想是:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表
类加载的方式
1.通过new
2.通过反射利用当前线程的classloader
3.自己继承实现一个classloader实现自己的类装载器
class.forName 和 Classloader.loadClass区别
Classloader
HashMap内部是怎么实现的?(拉链式结构)
核心:hashMap采用拉链法,构成“链表的数组”
存储示意图:
索引index建立规则:
(1)一般情况下通过hash(key)%length实现,元素存储在数组中的索引是由key的哈希值对数组的长度取模得到。
(2)hashmap也是一个线性的数组实现的,里面定义一个内部类Entry,属性包括key、value、next.Hashmap的基础就是一个线性数组,该数组为Entry[] ,map里面的内容都保存在entry[]数组中。
(3)确定数组的index:hashcode%table.length
数组的下标index相同,但是不表示hashcode相同。
实现随机存储的方法:
// 存储时:
int hash = key.hashCode(); // 这个hashCode方法这里不详述,只要理解每个key的hash是一个固定的int值
int index = hash % Entry[].length;
Entry[index] = value;
// 取值时:
int hash = key.hashCode();
int index = hash % Entry[].length;
return Entry[index];
put方法的实现:
如果两个key通过hash%Entry[].length得到的index相同,会不会有覆盖的危险?
这里HashMap里面用到链式数据结构的一个概念。上面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry。打个比方, 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。所以疑问不用担心。也就是说数组中存储的是最后插入的元素。
get方法的实现:
先定位到数组元素,再遍历该元素处的链表
table的大小:
table的初始的大小并不是initialCapacity,是initialCapacity的2的n次幂
目的在于:当哈希表的容量超过默认的容量时,必须重新调整table的大小,当容量已经达到最大的可能的值时,这时需要创建一张新的表,将原来的表映射到该新表。
req.getSession().invalidate(); 销毁session
req.getSession().setMaxInactiveInterval(30); 设置默认session的过期时间,tomcat的默认过期时间是 30分钟
利用线程池的优势:
1、降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2、提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
3、提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
boolean 单独使用的时候还会被变成int,4个字节
boolean[] 数组使用的时候变成byte,1个字节
Cache和Buffer的区别
Cache:缓存区,位于cpu和主内存之间容量很小但速度很快的存储器,因为CPU的速度远远高于主内存的速度,CPU从内存中读取数据许要等待很长的时间,而Cache保存着CPU刚用过的数据或循环使用的部分数据,Cache读取数据更快,减少cpu等待时间。
Buffer:缓冲区,用于存储速度不同步的情况,处理系统两端速度平衡,为了减小短期内突发I/O的影响,起到流量整形的作用。速度慢的可以先把数据放到buffer,然后达到一定程度在读取数据
内连接:
必须两个表互相匹配才会出现
外链接
左外链接:左边不加限制
右外连接:右边不加限制
全连接:左右都不加限制
三、如何创建索引
全文检索的索引创建过程一般有以下几步:
第一步:一些要索引的原文档(Document)。
为了方便说明索引创建过程,这里特意用两个文件为例:
文件一:Students should be allowed to go out with their friends, but not allowed to drink beer.
文件二:My friend Jerry went to school to see his students but found them drunk which is not allowed.
第二步:将原文档传给分词器(Tokenizer)。
分词器(Tokenizer)会做以下几件事情(此过程称为Tokenize):
-
将文档分成一个一个单独的单词。
-
去除标点符号。
-
去除停词(Stop word)。
所谓停词(Stop word)就是一种语言中最普通的一些单词,由于没有特别的意义,因而大多数情况下不能成为搜索的关键词,因而创建索引时,这种词会被去掉而减少索引的大小。
英语中停词(Stop word)如:“the”,“a”,“this”等。
对于每一种语言的分词组件(Tokenizer),都有一个停词(stop word)集合。
经过分词(Tokenizer)后得到的结果称为词元(Token)。
在我们的例子中,便得到以下词元(Token):
“Students”,“allowed”,“go”,“their”,“friends”,“allowed”,“drink”,“beer”,“My”,“friend”,“Jerry”,“went”,“school”,“see”,“his”,“students”,“found”,“them”,“drunk”,“allowed”。
第三步:将得到的词元(Token)传给语言处理组件(Linguistic Processor)。
语言处理组件(linguistic processor)主要是对得到的词元(Token)做一些同语言相关的处理。
对于英语,语言处理组件(Linguistic Processor)一般做以下几点:
-
变为小写(Lowercase)。
-
将单词缩减为词根形式,如“cars”到“car”等。这种操作称为:stemming。
-
将单词转变为词根形式,如“drove”到“drive”等。这种操作称为:lemmatization。
而且在此过程中,我们惊喜地发现,搜索“drive”,“driving”,“drove”,“driven”也能够被搜到。因为在我们的索引 中,“driving”,“drove”,“driven”都会经过语言处理而变成“drive”,在搜索时,如果您输入“driving”,输入的查询 语句同样经过我们这里的一到三步,从而变为查询“drive”,从而可以搜索到想要的文档。
ZK实现分布式锁- 是否存在,并且最小的
根据ZK中节点是否存在,可以作为分布式锁的锁状态,以此来实现一个分布式锁,下面是分布式锁的基本逻辑:
客户端调用create()方法创建名为“/dlm-locks/lockname/lock-”的临时顺序节点。
客户端调用getChildren(“lockname”)方法来获取所有已经创建的子节点。
客户端获取到所有子节点path之后,如果发现自己在步骤1中创建的节点是所有节点中序号最小的,那么就认为这个客户端获得了锁。
如果创建的节点不是所有节点中需要最小的,那么则监视比自己创建节点的序列号小的最大的节点,进入等待。直到下次监视的子节点变更的时候,再进行子节点的获取,判断是否获取锁。
而且zk的临时节点可以直接避免网络断开或主机宕机,锁状态无法清除的问题,顺序节点可以避免惊群效应。这些特性都使得利用ZK实现分布式锁成为了最普遍的方案之一。
Redus实现分布式锁,使用setNX (set if not exists)
getset(先写新值返回旧值,用于分辨是不是首次操作) 防止网络断开后 会设置超时
http://blog.csdn.net/ugg/article/details/41894947
SETNX 可以直接加锁操作,比如说对某个关键词foo加锁,客户端可以尝试
SETNX foo.lock
如果返回1,表示客户端已经获取锁,可以往下操作,操作完成后,通过
DEL foo.lock
命令来释放锁。
处理死锁
在 上面的处理方式中,如果获取锁的客户端端执行时间过长,进程被kill掉,或者因为其他异常崩溃,导致无法释放锁,就会造成死锁。所以,需要对加锁要做时 效性检测。因此,我们在加锁时,把当前时间戳作为value存入此锁中,通过当前时间戳和Redis中的时间戳进行对比,如果超过一定差值,认为锁已经时 效,防止锁无限期的锁下去,但是,在大并发情况,如果同时检测锁失效,并简单粗暴的删除死锁,再通过SETNX上锁,可能会导致竞争条件的产生,即多个客 户端同时获取锁。
C1获取锁,并崩溃。C2和C3调用SETNX上锁返回0后,获得foo.lock的时间戳,通过比对时间戳,发现锁超时。
C2 向foo.lock发送DEL命令。
C2 向foo.lock发送SETNX获取锁。
C3 向foo.lock发送DEL命令,此时C3发送DEL时,其实DEL掉的是C2的锁。
C3 向foo.lock发送SETNX获取锁。
此时C2和C3都获取了锁,产生竞争条件,如果在更高并发的情况,可能会有更多客户端获取锁。所以,DEL锁的操作,不能直接使用在锁超时的情况下,幸好我们有GETSET方法,假设我们现在有另外一个客户端C4,看看如何使用GETSET方式,避免这种情况产生。
C1获取锁,并崩溃。C2和C3调用SETNX上锁返回0后,调用GET命令获得foo.lock的时间戳T1,通过比对时间戳,发现锁超时。
C4 向foo.lock发送GESET命令,
GETSET foo.lock
并得到foo.lock中老的时间戳T2
如果T1=T2,说明C4获得时间戳。
如果T1!=T2,说明C4之前有另外一个客户端C5通过调用GETSET方式获取了时间戳,C4未获得锁。只能sleep下,进入下次循环中。
现在唯一的问题是,C4设置foo.lock的新时间戳,是否会对锁产生影响。其实我们可以看到C4和C5执行的时间差值极小,并且写入foo.lock中的都是有效时间错,所以对锁并没有影响。
为 了让这个锁更加强壮,获取锁的客户端,应该在调用关键业务时,再次调用GET方法获取T1,和写入的T0时间戳进行对比,以免锁因其他情况被执行DEL意 外解开而不知。以上步骤和情况,很容易从其他参考资料中看到。客户端处理和失败的情况非常复杂,不仅仅是崩溃这么简单,还可能是客户端因为某些操作被阻塞 了相当长时间,紧接着 DEL 命令被尝试执行(但这时锁却在另外的客户端手上)。也可能因为处理不当,导致死锁。还有可能因为sleep设置不合理,导致Redis在大并发下被压垮。 最为常见的问题还有
AOF重写带有子进程副本保证安全
Java虚拟机
Java内存结构,分区,每个区放置什么
程序计数器:(线程私有)当前线程所执行的字节码的行号指示器,通过改变这个计数器的值来选取下一条需要执行的字节码的指令,以程序中分支、循环和跳转等流程的控制都离不开这个计数器的指示。
虚拟机栈:(线程私有),每个方法在执行时都会创建一个栈桢,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。一个方法从调用到执行完成的过程,对应的栈桢在虚拟机栈的进出过程。当线程结束时,虚拟机栈中的数据会被自动的释放。
局部变量表:基本数据类型、对象的引用、返回地址,局部变量表锁需要的内存空间是在程序编译时就已经会被确定好的。
本地方法栈:(线程私有)虚拟机栈是为执行java方法所服务的,而本地方法栈是为了虚拟机使用到的本地方法锁服务的。
堆区:(线程共享)java堆是被所有的线程所共享的一片区域,所有的对象的实例和数组都会在堆区尽心分配。java堆细分:新生代和老年代;也可能会划分出多个线程锁共享额分配缓冲区TLAB;
Java堆可以在物理上不连续的内存空间中,只要逻辑上连续就可以。
方法区:(线程共享)存储已经被虚拟机加载过的类的信息、常量、静态变量和及时编译器编译后的代码。在方法区中一个区域叫做:运行时常量池,用于存放编译后生成的字面量和符号的引用。
堆的分代
(1)年轻代:
所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个Survivor区(一般而言)。
大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当一个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当另一个Survivor区也满了的时候,从前一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。
(2)年老代:
在年轻代中经历了N(可配置)次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
(3)持久代:
用于存放静态数据,如 Java Class, Method 等。持久代对垃圾回收没有显著影响。
OOM异常的处理思路
对象的创建方法,对象的内存的分配,对象的访问定位
对象的创建:
(1)第一步,当遇到一个new的指令,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有,需要先执行相应的类加载过程;
(2)第二步,根据类加载完成后确定的内存大小,为对象分配内存;
(3)第三步,需要对分配到的内存空间都初始化为零值;
(4)第四步,虚拟机要对对象设置一些基本信息,如对象是那个类的实例、对象的哈希码、对象的GC分代年龄信息、如何才能找到类的元数据信息等,到这里虚拟机创建对象的工作已经完成;
(5)第五步,从程序的角度,我们还需要对对象进行初始化操作。
对象的内存分配:
(1)对象头:
存储hashcode 、gc分代年龄以及一些必要的自身的运行时数据
(2)实例数据:
存储真实的数据信息
(3)对齐填充:
仅仅起到占位符的作用
对象的访问定位:
通过句柄池的访问,在句柄池中保存着到对象实例数据的指针以及到对象类型的数据的指针
通过直接的指针服访问,通过引用直接指向java堆的对象的实例数据
GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别用在什么地方,如果让你优化收集方法,有什么思路?
标记清除法:
就是先标记哪些对象实例不用,然后直接清除。缺点就是产生大量的内存碎片,下次若要存储一个大的对象,无法找到连续内存而又必须提前GC
标记整理:
也就是先标记,然后对存活的对象进行移动,全部移动到一端,然后再对其它的内存进行清理。
复制算法:
把内存分成相等的AB两块,每次只使用其中的一块。比如当A内存使用完后,就把A中还存活着的对象复制到另外一块内存中去(B),然后再把已经使用过的内存清理掉。优点:这样就不用考虑内存碎片的问题了。缺点:内存减半,代价略高。
GC收集器有哪些?CMS收集器与G1收集器的特点。
对于新生代的收集器:
Serial单线程收集器 parnew多线程收集器 parallelSccavenge收集器
对于老年代的收集器:
CMS并发收集低停顿收集器 serial Old单线程收集器 parallel Old多线程收集器
CMS收集器:
优点:并发收集、低停顿
缺点:
(1)对cpu资源非常的敏感,在并发的阶段虽然不会导致用户的线程停顿,但是会由于占用一部分的线程导致应用程序变慢,总的吞吐量会降低;
(2)无法去处理浮动垃圾;
(3)基于“标记-清除”算法的收集器,所以会出现碎片。
G1收集器:
优点:
(1)能充分利用cpu、多核的优势,使用多个cpu缩短停顿的时间;
(2)分代收集,不要其他收集器的配合便可以独立管理整个的GC堆;
(3)空间整合:整体基于“标记-清理”算法的实现,局部是基于“复制”算法的实现;
(4)可以预测的停顿
Minor GC、Full GC分别在什么时候发生?
Minor GC:新生代GC,当jvm无法为一个新的对象分配空间时会触发
Full GC:整个堆空间的GC
类加载的五个过程:加载、连接、初始化。
类的加载:将类的class文件读入内存,并创建一个叫做java.lang.Class对象,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。
这些类的class文件的来源:
(1)从本地文件系统中加载class文件
(2)从jar包中加载class文件,比如jdbc编程时
(3)通过网络加载class文件
(4)把一个java源文件动态编译,并执行加载
连接:
(1)验证:验证阶段用于检验被加载的类是否具有正确的内部结构,并和其他的类协调一致
(2)准备:为类的类变量分配内存,并去设置默认的值
(3)解析:将类的二进制数据中的符号引用替换成直接引用。
初始化:
主要是对类变量进行初始化。
(1)如果该类还没有被加载和连接,则先进行加载连接
(2)如果该类的直接父类还没有被初始化,则先初始化其直接父类
(3)类中如果有初始化的语句则先去执行这些初始化语句。
反射
概念:在运行的状态中,对于任何一个类或者对象,可以知道其任意的方法和属性,这种动态地调用其属性和方法的手段叫做反射。利用的反编译的手段
一、通过三种方式来获取Employee类型,获取类:
(1)Class c1 = Class.forName(“Employee”);
(2)Class c2 =Employee.class;
(3)Employee e = new Employee(); Class c3 = e.getClass();
二、得到class的实例:
Object o = c1.newInstance();
三、获取所有的属性
Field[] fs = c.getDeclaredFields();
四、获取所有的方法
GetDeclareMethods();
动态代理
J2se
多线程(线程锁)
线程的状态:
新建状态、就绪状态、运行状态、阻塞状态、死亡状态(线程状态转换图)
多线程的创建和启动:
(1)继承Thread类,重写类的run方法,调用对象的start方法启动
(2)实现Runnable接口,并重写该接口的run方法,该方法同样是线程的执行体,创建runnable实现类的实例,并以此实例作为Thread类的target来创建thread对象,该thread对象才是真的线程对象。
(3)使用Callable和Future接口创建线程。具体是创建Callable接口的实现类,并实现clall()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。
方法3:
// 创建MyCallable对象
Callable myCallable = new MyCallable();
//使用FutureTask来包装MyCallable对象
FutureTask ft = new FutureTask(myCallable);
//FutureTask对象作为Thread对象的target
Thread thread = new Thread(ft);
//线程进入到就绪状态
thread.start();
线程同步的方法:sychronized、lock、reentrantLock等
synchronized修饰同步监视器:修饰可能被并发访问的共享资源充当同步监视器;
synchronized修饰方法,同步方法的同步监视器是this,也就是调用该方法的对象;
synchronizedd可以用来修饰方法,可以修饰代码块,但是不能修饰构造器和成员变量;
使用lock锁对象,每次只能有一个线程对lock对象进行加锁和释放锁,线程开始访问该锁对象时必须先获得锁lock
基本用法:
Private final ReentrantLock lock = new ReentrantLock();
Lock.lock();
Try(){
}catch(Exception e){
}finally{}
Lock.unlock();
锁的等级:内置锁、对象锁、类锁、方法锁。
内置锁:每一个java对象都可以用做一个实现同步的锁,这个锁成为内置锁。当一个线程进入同步代码块或者方法的时候会自动获得该锁,在退出同步代码块或者方法时会释放该锁。
获得内置锁的方法:进入这个锁的保护的同步代码块或者方法
注意:java内置锁是一个互斥锁,最多只有一个线程能够获得该锁。
对象锁:对象锁是用于对象实例方法,或者一个对象实例上的。
类锁:类锁用于类的静态方法或者一个类的class对象上,一个类的对象实例有多个,但是每个类只有一个class对象,即不同对象实例的对象锁是互不干扰的,每一个类都有一个类锁。类锁只是概念上的,并不是真实存在的。
方法锁:synchronized修饰方法,同步方法的同步监视器是this,也就是调用该方法的对象;
ThreadLocal的设计理念与作用。
作用:
ThreadLocal类只能去创建一个被线程访问的变量,如果一段代码含有一个ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的ThreadLocal变量。
创建ThreadLocal的方式:
private ThreadLocal myThreadLocal = new ThreadLocal();
我们可以看到,通过这段代码实例化了一个ThreadLocal对象。
我们只需要实例化对象一次,并且也不需要知道它是被哪个线程实例化。
虽然所有的线程都能访问到这个ThreadLocal实例,但是每个线程却只能访问到自己通过调用ThreadLocal的set()方法设置的值。即使是两个不同的线程在同一个ThreadLocal对象上设置了不同的值,他们仍然无法访问到对方的值。
如何为ThreadLocal对象赋值和取值:
一旦创建了一个ThreadLocal变量,你可以通过如下代码设置某个需要保存的值:
myThreadLocal.set("A thread local value”);
可以通过下面方法读取保存在ThreadLocal变量中的值:
String threadLocalValue = (String) myThreadLocal.get();
get()方法返回一个Object对象,set()对象需要传入一个Object类型的参数。
初始化该ThreadLocal变量:
通过创建一个ThreadLocal的子类重写initialValue()方法,来为一个ThreadLocal对象指定一个初始值。
ThreadPool用法与优势。
优势:
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。但是要做到合理的利用线程池,必须对其原理了如指掌。
用法:
线程池的创建:
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler);
参数:corePoolSize:线程池的基本大小
maximumPoolSize:线程池的最大大小
runnableTaskQueue:任务队列
keepAliveTime:线程活动保持时间
执行方式:
threadsPool.execute(handler);
threadsPool.submit(handler);
线程池的关闭:
Shutdown和shutdownNow方法实现
线程池的工作流程分析:
先将任务提交的顺序为核心线程池、队列、线程池、当这三个关节都不能执行用户所提交的线程时,则抛出“无法执行的任务”。
字节流和字符流
(1)java中字节流处理的最基本的单位是单个字节。通常用来处理二进制数据,最基本的两个字节流类是InputStream和OutputStream,这两个类都为抽象类。
字节流在默认的情况下是不支持缓存的。每次调用一次read方法都会请求操作系统来读取一个字节,往往会伴随一次磁盘的IO,如果要使用内存提高读取的效率,应该使用BufferedInputStream。
(2)字符流处理的最基本的单元是unicode(码元),通常用来来处理文本数据。
输入字符流(文件到内存):把要读取的字节序列按照指定的编码方式解码为相应的字符序列,从而可以存在内存中。
输出字符流(内存到文件):把要写入文件的字符序列转为指定的编码方式下的字节序列,然后写入文件中。
区别如下:
1、字节流操作的基本单元为字节;字符流操作的基本单元为Unicode码元。
unicode的编码范围:0x0000~0XFFFF,在这个范围的每个数字都有一个字符与之对应
2、字节流默认不使用缓冲区;字符流使用缓冲区。
3、字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元;字符流通常处理文本数据,它支持写入及读取Unicode码元。
序列化(常见的序列化操作)
含义:
java序列化:将java对象转换为字节序列的过程;
java反序列化:将字节序列恢复为java对象的过程
序列化的目的:
实现数据的持久化,将数据永久地保存在磁盘上,通常放在文件中;
利用序列化实现远程的通讯,在网络上传送对象的字节序列.
实现序列化的三种方法:
(1)某实体类仅仅实现了serializable接口(常用)
序列化步骤:
步骤一:创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流:
ObjectOutputStream out = new ObjectOutputStream(new fileOutputStream(“D:\objectfile.obj”));
步骤二:通过对象输出流的writeObject()方法写对象:
//Hello对象的字节流将输入到文件
out.writeObject(“Hello”);
反序列化步骤:
步骤一:创建一个对象输入流,它可以包装一个其它类型输入流,如文件输入流:
ObjectInputStream in = new ObjectInputStream(new fileInputStream(“D:\objectfile.obj”));
步骤二:通过对象输出流的readObject()方法读取对象:
//将从文件中读取到字节序列转化为对象
String obj1 = (String)in.readObject();
(2)若实体类仅仅实现了Serializable接口,并且还定义了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out),则采用以下方式进行序列化与反序列化。
ObjectOutputStream调用该对象的writeObject(ObjectOutputStream out)的方法进行序列化。
ObjectInputStream会调用该对象的readObject(ObjectInputStream in)的方法进行反序列化。
(3)若Student类实现了Externalnalizable接口,且Student类必须实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,则按照以下方式进行序列化与反序列化。
ObjectOutputStream调用Student对象的writeExternal(ObjectOutput out))的方法进行序列化。
ObjectInputStream会调用Student对象的readExternal(ObjectInput in)的方法进行反序列化。
String,StringBuffer,StringBuilder的区别,应用场景
1)在执行的速度上:StringBuilder>StringBuffer>String
2)String是字符串常量 StringBuffer和StringBuilder是字符串变量
例子1:
String s = “abcd”;
s=s+1;
Syos(s);
底层执行:首先创建一个对象s,赋予abcd.然后又创建新的对象s,之前的对象并没有发生变化,利用string操作字符串时,是在不断创建新的对象,而原来的对象由于没有了引用,会被GC,这样执行的效率会很低。
例子2:
String str2 = “This is only a”;
String str3 = “ simple”;
String str4 = “ test”;
String str1 = str2 +str3 + str4;
同理:str2 str3 str3没有被引用,但是创建了新的对象str1,执行速度上会很慢。
StringBuilder:线程非安全的
StringBuffer:线程安全的
例子3:
StringBuffer builder = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);
应用场景:
A:使用要操作少量的数据时,使用String
B:单线程操作字符串缓冲区下操作大量数据使用StringBulider
C:多线程操作字符串缓冲区下操作大量的数据使用StringBuffer
HashMap和HashTable的区别
HashMap是线程不安全的;允许有null的键和值;执行的效率高一点;方法不是synchronize的要提供外同步;包含有containsvalue和containskey的方法
HashTable是线程安全的;不允许有null的键和值;效率稍微低些;方法是synchronize的;包含contains方法
GC垃圾回收机制原理
(参看博客)
== 与 equals区别
:对于基本数据类型的变量,直接比较存储的值是否相等;作用于引用类型的变量,则比较的是该变量所指向的地址是否相同。
equals:不同作用于基本数据类型的变量,如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址(相当于直接使用父类的equals方法,而该方法则是用进行的比较,所以结果和用比较的效果是一样的);但是比如String Date类对equals进行了重写,比较的是字面量。
final关键字
对于基本的数据类型,使用final关键字将使得数值恒定不变;
对于对象引用,final则是引用恒定不变,一但被初始化指向一个对象,它就不会再指向另外一个对象,但是该对象本身是可以被修改的;
对于类,如果不想继承某个类,可以将该类设置为fianl形式,该类不会有子类;
对于方法,final修饰的方法不会被重写
对于空白的final,对于没有给定初始值的fianl,编译器会在使用前初始化该final修饰的变量
对于宏变量:被final修饰的变量为宏常量 在编译的阶段被其本身的值直接替换
short s1=1;s1 = s1+1;
表达式类型的自动提升,一个short类型的变量和一个int型的数在一起进行运算,会将short类型的数隐式转换为int参与运算,但是该运算的结果为int类型是不同直接赋值给一个short类型的,必须进行强制的类型转换,否则编译是通不过的。
八种基本数据类型的大小,以及他们的封装类。
类型转换:byte (1字节)—>short(1)/char(2)—>int(4)—>long(8)—>float(4)—>double(8)
分装类:Byte Short Character Integer Long Float Double
Switch能否用string做参数?(分版本讨论)
(1)在jdk1.7版本前不支持string作为参数,仅仅支持byte、short、char,因为可以转换为int,但是long和string不能转换为int,所以不能使用。
(2)在jdk1.7之后,支持使用string作为case的参数,实际匹配的是该字符串的hash值,然后用equals进行安全性检查。Switch支持String其实是一个语法糖,在编译后的字节码文件中都会被还原成原生的类型,并在相应的位置插入强制转换的代码,底层的JVM在switch上并没有修改;当传入switch是null时,在运行时对一个null调用hashcode()方法,会抛出空指针异常。
Object有哪些公用方法?
object是所有类的父类,任何类都默认继承Object类
9、Clone
private保护方法,实现对象的浅复制,只有类实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportException
10、Equals
在object中与==是一样的,子类一般需要重写该方法
11、hashCode
该方法用于哈希查找,重写了equals方法一般都要重写hashcode方法,这个方法在一些具有哈希功能的collection中使用
12、getClass
final方法,获得运行时的类型
13、wait方法
使得当前的线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。Wait方法会一直等待,直到获得锁(到了睡眠的时间间隔也会唤醒自己)或者被中断掉。
调用该方法,当前的线程会进入到睡眠的状态,直到调用该对象的notify方法、notifyAll方法、调用interrupt中断该线程,时间间隔到了。
14、Notify
唤醒在该对象上的等待的某个线程
15、notifyAll
唤醒在该对象上的等待到所有的线程
16、toString
把对象转换成string类型进行输出
Java的四种引用,强弱软虚,用到的场景。
引用的级别:
强引用>软引用>弱引用>虚引用
强引用:如果一个对象具有强引用,垃圾回收器绝对不会回收它。当内存空间不足时,jvm宁愿抛出outofmemoryError,使得程序的异常终止。
软引用:如果一个对象具有软引用,则内存空间足够,垃圾回收机制就不会去回收它,当内存不足时,就会进行回收。如果软引用所引用的对象被垃圾回收器回收,java虚拟机就会把这个软引用加入到与之关联的引用队列。
应用场景:实现内存敏感的高速缓存
弱引用:在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
应用场景:gc运行后终止
虚引用:就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
写出生产者消费者模式。
Hashcode的作用。
(1)Hashcode 的存在主要用于解决查找的快捷性,比如在hashmap、hashtable中,hashcode是用来在散列的存储结构中确定对象的存储的位置的。
(2)如果两个对象相同,就是通过equals方法比较返回true,两个对象装在一个桶里,也就是hashcode也要一定相同。
(3)两个对象的hashcode相同,并不一定代表两个对象就是相同的,只能说明他们存储在一个桶里。
(4)一般重写了equals方法,也尽量去重写hashcode方法,保证先找到该桶,再去找到对应的类,通过equals方法进行比较。
ArrayList、LinkedList、Vector的区别。
1、ArrayList:是基于动态数组的数据结构,LinkedList的基于链表的数据结构
2、对于随机访问get和set,ArrayList性能较好,因为LinkedList会去移动指针
3、对于新增和删除的操作,linkedList只需要修改指针的指向,性能较好,但是arrayList会移动数据。
vector的特点:
1、vector的方法都是线程同步的,是线程安全的,但是arraylist和linkedlist不是,由于线程的同步必然会影响性能,所以vector的性能不太高。
2、当vector或者arraylist的元素超过它的初始的大小时,vector会将容量翻倍,但是arraylist只会增加50%,这样有利于节约内存的空间。
Map、Set、List、Queue、Stack的特点与用法。
HashMap和ConcurrentHashMap的区别
hashMap不是线程安全的;
concurrentHashMap是线程安全的;在其中引入了“分段锁”,而不是将所有的方法加上synchronized,因为那样就变成了hashtable.
所谓“分段锁”,就是把一个大的Map拆分成N个小的hashtable,根据key.hashcode()决定把key放在哪一个hashtable中。
通过把整个Map分为N个Segment(类似HashTable),可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。
TreeMap、HashMap、LindedHashMap的区别。
HashMap:根据键的hashcode值进行存储数据,根据键可以直接获取它的值,具有快速访问的特点,遍历时取得数据是随机的,hashmap最多只允许一条记录的键为null(set无序不重复),允许多条记录的值为Null;如果要保证线程的同步,应该使用Collections.synchronizedMap()方法进行包装,或者使用ConcurrentHashMap
LinkedHashMap:保存了记录的插入的顺序,在迭代遍历Linkedhashmap时,先得到的记录肯定是先插入的,它遍历的速度只和实际的数据有关和容量没关。
TreeMap:实现的是SortMap,能够把保存的记录按照键进行排序,默认会按照键值的升序进行排序,当遍历TreeMap时得到的记录是排序过后的。
Collection包结构,与Collections的区别。
Collection是一个集合的接口,提供了对集合对象进行操作的通用的方法。
在它下面的子接口:set、list、map
java.util.Collections是一个包装的类,包含有各种的有关集合操作的静态方法,比如包含对集合的搜索、排序、线程安全化等一系列的操作,此类不能被实例化,相当于是操作集合的工具类,服务于java的collection的框架。
介绍下Concurrent包
concurrent包基本有3个package组成
(1)java.util.concurrent:提供大部分关于并发的接口和类,如BlockingQueue,Callable,ConcurrentHashMap,ExecutorService, Semaphore等
(2)java.util.concurrent.atomic:提供所有原子操作的类, 如AtomicInteger, AtomicLong等;
(3)java.util.concurrent.locks:提供锁相关的类, 如Lock, ReentrantLock, ReadWriteLock, Condition等;
concurrent包的优点:
-
首先,功能非常丰富,诸如线程池(ThreadPoolExecutor),CountDownLatch等并发编程中需要的类已经有现成的实现,不需要自己去实现一套; 毕竟jdk1.4对多线程编程的主要支持几乎就只有Thread, Runnable,synchronized等
-
concurrent包里面的一些操作是基于硬件级别的CAS(compare and swap),就是在cpu级别提供了原子操作,简单的说就可以提供无阻塞、无锁定的算法; 而现代cpu大部分都是支持这样的算法的;
Try-catch -finally,try里有return,finally还执行么?
任然会执行。
1、不管有木有出现异常,finally块中代码都会执行;
2、当try和catch中有return时,finally仍然会执行;
3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
Excption与Error包结构。OOM你遇到过哪些情况,SOF你遇到过哪些情况。
Java面向对象的三个特征与含义。
封装:
是指将某事物的属性和行为包装到对象中,这个对象只对外公布需要公开的属性和行为,而这个公布也是可以有选择性的公布给其它对象。在Java中能使用private、protected、public三种修饰符或不用(即默认defalut)对外部对象访问该对象的属性和行为进行限制。
继承:
是子对象可以继承父对象的属性和行为,亦即父对象拥有的属性和行为,其子对象也就拥有了这些属性和行为。
多态:
java的引用变量有两种类型,一个是编译时的类型,一个是运行时的类型,编译时类型由申明该变量时的类型决定,运行时的类型由实际赋值给该变量的对象所决定,如果编译时的类型和运行时的类型不一致就可能出现所谓的多态。
在java中把一个子类的对象直接赋值给一个父类的引用变量,当运行该引用变量的方法时,其方法行为总是表现出子类方法的行为特征,这就有可能出现,相同类型的变量,调用同一个方法时呈现多种不同的行为特征,出现了“多态”
Override和Overload的含义和区别。
Overload:方法重载,在同一个类中,方法名相同,参数列表不同,至于方法的修饰符,反回值的类型,与方法的重载没有任何的联系。
Override:方法重写,两同两小一大
两同:方法名称相同、参数列表相同
两小:返回值类型要小或者相等;抛出的异常要小或者相等
一大:子类方法的访问权限要相等或者更大
Interface与abstract类的区别。
实例化:
都不能被实例化
类:一个类只能继承一次abstract类;一个类可以实现多个interface
数据成员:可以有自己的;接口的数据成员必须定义成static final的
方法:可以有私有的,非abstract方法必须实现;接口中不可以有私有的方法,默认都是public abstract的
变量:可以有私有的,其值可以在子类中重新定义,也可以重新赋值;接口中不可以有私有的成员变量,默认是public static final 实现类中不能去重新定义和改变其值
Java IO与NIO
IO是面向流的,NIO是面向缓冲区
Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
阻塞与非阻塞IO
Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
选择器(Selectors)
Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。
反射的作用原理。
见博客
wait()和sleep()的区别。
(1)sleep方法,该方法属于thread类;wait方法属于object类
(2)sleep方法导致程序会暂停执行指定的时间,让出cpu给其他的线程,但是他还是监控状态的保持者,当到达指定的时间又会自动恢复运行。也就是调用sleep方法线程不会释放对象锁;调用wait方法会释放对象锁,进入到等待此对象的等待锁定池,只有当针对此对象调用了notify()方法后,才会获取对象锁进入运行的状态。
foreach与正常for循环效率对比。
For循环可以从前向后遍历,也可以从后向前遍历,可以不逐个遍历,通常用于已知次数的循环。
foreach循环不能向迭代变量赋值,通常对集合对象从头到位进行读取,其有优化的存在。
Java与C++对比。
1、指针
java语言不提供指针,增加了自动的内存管理,有效的防止c/c++中的指针操作失误。
2、多重继承
C++支持多重继承,java不支持多重继承,但是允许实现多个接口。
3、数据类型和类
java将数据和方法结合起来,分装到类中,每个对象都可以实现自己的特点和方法;而c++允许将函数和变量定义全局的。
4、内存管理
java可以对所有的对象进行内存管理,自动回收不再使用的对象的内存;c++必须由程序员显式分配内存释放内存。
5、操作符的重载
C++支持操作符的重载,java不允许进行操作符的重载。
6、预处理功能
java不支持预处理功能,c++有一个预编译的阶段,也就是预处理器。
7、字符串
C++不支持字符串,java中支持字符串,是java的类对象。
8、数组
java引入了真正的数组,不同于c++中利用指针实现的伪数组。
9、类型的转换
C++中有时会出现数据类型的隐含转换,设计到自动强制类型的转换问题,比如存在将浮点数直接转换为整数的情况,java不支持自动的强制类型转换,如果需要,必须显示进行强制的类型转换。
10、异常
java中使用try{}catch(){}finally{}进行异常的处理,c++没有。
HTTP和HTTPS的区别
https:是http的安全版本,利用ssl可以对所传输的数据进行加密,默认端口是443
cookie和session的区别
(1)cookie数据存放在客户的浏览器上,session数据放在服务器上。
(2)cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,如果主要考虑到安全应当使用session。
(3)session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用COOKIE。
(4)单个cookie在客户端的限制是4K,就是说一个站点在客户端存放的COOKIE不能4K。
(5)所以:将登陆信息等重要信息存放为SESSION;其他信息如果需要保留,可以放在COOKIE中
网路
TCP三次握手、四次挥手,各个状态的名称和含义timewait的作用?
ACK:tcp协议规定,只有ack=1时才有效,在连接建立后所有发送的豹纹的ack=1
Syn(SYNchronization):在连接建立时用来同步序号。
当SYN=1而ACK=0:这是一个连接请求报文;
当对方同意建立连接时,则应该使得SYN=1而且ACK=1;
当SYN=1:这是一个连接请求或者连接接受报文
FIN(finis):终结的意思,用来释放一个连接。当fin=1,表示次报文段的发送方的数据已经发送完毕并要求释放连接。
A的状态:关闭状态—>同步已发送—>已建立
B的状态:关闭状态—>监听状态—>同步收到—>已建立
A:建立状态—>终止等待1—>终止等待2—>等待2MSL
B:建立状态—>关闭等待—>最后确认
Timewait的作用?
(1)为了保证A发送最后一个ACK报文能到达B,因为这个ACK报文有可能会丢失,这样会使得处在最后确认阶段的B收不到已经发送的FIN+ACK的确认信息,B会超时重传该报文段,在2MSL的时间内,A会收到信息,重传依次确认,重启该计时器。
(2)保证在2MSL的时间内,所有在本网络上产生的报文段都消失,使得在新的连接中不会出现旧的连接请求的报文段。
(2)SYN攻击防范
TCP/IP层次架构,每层的作用和协议
OSI模型:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层
TCP/IP模型:应用层、传输层、网络互联层、主机到网络层
协议:
(1)应用层:FTP、TELNET、HTTP | SNMP、TFTP、NTP
将OSI模型的会话层和表示层整合成应用层,应用层面向不同的网络应用引入了不同的应用层协议。
(2)传输层:TCP|UDP
功能是使得源端主机和目标端主机上的对等实体可以进行会话,定义了两种服务质量不同的协议,分别是TCP和UDP协议。
TCP协议是一个面向连接的、可靠的协议。它将一台主机发出的字节流无差错地发往互联网上的其他主机。在发送端,它负责把上层传送下来的字节流分成报文段并传递给下层。在接收端,它负责把收到的报文进行重组后递交给上层。TCP协议还要处理端到端的流量控制,以避免缓慢接收的接收方没有足够的缓冲区接收发送方发送的大量数据。
UDP协议是一个不可靠的、无连接协议。主要适用于不需要对报文进行排序和流量控制的场合。
(3)网络互联层:IP
网络互联层是整个TCP/IP协议栈的核心。功能是把分组发往目标网络或者主机。为了尽快发送分组,可能会沿着不同的路径同时进行分组传递。因此,分组到达的顺序和发送的顺序可能会不一致,这就需要上层必须对分组进行排序。同时它可以将不同类型的网络进行互联,完成拥塞控制的功能。
(4)主机到网络层:以太网、令牌环网、PPP
该层未被定义,具体的实现方式随着网络类型的不同而不同。
TCP拥塞控制
拥塞:计算机网络中的带宽、交换节点中的缓存和处理机都是网络中的资源,当在某一个时间,对网络中的某一个资源的需求超出了该资源所能提供的部分,网络的性能会变坏,就出现了拥塞。
拥塞控制:防止过多的数据注入到网路,使得网络中的路由器和链路不至于过载。拥塞控制是一个全局的过程,和流量控制不同,流量控制是点对点的通信量的控制。
慢开始和拥塞避免:
发送方维持一个叫做拥塞窗口的状态变量,拥塞窗口取决于网络的拥塞程度,并且会动态的变化。发送方让自己的发送窗口等于拥塞窗口,考虑接受方的接受能力,发送窗口可能会小于拥塞窗口。
慢开始算法:不要一开始就发送大量的数据,先探测下网络的拥塞程度,由小到大逐渐增加拥塞窗口的数量。
拥塞避免算法:让拥塞窗口缓慢增长,每进过一个往返时间就把发送方的拥塞窗口cwnd+1,而不是加倍,此时拥塞窗口按照线性的规律缓慢增长。
结合使用:为了防止拥塞窗口增长过大引发网络的拥塞,设置一个慢开始门限ssthresh状态变量。其用法:
当cwnd<ssthresh,使用慢开始算法
当cwnd>ssthresh,使用拥塞避免算法
当cwnd=ssthresh,慢开始算法和拥塞避免算法随意。
当遇到网络拥塞时,就把慢开始门限设置为出现拥塞时发送窗口大小的一半,同时将拥塞的窗口设置为1,再重新开始执行慢开始算法。
滑动窗口是什么设计的?
窗口:是一段可以被发送者发送的字节序列,其连续的范围称为“窗口”
滑动:这段“允许发送的范围”是随着发送的过程而不断变换的,表现的形式就是“按照顺序滑动”
流量控制:
(1)TCP利用滑动窗口实现流量的控制机制
(2)如何考虑流量控制中的传输效率
流量控制,接受方传递信息给发送方,使其发送数据不要太快,是一种端到端的控制,主要的方式是返回的ack中会包含自己的接受的窗口的大小,发送方收到该窗口的大小时会控制自己的数据发送。
传递效率:单个发送字节单个确认,和窗口有一个空余即通知发送方发送一个字节,会增加网络中许多不必要的报文,因为会为一个字节数据添加40个字节的头部。
TCP/UDP的区别
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP首部开销20字节;UDP的首部开销小,只有8个字节
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠
TCP报文结构
紧急比特URG:URG=1,注解该报文应该尽快送达,而不需要按照原来的的队列次序依次送达
确认比特ACK:只有当ACK=1,确认序号字段才有意义
急迫比特PSH:当PSH=1时,注解恳求远地TCP将本报文段立即传送给应用层
复位比特RST:当注解呈现严重错误时,必须开释连接,进行重新的传输连接
同步比特SYN:当SYN=1而ACK=0时,这是一个连接请求报文段,若对方赞成连接请求会将SYN=1而且ACK=1
终止比特FIN:当FIN=1,注解字符串已经发送完毕,并请求开释传输连接。
HTTP的报文结构(请求报文+响应报文)
HTTP请求报文:(1)请求行+(2)请求头部+(3)请求正文
(1)请求行:请求方法+URL+协议版本
请求方法:常用GET、POST
协议版本:HTTP/主版本号.次版本号 常用HTTP/1.0和HTTP/1.1
(2)为请求报文添加的一些附加的信息,“名/值”组成,并且是每行一对 用冒号进行分割
在请求头部存在空行,表示请求头部的结束,接下来是请求正文!
区别get和post方式
对于get方式没有请求的正文,对于post方式有请求的正文。
HTTP响应的报文格式:
(1)状态行+(2)响应头部+(3)响应正文
(1)状态行:协议版本+状态码+状态码描述
(2)响应头部:也是由键值对所组成
(3)响应正文,由服务器端接受数据
HTTP状态码的含义
http request的几种类型(8种)
(1)OPTIONS:返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送’*'的请求来测试服务器的功能性。
(2)HEAD:向服务器索要与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。
(3)GET:向特定的资源发出请求。
(4)POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的创建和/或已有资源的修改。
(5)PUT:向指定资源位置上传其最新内容。
(6)DELETE:请求服务器删除Request-URI所标识的资源。
(7)TRACE:回显服务器收到的请求,主要用于测试或诊断。
(8)CONNECT:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
GET方式和POST方式对比:
GET方式:请求数据放在HTTP包头;使用明文传送,不安全;长度较小,一般为1024B;应用的场景为查询数据;如果传送的是英文数字或者是数字,直接发送,如果传送的是中文字符或则是其他的字符,则会进行BASE64编码
POST方式:请求数据放在HTTP正文;可明文或者密文传送,较为安全;长度一般没有限制;应用在修改数据上。
http1.0 和 http1.1的区别
(1)HTTP1.0 规定浏览与服务器只是保持短暂的连接,浏览器每次请求都需要和服务器建立一个TCP连接,服务器完成请求处理后立即断开TCP连接,服务器不去跟踪每个客户也不去记录每个客户过去的请求。HTTP1.0 不支持HOST请求字段
(2)HTTP1.1支持久连接,在一个TCP上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟;
允许客户端不用等待上一次请求的返回结果,就可以 去发送下一个请求,但是服务器端必须按照接受的客户端请求的先后顺序依次会送响应的结果,这样客户端才能够区分出每次请求的响应的内容。
HTTP1.0 支持HOST请求字段,这样就可以使用一个IP地址和端口号,在此基础上使用不同的主机名创建多个虚拟的WEB站点;
HTTP1.1 提供了与身份认证、状态管理和cache缓存等机制
http怎么去处理长连接
http1.1 默认支持长连接,即一次tcp连接,允许发送多个http请求。
当web服务器看到keep-alive值时,会建立长连接。
电脑上访问一个网页的整个过程是怎样的?DNS、HTTP、TCP、OSPF、IP、ARP
步骤1:当访问www.baidu.com时,会先从本地的host文件中获取该域名对应的IP地址,如果找不到就会用DNS协议来获取IP,在该DNS协议中,计算机会由本地的DNS服务器来解析该域名,最终找到对应的IP地址。
步骤2:接下来是使用TCP协议,建立TCP连接,在建立连接之前需要,为了将给服务器的消息带给服务器,则需要OSPF\IP\ARP协议的支持,IP告诉该消息从哪里出发,去向那里;消息的传送会经过一个个的路由器,OSPF会利用路由算法找出最佳的通往目的地址的路径;ARP负责找到下一个节点的地址,ARP协议使用的MAC地址,整个的发送的过程涉及到每一个节点的MAP地址。
步骤3:通过步骤2的解析IP,现在可以和服务器建立TCP连接了,这时客户端便可以将Http请求数据发送给服务器端。服务器端进行处理,然后以http response的形式发送给客户端。
IP地址的分类
A类地址:1个字节的网络号+3个字节的主机地址 0.0.0.0~126.255.255.255
B类地址:2个字节的网络号+2个字节的主机地址 128.0.0.0~191.255.255.255
C类地址:3个字节的网络号+1个字节的主机地址 192.0.0.0~223.255.255.255
D类地址:多播地址
E类地址:保留为今后使用
路由器和交换机的区别
交换机:为数据桢从一个端口到另外一个端口的转发提供了低时延、低开销的通路,使得任意端口接受的数据帧都能够从其他的端口送出。
路由器:网络连接和路由选择,用于网络层的数据转发。
如何设计一个高并发的系统?
① 数据库的优化,包括合理的事务隔离级别、SQL语句优化、索引的优化
② 使用缓存,尽量减少数据库 IO
③ 分布式数据库、分布式缓存
④ 服务器的负载均衡
设计模式
简单工厂模式
有一个抽象的产品父类将所有的具体的产品抽象出来,达到复用的目的。同时有一个简单工厂维护一个对抽象产品的依赖,在该简单工厂中去负责实例的创建,在该工厂中去实例不同的对象,往往需要利用case判断语句去动态实例化相关的类。
工厂方法模式
创建对象的接口,让子类去决定具体实例化的对象,把简单的内部逻辑的判断,转移到了客户端,让客户端去动态地实例化相关的子类。工厂方法模式克服了简单工厂违背开放-封闭原则的特点。
抽象工厂模式
提供创建一系列相关或者相互依赖对象的接口,而无需指定他们具体的类。
职责链模式
使得多个对象都有机会去处理请求,从而避免请求的 发送者和接受者之间的耦合关系,将这些对象连成一条链,并沿着这条链去传递该请求,直到有一个对象处理它为之。
单例模式
(2)恶汉式的单例模式
利用静态static的方式进行实例化,在类被加载时就会创建实例。
/**
- 饿汉式实现单例模式
*/
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
(6)懒汉式实现单例模式
在被第一次引用时才去创建对象。
/**
- 懒汉式实现单例模式
*/
public class Singleton {
private static Singleton instance;//创建私有的静态变量
private Singleton() {//私有的构造函数
}
// synchronized方法,多线程情况下保证单例对象唯一
public static synchronized Singleton getInstance() {
//如果实例对象为空,就重新去实例化
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
分析:这中方法的实现,效率不高,因为该方法定义为同步的方法。
(7)双重锁实现的单例模式
/**
- DCL实现单例模式
*/
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
// 两层判空,第一层是为了避免不必要的同步
// 第二层是为了在null的情况下创建实例
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
分析:资源的利用率较高,在需要的时候去初始化实例,而且可以保证线程的安全,该方法没有去进行同步锁,效率比较好。
(8)静态内部类实现单例模式
/**
- 静态内部类实现单例模式
*/
public class Singleton {
private Singleton() {
}
//返回实例的方法
public static Singleton getInstance() {
return SingletonHolder.instance;
}
/**
* 静态内部类
*/
private static class SingletonHolder {
//静态私有的实例对象
private static Singleton instance = new Singleton();
}
}
分析:第一次加载类时不会去初始化instance,只有第一次调用getInstance()方法时,虚拟机才会加载内部类,初始化instance
可以保证线程的安全,单例对象的唯一,延迟了单例的初始化。
(9)枚举单例
/**
- 枚举实现单例模式
*/
public enum SingletonEnum {
INSTANCE;
public void doSomething() {
System.out.println("do something");
}
}
分析:枚举实例的创建是线程安全的,即使反序列化也不会生成新的实例,在任何的情况下都是单例的。
适配器模式
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口步兼容而不能一起工作的类变得可以一起工作。
target是我们所期望的接口的类型,包含一个request方法,通过使用adapter去实现该接口,并实现其中的request方法,在adapter中建立一个私有的adaptee对象,在adapter重写的方法中去调用specificRequest方法,这样适配器adapter就构建好了。只需要在客户端,创建adapter实例,调用request方法就可以利用多态的方式,实现了specificRequest()方法。
观察者模式
定义了一种一对多的依赖关系,让多个观察者可以同时去监听某一个主题对象,这个主题对象在状态发生变化时,会通知所有的观察者对象,使得他们能够自动更新自己。
Subject:把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者,可以增加删除观察者对象。
Observer:抽象观察者,为所有的具体的观察者定义一个接口,在得到主题时更新自己。
concreteObserver:具体的观察者,实现更新的方法
concreteSubject:具体的主题
作用:应用在一个对象改变时,需要改变其他的对象,而且具体不知道有多少个对象需要改变,将耦合的双方都依赖于抽象而不是依赖于具体,从而使得各自的变化都不会影响到另外一边的变化。
大整数BigInteger 大浮点数 BigDecimal
java运算的优先级:优先级相同时比较结合性: