JAVA面试题及答案

2020-12-28  本文已影响0人  码道功臣

由于篇幅问题,这里的答案最只做最浅显的回答,更深入的答案自己百度

这些答案都是我本人总结出来的,如果有误,请回复我,我更正下,谢谢!

一、Java基础和高级

主要是为了”安全性“和”效率“的缘故,因为:

  1. 由于String类不能被继承,所以就不会没修改,这就避免了因为继承引起的安全隐患;
  2. String类在程序中出现的频率比较高,如果为了避免安全隐患,在它每次出现时都用final来修饰,这无疑会降低程序的执行效率,所以干脆直接将其设为final一提高效率;

源码自己百度。
底层结构:JDK1.8之前是使用数组+链表,JDK1.8之后是使用数组+链表或红黑树

最重要的区别是 Class.forName 会初始化Class,而 Classloader 不会。

Classloader 只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。 forName("")得到的class是已经初始化完成的。

因此如果要求加载时类的静态变量被初始化或静态块里的代码被执行就只能用 forName,而用 Classloader 只有等创建类实例时才会进行这些初始化。

cookie存储在客户端,session存储在服务端,通常cookie中会持有session的句柄。

单客户端和服务端建立连接时创建session,通常在关闭连接后session失效,但是当客户端使用客户端保存的句柄重新连接时,能够重用session,这是需要主动刷新session,或者等服务端session过期。

queue
  1. 未实现阻塞接口的:
    LinkedList : 实现了Deque接口,受限的双向链表
    PriorityQueue : 优先队列,本质维护一个有序列表。可自然排序亦可传递 comparator构造函数实现自定义排序。
    ConcurrentLinkedQueue:基于链表 线程安全的队列。增加删除O(1) 查找O(n)

  2. 实现阻塞接口的:
    实现blockqueue接口的五个阻塞队列,其特点:线程阻塞时,不是直接添加或者删除元素,而是等到有空间或者元素时,才进行操作。
    ArrayBlockingQueue: 基于数组的有界队列
    LinkedBlockingQueue: 基于链表的无界队列
    ProiporityBlockingQueue:基于优先次序的无界队列
    DelayQueue:基于时间优先级的队列
    SynchronousQueue:内部没有容器的队列 较特别 --其独有的线程一一配对通信机制

add 增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
offer 添加一个元素并返回true 如果队列已满,则返回false
poll 移除并返问队列头部的元素 如果队列为空,则返回null
peek 返回队列头部的元素 如果队列为空,则返回null
put 添加一个元素 如果队列满,则阻塞
take 移除并返回队列头部的元素 如果队列为空,则阻塞

内存模型:


JVM.png

GC算法:标记-清除(mark-and-sweep、Tracing)、标记-复制(Copying)、标记-整理(Compacting)

https://www.jianshu.com/p/b072ecc239f5

  1. jdk7,可以使用字符串控制switch语句;
  2. jdk7,带资源的try语句;
  3. jdk7,运用List<String> tempList = new ArrayList<>(); 即泛型实例化类型自动推断;
  4. jdk8,lambda表达式,函数式编程;
  5. ...

数组为连续空间,查找快,但是写入慢。链表是通过指针连接起来的非连续空间,查找慢,但是修改快。

  1. jmap用来查看堆内存使用状况
  2. jstack主要用来查看某个Java进程内的线程堆栈信息

开闭原则(OCP,The Open-Closed Principle)两个主要特征:

  1. 对扩展开放(open for extension):模块的行为的可以扩展的,当应用的需求改变时,可以对模块进行扩展。
  2. 对修改关闭(closed for modification):对模块进行扩展时,不必改动模块的源代码

开闭原则是面向对象设计中可复用设计的基石。

  1. Collections.sort(list),如果集合是对象,则需要继承Comparable<T>;
  2. Collections.sort(List<T> list, Comparator<? super T> c);

忙循环就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓存。

在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。

死锁的发生必须满足以下四个条件:

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

两种种用于避免死锁的技术:

  1. 加锁顺序(线程按照一定的顺序加锁)
  2. 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)

相同点:

  1. 两者都是接口;(废话)
  2. 两者都可用来编写多线程程序;
  3. 两者都需要调用Thread.start()启动线程;

不同点:

  1. 两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
  2. Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;

二、spring框架

  1. 核心容器(Spring Core)
    核心容器提供Spring框架的基本功能。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。Spring使用BeanFactory来产生和管理Bean,它是工厂模式的实现。BeanFactory使用控制反转(IoC)模式将应用的配置和依赖性规范与实际的应用程序代码分开。

  2. 应用上下文(Spring Context)
    Spring上下文是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,如JNDI、EJB、电子邮件、国际化、校验和调度功能。

  3. Spring面向切面编程(Spring AOP)
    通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring框架中。所以,可以很容易地使 Spring框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。

  4. JDBC和DAO模块(Spring DAO)
    JDBC、DAO的抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理,和不同数据库供应商所抛出的错误信息。异常层次结构简化了错误处理,并且极大的降低了需要编写的代码数量,比如打开和关闭链接。

  5. 对象实体映射(Spring ORM)
    Spring框架插入了若干个ORM框架,从而提供了ORM对象的关系工具,其中包括了Hibernate、JDO和 IBatis SQL Map等,所有这些都遵从Spring的通用事物和DAO异常层次结构。

  6. Web模块(Spring Web)
    Web上下文模块建立在应用程序上下文模块之上,为基于web的应用程序提供了上下文。所以Spring框架支持与Struts集成,web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。

  7. MVC模块(Spring Web MVC)
    MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的。MVC容纳了大量视图技术,其中包括JSP、POI等,模型来有JavaBean来构成,存放于m当中,而视图是一个街口,负责实现模型,控制器表示逻辑代码,由c的事情。Spring框架的功能可以用在任何J2EE服务器当中,大多数功能也适用于不受管理的环境。Spring的核心要点就是支持不绑定到特定J2EE服务的可重用业务和数据的访问的对象,毫无疑问这样的对象可以在不同的J2EE环境,独立应用程序和测试环境之间重用。

MVC.jpg

@RequsetBody、@Autowired、@RequestMapping、@Controller、@Service

BeanFactory在启动的时候不会去实例化Bean,中有从容器中拿Bean的时候才会去实例化;

ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化;

三种:

  1. 使用属性的setter方法注入 这是最常用的方式;
  2. 使用构造器注入;
  3. 使用Filed注入(用于注解方式).
  1. 编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。
  2. 基于 TransactionProxyFactoryBean的声明式事务管理
  3. 基于 @Transactional 的声明式事务管理
  4. 基于Aspectj AOP配置事务

IOC使用配置(XML、注解)将Bean的创建交给Spring容器控制,保证了松耦合;
AOP面向切面编程可以保证不同层面的逻辑分离,保证了代码的简洁,避免了业务的污染,例如:事务、日志;

基于构造器的注入方式可能造成循环依赖,然后跑错,尽量避免这种用法。

https://www.jianshu.com/p/0173d5220dfb

  1. spring-boot-starters
  2. spring-boot-actuator
  3. spring-boot-starter-web
  4. spring-boot-starter-test
  5. spring-boot-starter-jdbc
  6. spring-boot-starter-security
  7. spring-boot-starter-aop
  8. ...

三、java多线程常见问题

run()方法:在本线程内调用该Runnable对象的run()方法,可以重复多次调用;
start()方法:启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程;

  1. newCachedThreadPool:
    底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
    通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
    适用:执行很多短期异步的小程序或者负载较轻的服务器

  2. newFixedThreadPool:
    底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列
    通俗:创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
    适用:执行长期的任务,性能好很多

  3. newSingleThreadExecutor:
    底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列
    通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
    适用:一个任务一个任务执行的场景

  4. newScheduledThreadPool:
    底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列
    通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
    适用:周期性执行任务的场景

线程池大小不变。

可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁。

ConcurrentHashMap,在1.7中使用分段锁保证线程安全,在1.8中使用CAS原子操作保证线程安全。

  1. 管道:速度慢,容量有限,只有父子进程能通讯;
  2. FIFO:任何进程间都能通讯,但速度慢;
  3. 消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题;
  4. 信号量:不能传递复杂消息,只能用来同步;
  5. 共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存;

四、网络通信

OPTIONS、HEAD、GET、POST、PUT、DELETE、TRACE、CONNECT

五、常用Linux命令

六、数据库MySql

七、设计模式(写代码)

八、算法&数据结构&设计模式

九、分布式缓存

string、hash、list、set、zset

RDB、AOF

如果要添加的新节点是一个主节点, 那么我们需要创建一个空节点(empty node), 然后将某些哈希桶移动到这个空节点里面。

另一方面, 如果要添加的新节点是一个从节点, 那么我们需要将这个新节点设置为集群中某个节点的复制品(replica)。

十、线程池、高并发、NIO

所有线程池的工作方式本质是一样的:有一个任务队列,一定数量的线程会从该任务队列获取任务然后执行。任务的结果可以发回客户端,或保存到数据库,或保存到某个内部数据结构中,等等。但是在执行完任务后,这个线程会返回任务队列,检索另一个任务并执行。

线程池有最小线程数和最大线程数。池中会有最小数目的线程随时待命,等待任务指派给它们。因为创建线程的成本非常高昂,这样可以提高任务提交时的整体性能。线程池的最小线程数称作核心池大小,考虑ThreadPoolExecutor最简单的情况,如果有个任务要执行,而所有的并发线程都在忙于执行另一个任务,就会启动一个新线程,直到创建的线程达到最大线程数。

https://www.cnblogs.com/jianzh5/p/6437315.html

IO密集型=2Ncpu(可以测试后自己控制大小,2Ncpu一般没问题)(常出现于线程中:数据库数据交互、文件上传下载、网络数据传输等等)
计算密集型=Ncpu(常出现于线程中:复杂算法)
java中:Ncpu=Runtime.getRuntime().availableProcessors()

JDK动态代理(Spring默认使用)、Cglib动态代理。

HashMap线程不安全,多线程需要自己控制并发,非常困难,可以使用ConcurrentHashMap替代。

  1. Class clazz = p.getClass();
  2. Class clazz = Person.class;
  3. Class clazz = Class.forName("cn.xxx.Person");

十一、JVM相关(面试必考)

双亲委派解决类重复加载的问题。

classloader.png

在计算机执行指令的顺序在经过程序编译器编译之后形成的指令序列,一般而言,这个指令序列是会输出确定的结果;以确保每一次的执行都有确定的结果。但是,一般情况下,CPU和编译器为了提升程序执行的效率,会按照一定的规则允许进行指令优化,在某些情况下,这种优化会带来一些执行的逻辑问题,主要的原因是代码逻辑之间是存在一定的先后顺序,在并发执行情况下,会发生二义性,即按照不同的执行逻辑,会得到不同的结果信息。

内存栅栏可以阻止指令重排序、强制将缓存的修改立即同步到主存。

https://www.jianshu.com/p/09f29cd5ab55

stackoverflow通常出现在方法执行过程栈太深,到时栈内存被耗尽,比如:递归调用。

PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,这一部分用于存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域,它和存放Instance的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的APP会LOAD很多CLASS的话,就很可能出现PermGen space错误。

解决办法,调整JVM启动配置,加大永久代内存空间。

-Xms:初始堆大小
-Xms:最大堆大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3表示年轻代和年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。
-XX:MaxPermSize=n:设置持久代大小

十二、分布式相关

分库分表后,如何解决全表查询的问题:冗余、业务系统组合

十三、数据库

  1. B+TREE的磁盘读写低价更低;
  2. B+TREE的查询效率更加稳定;
  3. B+TREE更有利于对数据扫描;

在联机事务处理系统中,造成死机主要有两方面原因。一方面,由于多用户、多任务的并发性和事务的完整性要求,当多个事务处理对多个资源同时访问时,若双方已锁定一部分资源但也都需要对方已锁定的资源时,无法在有限的时间内完全获得所需的资源,就会处于无限的等待状态,从而造成其对资源需求的死锁。

https://www.jianshu.com/p/79a14f59da13

十四、Redis&缓存相关

限定 Redis 占用的内存,Redis 会根据自身数据淘汰策略,留下热数据到内存。所以,计算一下 20w 数据大约占用的内存,然后设置一下 Redis 内存限制即可,并将淘汰策略为volatile-lru或者allkeys-lru。

https://www.jianshu.com/p/13386c8b67dc

上一篇下一篇

猜你喜欢

热点阅读