Java后端面试题总结
Synchronized实现内存共享,ThreadLocal为每个线程维护一个本地变量。ThreadLocal类中维护一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值为对应线程的变量副本。
Java内存模型
Java虚拟机规范中将Java运行时数据分为六种。
- 程序计数器:是一个数据结构,用于保存当前正常执行的程序的内存地址。Java虚拟机的多线程就是通过线程轮流切换并分配处理器时间来实现的,为了线程切换后能恢复到正确的位置,每条线程都需要一个独立的程序计数器,互不影响,该区域为“线程私有”。
- Java虚拟机栈:线程私有的,与线程生命周期相同,用于存储局部变量表,操作栈,方法返回值。局部变量表放着基本数据类型,还有对象的引用。
- 本地方法栈:跟虚拟机栈很像,不过它是为虚拟机使用到的Native方法服务。
- Java堆:所有线程共享的一块内存区域,对象实例几乎都在这分配内存。
- 方法区:各个线程共享的区域,储存虚拟机加载的类信息,常量,静态变量,编译后的代码。
- 运行时常量池:代表运行时每个class文件中的常量表。包括几种常量:编译时的数字常量、方法或者域的引用。
Java GC回收在什么时候、什么东西、做什么
在什么时候:
- 新生代有一个Eden区和两个survivor区,首先将对象放入Eden区,如果空间不足就向其中的一个survivor区上放,如果仍然放不下就会引发一次发生在新生代的minor GC,将存活的对象放入另一个survivor区中,然后清空Eden和之前的那个survivor区的内存。在某次GC过程中,如果发现仍然又放不下的对象,就将这些对象放入老年代内存里去。
- 大对象以及长期存活的对象直接进入老年区。
- 当每次执行minor GC的时候应该对要晋升到老年代的对象进行分析,如果这些马上要到老年区的老年对象的大小超过了老年区的剩余大小,那么执行一次Full GC以尽可能地获得老年区的空间。
对什么东西:从GC Roots搜索不到,而且经过一次标记清理之后仍没有复活的对象。
做什么:
- 新生代:复制清理;
- 老年代:标记-清除和标记-压缩算法;
- 永久代:存放Java中的类和加载类的类加载器本身。
GC Roots都有哪些:
- 虚拟机栈中的引用的对象
- 方法区中静态属性引用的对象,常量引用的对象
- 本地方法栈中JNI(即一般说的Native方法)引用的对象。
Synchronized与Lock
- Synchronized 与Lock都是可重入锁,同一个线程再次进入同步代码的时候.可以使用自己已经获取到的锁。
- Synchronized是悲观锁机制,独占锁。
String、StringBuffer、StringBuilder
- StringBuffer是线程安全的
- StringBuilder是非线程安全的
- String每次操作字符串,会生成一个新的对象,而StringBuffer不会
Volatile和Synchronized四个不同点:
- 粒度不同,前者锁对象和类,后者针对变量
- syn阻塞,volatile线程不阻塞
- syn保证三大特性,volatile不保证原子性
- syn编译器优化,volatile不优化
volatile具备两种特性:
- 保证此变量对所有线程的可见性,指一条线程修改了这个变量的值,新值对于其他线程来说是可见的,但并不是多线程安全的。
- 禁止指令重排序优化。
Volatile如何保证内存可见性:
- 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。
- 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
线程池的作用:
在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。
常用线程池:ExecutorService 是主要的实现类,其中常用的有
Executors.newSingleThreadPool(),newFixedThreadPool(),newcachedTheadPool(),newScheduledThreadPool()。
什么时候使用索引:
- 经常出现在group by,order by和distinc关键字后面的字段
- 经常与其他表进行连接的表,在连接字段上应该建立索引
- 经常出现在Where子句中的字段
- 经常出现用作查询选择的字段
Spring IOC (控制反转,依赖注入)
Spring支持三种依赖注入方式,分别是属性(Setter方法)注入,构造注入和接口注入。
在Spring中,那些组成应用的主体及由Spring IOC容器所管理的对象被称之为Bean。
Spring的IOC容器通过反射的机制实例化Bean并建立Bean之间的依赖关系。
简单地讲,Bean就是由Spring IOC容器初始化、装配及被管理的对象。
获取Bean对象的过程,首先通过Resource加载配置文件并启动IOC容器,然后通过getBean方法获取bean对象,就可以调用他的方法。
Spring AOP应用场景
性能检测,访问控制,日志管理,事务等。
Spring Bean的作用域:
- Singleton:Spring IOC容器中只有一个共享的Bean实例,一般都是Singleton作用域。
- Prototype:每一个请求,会产生一个新的Bean实例。
- Request:每一次http请求会产生一个新的Bean实例。
SpringMVC运行原理
- 客户端请求提交到DispatcherServlet
- 由DispatcherServlet控制器查询HandlerMapping,找到并分发到指定的Controller中。
- Controller调用业务逻辑处理后,返回ModelAndView
- DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图
- 视图负责将结果显示到客户端
Http请求
DNS域名解析 -> 发起TCP的三次握手 -> 建立TCP连接后发起http请求
-> 服务器相应http请求,浏览器得到html代码 -> 浏览器解析html代码,并请求html代码中的而资源
-> 浏览器对页面进行渲染呈现给用户
设计存储系统
设计存储海量数据的存储系统:设计一个叫“中间层”的一个逻辑层,在这个层,将数据库的海量数据抓出来,做成缓存,
运行在服务器的内存中,同理,当有新的数据到来,也先做成缓存,再想办法,持久化到数据库中,这是一个简单的思路。
主要的步骤是负载均衡,将不同用户的请求分发到不同的处理节点上,然后先存入缓存,定时向主数据库更新数据。
读写的过程采用类似乐观锁的机制,可以一直读(在写数据的时候也可以),但是每次读的时候会有个版本的标记,
如果本次读的版本低于缓存的版本,会重新读数据,这样的情况并不多,可以忍受。
Session与Cookie
- Cookie可以让服务端跟踪每个客户端的访问,但是每次客户端的访问都必须传回这些Cookie,如果Cookie很多,则无形的增加了客户端与服务端的数据传输量。
- Session则很好地解决了这个问题,同一个客户端每次和服务端交互时,将数据存储通过Session到服务端,不需要每次都传回所有的Cookie值,而是传回一个ID, 每个客户端第一次访问服务器生成的唯一的ID,客户端只要传回这个ID就行了,这个ID通常为NAME为JSESSIONID的一个Cookie。 这样服务端就可以通过这个ID,来将存储到服务端的KV值取出了。
分布式Session框架
- 配置服务器,Zookeeper集群管理服务器可以统一管理所有服务器的配置文件
- 共享这些Session存储在一个分布式缓存中,可以随时写入和读取,而且性能要很好,如Memcache,Tair
- 封装一个类继承自HttpSession,将session存入到这个类中然后再存入分布式缓存中
- 由于Cookie不能跨域访问,要实现Session同步,要同步SessionID写到不同域名下
- 适配器模式:将一个接口适配到另一个接口,JAVA I/O中InputStreamReader将Reader类适配到InputStream,从而实现了字节流到字符流的转换。
- 装饰器模式:保持原来的接口,增强原有的接口功能。FileInputStream实现了InputStream的所用接口, BufferedInputStreams继承自FileInputStream是具体的装饰器实现者,将InputStream读取的内容保存在内存中,而提高读取的性能。
Spring事务配置方法:
- 切点信息,用于定位实施事务切面的业务类方法
- 控制事务行为的事务属性,这些属性包括事务隔离级别,事务传播行为,超时时间,回滚规则。
Spring通过aop/tx Schema 命名空间和@Transaction注解技术来进行声明式事务配置
Mybatis
每一个Mybatis的应用程序都以一个SqlSessionFactory对象的实例为核心。 首先用字节流通过Resource将配置文件读入,然后通过SqlSessionFactoryBuilder().build方法创建SqlSessionFactory, 然后再通过sqlSessionFactory.openSession()方法创建一个sqlSession为每一个数据库事务服务。 经历了Mybatis初始化 –>创建SqlSession –>运行SQL语句 返回结果三个过程
Servlet与Filter的区别
Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。
- Filter用处:
Filter可以进行对特定的url请求和相应做预处理和后处理。
在HttpServletRequest到达Servlet之前,拦截客户的HttpServletRequest。
根据需要检查HttpServletRequest,也可以修改HttpServletRequest头和数据。
在HttpServletResponse到达客户端之前,拦截HttpServletResponse。
根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据。
实际上Filter和Servlet极其相似,区别只是Filter不能直接对用户生成响应。 实际上Filter里doFilter()方法里的代码就是从多个Servlet的service()方法里抽取的通用代码,通过使用Filter可以实现更好的复用。
Filter和Servlet的生命周期
Filter在web服务器启动时初始化
如果某个Servlet配置了filter,该Servlet也是在Tomcat(Servlet容器)启动时初始化。
如果Servlet没有配置filter,该Servlet不会在Tomcat启动时初始化,而是在请求到来时初始化。
每次请求, Request都会被初始化,响应请求后,请求被销毁。
Servlet初始化后,将不会随着请求的结束而注销。
关闭Tomcat时,Servlet、Filter依次被注销。
HashMap与HashTable的区别
- HashMap是非线程安全的,HashTable是线程安全的。
- HashMap的键和值都允许有null值的存在,而HashTable则不行。
- 因为线程安全的问题,HashMap效率比HashTable高。
HashMap的实现机制
- 维护一个每个元素是一个链表的数组,而且链表中的每个节点是一个Entry[]键值对的数据结构。
- 实现了数组+链表的特性,查找快,插入删除也快。
- 对于每个key,他对应的数组索引下标是 int i = hash(key.hashcode)&(len-1);
- 每个新加入的节点放在链表首,然后该新加入的节点指向原链表首
数据库事务
数据库事务是指作为单个逻辑工作单元执行的一系列操作。
名称 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read uncommitted | yes | yes | yes |
Read commited | no | yes | yes |
Repeatable read | no | no | yes |
Serializable | no | no | no |
JAVA I/O
- File(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名称,删除文件,判断文件所在路径等。
- InputStream(字节流,二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
- OutputStream(字节流,二进制格式操作):抽象类,基于字节的输出操作,是所有输出流的父类。定义了所有输出流都具有的共同特征。
- Reader(字符流,文本格式操作):抽象类,基于字符的输入操作。
- Writer(字符流,文本格式操作):抽象类,基于字符的输出操作。
- RandomAccessFile(随机文件操作):它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。
java.io包里有4个基本类:InputStream、OutputStream及Reader、Writer类,它们分别处理字节流和字符流。
其他各种各样的流都是由这4个派生出来的。
来源/去向分类
- File: FileInputStream, FileOutputStream, FileReader, FileWriter
- byte[]: ByteArrayInputStream, ByteArrayOutputStream
- Char[]: CharArrayReader, CharArrayWriter
- String: StringBufferInputStream, StringReader, StringWriter
- 网络数据流: InputStream, OutputStream, Reader, Writer
示例代码
- 将标准输入(键盘输入)显示到标准输出(显示器),支持字符。
char ch; //将字节流转为字符流,带缓冲 BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); try { while ((ch = (char) in.read()) != -1){ System.out.print(ch); } } catch (IOException e) { e.printStackTrace(); }
- 将AtomicityTest.java的内容打印到显示器
- 方法一
BufferedReader in = new BufferedReader(new FileReader("AtomicityTest.java")); String s; try { while ((s = in.readLine()) != null){ System.out.println(s); } in.close(); } catch (IOException e) { e.printStackTrace(); }
- 方法一
- 方法二
FileReader in = new FileReader("AtomicityTest.java"); int b; try { while ((b = in.read()) != -1){ System.out.print((char)b); } in.close(); } catch (IOException e) { e.printStackTrace(); }
- 方法二
- 方法三:(有可能出现乱码)
FileInputStream in = new FileInputStream("AtomicityTest.java"); int n = 50; byte[] buffer = new byte[n]; try { while ((in.read(buffer,0,n) != -1 && n > 0)){ System.out.print(new String(buffer)); } in.close(); } catch (IOException e) { e.printStackTrace(); }
- 方法三:(有可能出现乱码)