Java面试题
总阅读次
- 1. zk分布式锁:
- 2. 谈谈分布式
- 3. redis持久化实现原理
- 4. 生产者消费者
- 5. JVM的内存模型
- 6. JVM体系结构
- 7. 常用GC算法
- 8. 什么情况下出现Full GC,什么情况下出现Young GC
- 9. http1.0,http1.1,http2.0区别
- 10. Get/Post对缓存的影响
- 11. HTTP请求头和响应头
- 12. 通过三种方式编写单例模式
- 13. 编程技术书籍
- 14. NIO核心组件
- 15. 判断对象可收回
- 16. 中文编码问题
- 17. Spring的优缺点
- 18. 单例模式的优缺点
- 19. 唯一索引的作用
- 20. 共享锁和排他锁
- 21. 数据库隔离级别
- 22. 数据库隔离级别出现的问题
- 23. 分布式、集群、负载均衡的理解
- 24. 悲观锁和乐观锁
zk分布式锁:
详情参考:http://www.jiacheo.org/blog/620
1.在单机模式下,没有引入zookeeper时,我们可以通过创建一个临时文件加锁,然后在事务处理完毕后,将临时文件删除就代表解锁。这种加锁和解锁模式可以移植到zookeeper上,通过创建一个路径,来证明该锁已经存在,然后删除路径来释放该锁。而同时zookeeper又能支持对节点的监控,这样一来,我们在多机的情况下就能同时且实时知道锁是存在还是已经解锁了。
2.具体流程:
- 在zookeeper存储结构中,假设有一个lock目录。
- 在该目录下创建自增长的临时节点,这个节点上的数字用于表示获取锁的先后顺序。
- 假设有三台服务器请求zookeeper创建了,00001、00002、00003 三个节点。然后判断lock目录下的最小节点和自己创建的是否一致,如果一致可以获取锁,如果不一致执行等待操作。
- 当获取锁的节点执行完后,删除节点,并通知其他节点,在进行比较。
3、分布式锁出现死锁,发生死锁的情况主要出现在网络的情况下:
- 出现方式
- 假设zk服务收到了请求,子节点创建成功。但是返回给客户端的时候网络发生异常。这时候我们重试,创建的话,就会进入死锁。这里的死锁和平常的死锁不一样,不是回路循环等待,而是相当于这个锁死掉了。因为这个流程其实已经创建了一个节点,但是这个节点没有与客户端关联,称为幽灵节点。
- 解决方法
- 创建节点的时候,将客户端作为节点的一部分,也就是每个节点都有唯一标识的信息。
- 重试的时候,对每个节点进行判断,对于当前进程创建的节点不重复创建。
谈谈分布式
- 互联网应用满足要求:高吞吐、高并发、低延迟、负载均衡
- 高吞吐,意味着你的系统,可以同时承载大量的用户使用。
- 高并发,做尽量多的处理,同时处理多个任务。
- 低延迟,大量用户访问时,也能很快返回计算结果。
- 负载均衡,同时发生的请求,有效的让多个不同服务器承载。
- 分布式系统提高承载量的基本手段
- 分层模型
- 并发模型
- 缓存技术
- 存储技术(NoSql)
redis持久化实现原理
生产者消费者
JVM的内存模型
- 内存空间(Runtime Data Area)中可以按照是否线程共享分成两块,线程共享的是方法区(Method Area)和堆(Heap),线程独享的是Java栈(Java Stack),本地方法栈(Native Method Stack)和PC寄存器(Program Counter Register)。
- 在 JDK 1.7 及以往的 JDK 版本中,Java 类信息、常量池、静态变量都存储在 Perm(永久代)里。类的元数据和静态变量在类加载的时候分配到 Perm,当类被卸载的时候垃圾收集器从 Perm 处理掉类的元数据和静态变量。当然常量池的东西也会在 Perm 垃圾收集的时候进行处理。
JVM体系结构
类加载器
在JVM启动时或者在类运行时将需要的class加载到JVM中。执行引擎
执行引擎的任务是负责执行class文件中包含的字节码指令,相当于实际机器上的CPU内存区
将内存划分成若干个区域以模拟实际机器上的存储,记录和调度功能模块本地方法调用
调用C或C++实现的本地方法的代码返回结果
常用GC算法
- 引用计数法
- 复制法
原理:从根集合开始,通过追踪从From中找到存活对象,拷贝到To中;From和To交换身份,下次内存分配从To开始
优缺点:没有标记和清除的过程,效率高;没有内存碎片,可以快速实现内存分配;但是需要双倍空间 - 标记清除法
原理:分为两个阶段,标记和清除;标记,从根集合开始扫描,对存活的对象进行标记。清除,扫描整个内存空间,回收未被标记的对象。
优缺点:不需要额外空间,但是,两次扫描,耗时严重;会产生碎片 - 标记压缩法
原理:分为两个阶段,标记和压缩;标记,从根集合开始扫描,对存活对象进行标记。压缩,再次扫描,并往一端滑动存活对象。
优缺点:没有内存碎片,但是需要移动对象的成本 - 标记清除压缩法
原理:将标记清除和标记压缩相结合,过程和标记清除一样,但是进行多次GC后才压缩。
优缺点:减少了对象的移动成本
什么情况下出现Full GC,什么情况下出现Young GC
- 对象优先在新生代Eden区分配,如果Eden区没有足够的空间时,就会触发一次young gc
- full gc触发的条件有多个,且full gc的时候会出现stop world。
- 在执行yong gc之前,JVM会进行空间担保-如果老年代的连续空间小于新生代对象的总大小(或历次晋升的平均大小),则触发一次full gc。
- 显示的调用
System.gc()
时 - 大对象直接进入老年代,从年轻代晋升上来的老对象,尝试在老年代分配内存时,但是老年代内存不足时
- 永生区空间不足时
- jmap和jcmd dump文件时候会触发FGC
http1.0,http1.1,http2.0区别
Get/Post对缓存的影响
- 不支持Post Method。Get可以被浏览器缓存。
HTTP请求头和响应头
- HTTP请求头
请求头 | 说明 |
---|---|
Accept-Charset | 用于指定客户端接受的字符集 |
Accept-Encoding | 用户指定可接受的内容编码 |
Accept-Language | 用于指定一种自然语言,如果Accept-Language:zh-cn |
Host | 用于指定被请求资源的Internet主机和端口号 |
User-Agent | 客户端将它的操作系统、浏览器和其他属性告诉服务器 |
Connection | 当前连接是否保持 |
- HTTP响应头
请求头 | 说明 |
---|---|
Server | 使用的服务器名称 |
Content-Type | 用来指定响应的实体正文的类型 |
Content-Encoding | 与请求报头Accept-Encoding对应,告诉浏览器服务器端采用的是什么压缩编码 |
Content-Language | 描述资源所用的自然语言,与Accept-Language对应 |
Content-Length | 指明实体正文的长度,用以字节方式存储的十进制数字来表示 |
Keep-Alive | 保持连接的时间 |
通过三种方式编写单例模式
静态内部类:
123456789public class Singleton{private static class SingletonHolder{private static final Singleton INSTANCE = new Singletion();}private Singletion(){}public static final Singletion getInstance(){return SingletionHolder.INSTANCE;}}枚举
123public enum Singleton{INSTANCE;}饿汉式
1234567public class Singleton{private static Singleton instance = new Singleton();private Singleton(){}public static Singleton getInstance(){return instance;}}编程技术书籍
NIO核心组件
- Channel,可以理解为资源的一个流,通过这个流资源可以从Channel读取Data到一个Buffer中或者从一个Buffer中写入Data到Channel
- Buffer,java NIO Buffers 和Channels配合使用
- Selectors,Selector组件可以运行判断多个Channel,动态决定使用哪个Channel来执行Read 或者 Write操作。通过这个组件可以上线一个Thread 管理多个Channels 或者 多个网络连接Channel。
判断对象可收回
可达性分析
算法的基本思想是通过一系列被称为”GC Root” 的对象作为起点,从这些起点向下搜索,搜索所有的路径,这些路径称为引用链,当一个对象到GC Root没有任何引用链时,则证明此对象是不可用的可回收;GC Root对象包括System class
系统加载类和启动加载类。例如 rt.jar 包中的都是,比如
java.util.*
虚拟机栈中引用的对象
方法区中的类静态属性引用的对象
方法区中常量引用的对象
本地方法中引用的对象
引用类型判断
JDK有四种引用,分别是强引用、软引用、弱引用、虚引用强引用
强引用就是我们平常用的类似于“Object obj = new Object()”的引用,只要obj的生命周期没结束,或者没有显示地把obj指向为null,那么JVM就永远不会回收这种对象
软引用
软引用相对强引用来说就要脆弱一点,JVM正常运行时,软引用和强引用没什么区别,但是当内存不够用时,濒临逸出的情况下,JVM的垃圾收集器就会把软引用的对象回收。具体演示如下:
1234567891011121314151617181920212223public class SoftReferenceTest {public static void main(String[] args) {SoftReferenceTest softReferenceTest = new SoftReferenceTest();SoftReference<SoftReferenceTest> softReference = new SoftReference<>(softReferenceTest);softReferenceTest = null;System.out.println("before softReference:" + softReference.get());int i = 0;ArrayList<String> list = new ArrayList<>();while (i++<100000000){list.add(String.valueOf(i));}System.out.println("end");}protected void finalize() throws Throwable {super.finalize();System.out.println("SoftReferenceTest finalize");}}当调小值的时候不会执行
finalize()
方法弱引用
弱引用比软引用更加脆弱,弱引用的对象将会在下一次的gc被回收,不管JVM内存被占用多还是少。
虚引用
虚引用是最脆弱的引用
中文编码问题
- 中文变成了看不懂的字符
字符串在解码时所用的字符集与编码字符集不一致导致。123456String encode = URLEncoder.encode("淘!我喜欢","GBk");System.out.println(encode);String errorDecode = URLDecoder.decode(encode, "ISO-8859-1");System.out.println(errorDecode);String decode = URLDecoder.decode(encode, "GBk");System.out.println(decode);
输出如下
- 一个汉字变成一个问号
将中文和中文符号经过不支持中文的ISO-8859-1
编码后,所有字符变成?
,这是因为使用ISO-8859-1
进行解码时,遇到不在码值范围内的字符会统一用3f
表示,这也是常说的“黑洞”,所以ISO-8859-1
不认识的字符都会变成’?’。1234String encode = URLEncoder.encode("淘!我喜欢", "ISO-8859-1");System.out.println("encode:"+encode);String decode = URLDecoder.decode(encode, "ISO-8859-1");System.out.println("decode:"+decode);
输出为:
- 一个汉字变成两个问号
这种情况比较复杂,中文经过多次编码,但是其中一次编码或者解码不对仍然会出现中文字符变成’?’的情况
Spring的优缺点
优点
- 提供了一种管理对象的方法,可以把中间层对象有效地组织起来。一个完美的框架“黏合剂”。
- 采用了分层结构,可以增量引入到项目中。
- 有利于面向接口编程习惯的养成。
- 目的之一是为了写出易于测试的代码。
- 非侵入性,应用程序对Spring API的依赖可以减至最小限度。
- 一致的数据访问界面。
- 一个轻量级的架构解决方案。
缺点:
- 中断了应用程序的逻辑,使代码变得不完整,不直观。此时单从Source无法完全把握应用的所有行为。
- 将原本应该代码化的逻辑配置化,增加了出错的机会以及额外的负担。
- 调试阶段不直观,后期的bug对应阶段,不容易判断问题所在。
单例模式的优缺点
优点:
- 在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例
- 单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
- 提供了对唯一实例的受控访问。
- 由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
- 允许可变数目的实例。
- 避免对共享资源的多重占用。
缺点:
- 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
- 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
- 单例类的职责过重,在一定程度上违背了“单一职责原则”。
- 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
唯一索引的作用
共享锁和排他锁
共享锁
共享锁又称读锁,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务不能对数据进行修改(获取数据上的排他锁)。如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获取共享锁的事务只能读数据,不能修改数据。
排他锁
排他锁又称写锁,如果事务T对数据A加上排他锁后,其他事务不能再对A加任何类型的锁。获取排他锁的事务既能读数据,又能修改数据。
数据库隔离级别
- 读未提交的,另一个事务修改了数据,但尚未提交,而本事务中的select会读到这些未被提交的数据(也就是脏读)。脏读就是指另一个事务修改了数据,但尚未提交,而本事务中的select会读到这些未被提交的数据
- 读已提交的,本事务读取到的是最新的数据(其他事务提交后的)。问题是,在同一个事务里,前后执行相同的 select会读到不同的结果(不可重复读)。不可重复读,指同一个事务执行过程中,另外一个事务提交了新数据,因此本事务先后两次读到的数据结果不一致
- 可重复读,在同一事务中,SELECT的结果是事务开始时间点的状态,同样的SELECT操作读到的结果会是一致的。但是,会有幻读现象。可重复读保证了同一个事务里,查询的结果都是事务开始时的状态(一致性)。但是,如果另一个事务同时提交了新数据,本事务再更新时,就会发现了这些新数据,貌似之前读到的数据是幻觉,这就是幻读。
- 串行化,所有事务只能一个个执行,不能并发
数据库隔离级别出现的问题
- 脏读:对于两个事务T1,T2,T1读取了已经被T2更新但还没有提交的字段,之后,若T2回滚,T1读取到的内容就是临时无效的内容。
- 不可重复读:对于事务T1,T2,T1需要读取一个字段两次,在第一次和第二次读取之间,T2更新了该字段,导致T1第二次读取到的内容值不同。出现这样的原因是,T1事务读取完数据后释放了共享锁,导致T2可以获取排他锁对数据进行修改,所以第二次看到数据不一致。
- 幻读: 事务A读取与搜索条件相匹配的若干行。事务B以插入或删除行等方式来修改事务A的结果集,然后再提交。 幻读与不可重复读之间的区别是幻读强调的是新增或删除,而不可重复读强调的是修改。这是在可重复读的事务级别下出现的现象,在该事务级别下,T1读完数据后,不到事务提交是不会释放共享锁的,也就是其他事务不能获取到排他锁对已读到的数据进行修改,实现了可重复读;但是其他事务可以执行insert语句,这样就导致出现幻读。
分布式、集群、负载均衡的理解
集群:同一个业务,部署在多个服务器上。
分布式:一个业务分拆成多个子业务,或者本身就是不同的业务,部署在不同的服务器上。
负载均衡:通过负载均衡算法,将用户的请求转发给后台内网服务器。达到访问的负载均衡
负载均衡算法
- 轮询(RoundRobin)将请求顺序循环地发到每个服务器。当其中某个服务器发生故障,AX就把其从顺序循环队列中拿出,不参加下一次的轮询,直到其恢复正常。
- 比率(Ratio):给每个服务器分配一个加权值为比例,根椐这个比例,把用户的请求分配到每个服务器。当其中某个服务器发生故障,AX就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常。
- 优先权(Priority):给所有服务器分组,给每个组定义优先权,将用户的请求分配给优先级最高的服务器组(在同一组内,采用预先设定的轮询或比率算法,分配用户的请求);当最高优先级中所有服务器或者指定数量的服务器出现故障,AX将把请求送给次优先级的服务器组。这种方式,实际为用户提供一种热备份的方式。
- 最少连接数(LeastConnection):AX会记录当前每台服务器或者服务端口上的连接数,新的连接将传递给连接数最少的服务器。当其中某个服务器发生故障,AX就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常。
- 最快响应时间(Fast Reponse time):新的连接传递给那些响应最快的服务器。当其中某个服务器发生故障,AX就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常。
- 哈希算法( hash): 将客户端的源地址,端口进行哈希运算,根据运算的结果转发给一台服务器进行处理,当其中某个服务器发生故障,就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常。
- 基于数据包的内容分发:例如判断HTTP的URL,如果URL中带有.jpg的扩展名,就把数据包转发到指定的服务器。
悲观锁和乐观锁
悲观锁(Pessimistic Lock):
顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会Lock直到它拿到锁。乐观锁(Optimistic Lock):
顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。
适用场景:
悲观锁:比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。
乐观锁:比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。
总结:两种所各有优缺点,读取频繁使用乐观锁,写入频繁使用悲观锁。