准备面试时,需要认真整理java面试题,先按类别分类之后,再深入到每一个模块认真整理,这里以我的工作经验做的思维导图:
一、java基础
1.数组(arrayList)与链表(linkedList)的优缺点和区别
1.1数组:是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少插入和删除元素,就应该用数组
1.2链表:中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起,每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的 指针。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表。
2.&和&&区别
一样的地方就是二者执行最后的结果是一样的,但是执行的过程有区别,
对于&:无论&左边是否为false,他都会继续检验右边的boolean值。
对于&&:只要检测到左边Boolean值为false时,就会直接判断结果,不会在检验右边的值,效率更高。
&和 | 做位运算符,做二进制位之间的与运算
3.HashMap的存储结构及原理,如何避免hash冲突
这里HashMap用了一个算法。
map.put(key,value) //存储key
int hash=key.hashCode(); //获取key的hashCode,这个值是一个固定的int值
int index=hash%Entry[].length;//获取数组下标:key的hash值对Entry数组长度进行取余
Entry[index]=value;
注意:如果两个key通过hash%Entry[].length得到的index相同,会不会覆盖?(hash冲突)
3.1.链表法: 是不会的。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这个属性链接在一起。所以疑问不用担心。解决hash冲突的方法有很多,HashMap底层是通过链表来解决hash冲突的。HashMap其实就是一个Entry数组,Entry对象中包含了键和值,其中next也是一个Entry对象,它就是用来处理hash冲突的,形成一个链表。
3.2.开放定址法:一旦产生了冲突(该地址已有其它元素),就按某种规则(上面的算法)去寻找另一空地址。
4.hashcode和equals区别
hashCode()方法和equal()方法的作用其实一样,在Java里都是用来对比两个对象是否相等一致,因为重写的equal里一般比较全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只要生成一个hash值进行比较就可以了,效率很高。
那么hashCode()既然效率这么高为什么还要equal()呢?因为hashCode()并不是完全可靠,有时候不同的对象他们生成的hashcode也会一样(生成hash值得公式可能存在的问题),所以hashCode()只能说是大部分时候可靠,并不是绝对可靠。
1.equal()相等的两个对象他们的hashCode()肯定相等,也就是用equal()对比是绝对可靠的。
2.hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。
5.jdk1.8或1.9新特性
jdk1.8:
Lambda 表达式 − Lambda允许把函数作为一个方法的参数
方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
默认方法 − 默认方法就是一个在接口里面有了一个实现的方法
Jdk 1.9:
模块系统:模块是一个包的容器,Java 9 最大的变化之一是引入了模块系统(Jigsaw 项目)。
6.接口与抽象类区别
接口:所有的方法都是抽象方法,只能是方法的声明。成员变量是默认的public static final 类型。接口不能实例化自己。
抽象类:至少包含一个抽象方法的类叫抽象类,抽象类不能被自身实例化,并用abstract关键字来,可以有成员变量,可以声明普通方法和抽象方法。
7.方法重载和方法重写
重写:
方法名、参数、返回值相同。
子类方法不能缩小父类方法的访问权限。
子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。
存在于父类和子类之间。
方法被定义为final不能被重写。
重载:
参数类型、个数、顺序至少有一个不相同。
不能重载只有返回值不同的方法名。
存在于父类和子类、同类中。
8.java反射原理
JAVA语言编译之后会生成一个.class文件,反射就是通过字节码文件找到某一个类、类中的方法以及属性等。
9.IO与NIO区别
NIO是为了弥补IO操作的不足而诞生的,NIO的一些新特性有:非阻塞I/O,选择器,缓冲以及管道。管道(Channel),缓冲(Buffer) ,选择器( Selector)是其主要特征。
9.1、IO是面向流的,NIO是面向块(缓冲区)的。
9.2、IO是阻塞的,NIO是非阻塞的。
9.3、IO是面向流的,NIO是面向块(缓冲区)的。
10.Object常见方法
clone、toString、equals、hashCode、getClass
11. String、StringBuffer与StringBuilder的区别
String:适用于少量的字符串操作的情况。
StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况。
StringBuilder:单线程,是线程不安全的,而StringBuffer是线程安全的。
12.java 非线程安全的HashMap如何在多线程中使用
12.1.使用 java.util.Hashtable 类,此类是线程安全的。但是性能低,key-value不能为null
12.2.使用 java.util.concurrent.ConcurrentHashMap,此类是线程安全的,大量的利用了volatile,final,CAS算法
13.Hashmap的结构,jdk1.7和jdk1.8有哪些区别不同点
1.7是数组+链表,1.8则是数组+链表+红黑树结构
jdk1.8中的HashMap存储结构是由数组、链表、红黑树这三种数据结构形成,红黑树查询删除快新增慢。存储结构下图所示,根据key的hash与table长度确定table位置,同一个位置的key以链
表形式存储,超过一定限制链表转为树。数组的具体存取规则是tab[(n-1) & hash],其中tab为node数组,n为数组的长度,hash为key的hash值表中数据的临界值,如果达到8,就进行resize扩展,如果数组大于64则转换为树。
14.request.getParameter()和getReader()区别
14.1.getParameter()的表单也是采用这种方式编码:enctype=application/x- www-form-urlencoded
14.2.getReader()的表单也是采用这种方式编码:enctype=multipart/form-data或application/json时
14.理解Http协议
浏览器根据查询到的IP地址与Web服务器进行通信,而通信的协议就是HTTP协议,默认端口号为80。HTTP是基于客户端/服务端(C/S)的架构模型,通过一个可靠的链接来交换信息,是一个无状态的请求/响应协议。一个HTTP"客户端"是一个应用程序(Web浏览器或其他任何客户端),通过连接到服务器达到向服务器发送一个或多个HTTP的请求的目的,请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成。
14.1 HTTP是无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
14.2 HTTP是媒体独立的:这意味着,只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过HTTP发送。客户端以及服务器指定使用适合的MIME-type内容类型。
14.3 HTTP是无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
15.TCP/IP协议
15.tomcat优化:
15.1 最大的线程数、网络连接超时
15.2 配置内存大小:修改bin/catalina.bat中的set CATALINA_OPTS=-Xms64m -Xmx128m。
15.3 禁用DNS查询:修改server.xml文件中的Connector元素,修改属性enableLookups参数值false
二、springMVC
1.启动原理
1.1 用户发送请求至前端控制器DispatcherServlet,DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handle。
1.2 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。DispatcherServlet 调用 HandlerAdapter处理器适配器,HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器)。
1.3 Handler执行完成返回ModelAndView。HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet。
1.4 DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析,ViewResolver解析后返回具体View。DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)
1.5 DispatcherServlet响应用户。
2.常见注解
@Controller 用于标记在一个类上,使用它标记的类就是一个SpringMVC Controller 对象
@RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
@ResponseBody返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用
@Autowired与@Resource区别
@Autowired默认按类型装配(这个注解是属业spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false)
@Resource(这个注解属于J2EE的),默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找
@Transactional(rollbackFor = Exception.class)使用事务并回滚指定的异常。
3.ioc和aop
ioc:依赖注入-反射机制
在传统的程序设计中,当调用者需要被调用者的协助时,通常由调用者来创建被调用者的实例。但在spring里创建被调用者的工作不再由调用者来完成,创建被调用者实例的工作通常由spring容器来完成,然后注入调用者,因此也被称为依赖注入或控制反转
Aop:面向切面编程
主要采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行。面向切面编程(AOP)完善spring的依赖注入(DI),面向切面编程在spring中主要表现为两个方面
3.1.提供声明式事务管理
3.2.spring支持用户自定义的切面
4.事务
5.关于SpringMVC定时任务配置
5.1 在springMVC项目框架中新建applicationContext-timerTask.xml
5.2 直接创建task类 (注意几个重要的注解)
package com.xxx.controller;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Controller;
@Controller
@EnableScheduling
public class TestTask {
@Scheduled(cron = "0/5 * * * * ? ") // 间隔5秒执行
public void test() {
//具体业务
}
}
6.springBoot启动原理
1. SpringApplication实例的构建过程
其中主要涉及到了初始化器(Initializer)以及监听器(Listener)这两大概念,判定环境,是web环境还是普通环境。它们都通过META-INF/spring.factories完成定义。
加载环境变量,环境变量包括system environment、classpath environment、application environment(也就是我们自定义的application.properties配置文件)
2. SpringApplication实例run方法的执行过程
创建SpringApplicationRunListeners。创建ApplicationContext,设置装配context,在这里将所有的bean进行扫描最后在refreshContext的时候进行加载、注入。最终将装配好的context作为属性设置进SpringApplicationRunListeners,这就完成了一个spring boot项目的启动。
7.SpringBoot配置线程池
1. 创建一个线程配置类,注意需要的几个注解
@Configuration
@EnableAsync
public class ExecutorConfig {
private static Logger logger = LogManager.getLogger(ExecutorConfig.class.getName());
@Bean
public Executor asyncServiceExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//配置核心线程数
executor.setCorePoolSize(5);
//配置最大线程数
executor.setMaxPoolSize(10);
//配置队列大小
executor.setQueueCapacity(400);
//配置线程池中的线程的名称前缀
executor.setThreadNamePrefix("thread-");
// rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化
executor.initialize();
return executor;
}
}
2.创建一个使用线程的接口
public interface AsyncService {
//执行异步任务
void executeAsync(Map map);
}
3.接口实现类处理相关逻辑
public class AsyncServiceImpl implements AsyncService {
Log log = LogFactory.getLog(AsyncServiceImpl.class);
@Autowired
private UserMapper userMapper;
@Override
@Async("asyncServiceExecutor")
public void executeAsync(Map map) {
//方法体
}
}
8.SpringBoot配置定时任务(注意几个注解)
@Component
@Configuration //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling // 2.开启定时任务
public class ScheduleTask {
//3.添加定时任务
@Scheduled(cron = "0/5 * * * * ?")
//或直接指定时间间隔,例如:5秒
//@Scheduled(fixedRate=5000)
private void task() {
//具体任务
}
}
9.SpringBoot配置事务和回滚事务
8.1 maven导入spring-boot-starter-jdbc依赖,springBoot会自动默认注入DataSourceTransactionManager
8.2 @Transactional 注解在具体业务上
手动回滚事务:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
三、数据库
1.sql调优
使用执行计划,查看执行时间
1.1、在表中建立索引,优先考虑where、group by使用到的字段。
1.2、尽量避免使用select *,使用具体的字段代替*。
1.3、尽量避免使用in 和not in,这样会全表扫描。可以用between或者exists代替。
1.4、尽量避免在where条件中等号的左侧进行表达式、函数操作。
2.索引
常见的MySQL主要有两种结构:Hash索引和B+ Tree索引,我们使用的是InnoDB引擎,默认的是B+树。
Hash索引:存储关系上是完全没有任何顺序关系。对于区间查询是无法直接通过索引查询的。
B树索引:而B+ 树是一种多路平衡查询树,所以他的节点是天然有序的。BTREE索引就是一种将索引值按一定的算法,存入一个树形的数据结构中(二叉树),每次查询都是从树的入口root开始,依次遍历node,获取leaf。这是MySQL里默认和最常用的索引类型。
聚集索引:聚集索引表示表中存储的数据按照索引的顺序存储,检索效率比非聚集索引高,但对数据更新影响较大。
非聚集索引:非聚集索引表示数据存储在一个地方,索引存储在另一个地方,索引带有指针指向数据的存储位置,非聚集索引检索效率比聚集索引低,但对数据更新影响较小。
3.组合索引和索引条件下推
组合索引: 遵循最左原则
索引条件下推:索引条件下推(ICP)是对MySQL使用索引从表中检索行的情况的优化。如果没有ICP,存储引擎会遍历索引以查找基表中的行,并将它们返回给MySQL服务器,该服务器会评估WHERE行的条件。启用ICP后,如果WHERE只使用索引中的列来评估部分 条件,MySQL服务器会推送这部分内容。WHERE条件下到存储引擎。然后,存储引擎通过使用索引条目来评估推送的索引条件,并且仅当满足该条件时才从表中读取行。ICP可以减少存储引擎必须访问基表的次数以及MySQL服务器必须访问存储引擎的次数。
4.数据库死锁
原因:并发修改同一记录、一个用户A 访问表A(锁住了表A),然后又访问表B;另一个用户B 访问表B(锁住了表B),然后企图访问表A;这时用户A由于用户B已经锁住表B,它必须等待用户B
释放表B才能继续,同样用户B要等用户A释放表A才能继续,这就死锁就产生了。
解决方法:
1.使用悲观锁进行控制。悲观锁大多数情况下依靠数据库的锁机制实现,如Oracle的Select … for update语句,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受
2.以固定的顺序访问表和行。即按顺序申请锁,这样就不会造成互相等待的场面
3.大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小。
5.分库分表
简单方法:通过mybatis的拦截器,设置参数改变数据库和表面。
分表:
水平分表:一张表分成多张一样的表
垂直分表:按具体业务分
问题1:事务一致性问题
分布式事务。(基于XA协议的两阶段提交方案、GTS)
问题2:跨节点关联查询 join 问题
避免使用join查询,新建全局表:数据字典表。
问题3:跨节点分页、排序、函数问题
先在每个分片上执行相应的排序、函数,然后将各个分片的结果集进行汇总和再次计算,最终将结果返回。
问题4:全局主键避重问题
uuid
6.redis数据类型
string、hash、list、set、sorted set
7.多机redis 的部署
主从复制,读写分离
一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。
8.redis事务
Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的
8.1 MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
8.2 EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。
8.3 通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
8.4 WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
三、分布式框架
1.RPC工作原理
Clint Stub和Server Stub,他们主要的作用就是将调用的方法和参数进行编码(Marshalling)序列化,将序列化后的数据通过网络发送给Server Stub,然后等待Server回执。Server Stub将受到的序列化字节进行解码(Unmarshaling)反序列化,然后再将参数传入到对应到的方法中执行,将得出的结果计算出来之后再进行返回,返回的过程和正向的过程类似。
2.dubbo工作流程
启动-服务容器负责启动,加载,运行服务提供者。
生产者-服务提供者(生产者)在启动时,向注册中心注册自己提供的服务。服务消费者在启动时,向注册中心订阅自己所需的服务。
注册中心-注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
消费者-服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
3.zookeeper介绍
zookeeper就是个分布式文件系统,在zookeeper中,进行服务注册,实际上就是在zookeeper中创建了一个znode节点,该节点存储了该服务的IP、端口、调用方式(协议、序列化方式)等。该节点承担着最重要的职责,它由服务提供者(发布服务时)创建,以供服务消费者获取节点中的信息,从而定位到服务提供者真正网络拓扑位置以及得知如何调用。RPC服务注册、发现过程简述服务提供者启动时,会将其服务名称,ip地址注册到配置中心。服务消费者在第一次调用服务时,会通过注册中心找到相应的服务的IP地址列表,并缓存到本地,以供后续使用。当消费者调用服务时,不会再去请求注册中心,而是直接通过负载均衡算法从IP列表中取一个服务提供者的服务器调用服务。当服务提供者的某台服务器宕机或下线时,相应的ip会从服务提供者IP列表中移除。同时,注册中心会将新的服务IP地址列表发送给服务消费者机器,缓存在消费者本机。
4.zookeeper高可用
Zookeeper集群规模,机器数量只能是奇数个(容错<投票:可以这样理解。客户端的增删改操作无论访问到了哪台zookeeper服务器,最终都会被转发给leader服务器,再由leader服务器分给zookeeper集群中所有follower服务器去投票(投票指的是在内存中做增删改操作),半数投票通过就被认为操作可执行(commit),否则不可执行>),在三台机器集群的情况下,最多只能挂掉其中一台,如果主节点挂掉,剩下两台服务会重新选举一台作为主节点。
zoo.cfg配置文件配置心跳配置。
分别在每台服务器数据目录中新增名为 myid 文本文件,内容依次为 0,1,2,这是集群中每台 Zookeeper服务的唯一标识,不能重复。
每台机器的myid中内容为服务的唯一标识,不能重复。
五、jvm
1.jvm性能调优
jconsole工具、jps命令等
1.1、初始化内存和最大内存尽量保持一致,避免内存不够用继续扩充内存。最大内存不要超过物理内存,例如内存8g,你可以设置最大内存4g/6g但是不能超过8g否则加载类的时候没有空间会报错。
1.2、gc/full gc频率不要太高、每次gc时间不要太长、根据系统应用来定。
2.内存分配
2.1、程序计数器:程序计数器是一块较小的内存空间,它可以看做当前线程所执行的字节码的行号指示器,在jvm中,虚拟机通过改变程序计数器的值来选取下一条需要执行的字节码的指令,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖它。
2.2、java虚拟机栈:java虚拟机栈也是线程私有的,它的生命周期与线程相同,虚拟机栈描述的是java方法执行的内存模型:每一个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储用于存储局部变量表,操作数栈,动态链接,方法出口等信息,每一个方法从调用到执行 完成的过程就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
2.3、本地方法栈:本地方法栈与虚拟机栈的作用相似,它们之间的区别在于java虚拟机栈为虚拟机执行java方法(字节码)服务,而本地方法栈为虚拟机栈使用到的Native方法服务(在SUN HotSpot虚拟机中将虚拟机栈和本地方法栈合二为一)。
2.4、Java堆:ava堆是被所有线程共享的一块内存区域,这部分内存用于存放对象的实例,几乎所有对象的实例都在这里分配内存。
2.5、方法区:方法区与java堆一样,都是线程共享的内存区域,它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
2.6、运行时常量池:运行时常量池是方法区的一部分,在class文件中除了有类的版本,字段,方法,接口等描述信息外,还有一项信息就是常量池,用于存放编译器生成的各种字面量和符号引用,这部分内容在类加载后进入方法区的运行时常量池中存放。
3.垃圾回收算法
先判断对象是否存活(需要回收)的算法:引用计数算法、可达性分析算法(寻根搜索法)
3.1 标记-清除算法:效率偏低
3.2 复制算法:效率高,但是占用2倍内存
3.3 标记-整理算法:效率偏低
3.4 分代收集算法:把Java堆分为新生代和老年代,根据年代将特征选择上述算法。
老年代通常使用:a、c 新年代通常使用:b
GC分为2个部分: GC:收集新年代的区域 Full GC:收集 新年代和老年代的区域
4.常见的Java垃圾回收器
4.1 串行垃圾回收器:单线程 “牵一发动全身”
4.2 并行垃圾回收器:多线程 ,默认使用
4.3 并发标记扫描垃圾回收器 CMS:利用多线程对需要回收的对象进行标记并回收。
4.4 G1垃圾回收器:对比较大的进行回收,可以先压缩再回收。
5.介绍强引用、软引用、弱引用、虚引用
5.1 强引用: A a=new A() 只要引用a存在,垃圾回收器不会回收。
5.2 软引用:类似于缓存的方式,不影响垃圾回收,可以提升速度,节省内存。若对象被回收,
get()为null,此时可以重新new SoftReference
5.3 弱引用:用于监控对象是否被垃圾回收器回收 isEnQueued方法 WeakReference
5.4 虚引用:每次垃圾回收的时候都会被回收。主要用于对象是否已经从内存中删除。 通过IsEnQueued方法 PhantomReference
6.Java 类加载机制
JVM 将类的加载过程分为三个大的步骤:加载,链接,初始化。其中链接又分为三个步骤:验证,准备,解析。
6.1 加载- 查找并加载类的二进制数据:通过一个类的全限定名来获取其定义的二进制字节流,在Java 堆中生成一个代表这个类的java.lang.Class对象,作为方法区中这些数据的访问入口。
6.2 链接- 确保被加载类的正确性;为类的静态变量分配内存,并将其初始化为默认值;把类中的符号引用(类或接口、字段、类方法、接口方法等)转换为直接引用。
6.3 初始化- 为类的静态变量赋予正确的初始值:为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。
6.4 结束生命周期-System.exit()方法或者程序正常执行结束或者执行错误,Java虚拟机将结束生命周期。
7.JVM三种预定义加载器
引导类加载器、 扩展类加载器、系统类加载器。
8.什么是类加载器 "双亲委派" 机制
JVM在加载类时默认采用的是双亲委派机制。所谓的双亲委派机制,就是某个特定的类加载器在接到类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。关于虚拟机默认的双亲委派机制,我们可以从系统类加载器和标准扩展类加载器为例作简单分析。双亲委派机制是为了保证Java核心库的类型安全。这种机制能保证不会出现用户自己能定义java.lang.Object类的情况,因为即使定义了,也加载不了。
六、设计模式
1.手写两种设计模式
1.1代理模式
//接口:
public interface OldClass {
void visit();
}
//实现类:
public class OldClassImpl implements OldClass {
private String a = "abc";
@Override
public void getUser() {
System.out.println(a);
}
}
//代理类:
public class ProxyClass implements OldClass {
private OldClass oldClass;
public ProxyClass (OldClass oldClass) {
this.oldClass = oldClass;
}
@Override
public void getUser () {
oldClass.getUser ();
}
}
//调用
public class Test {
public static void main(String[] args) {
ProxyClass pc = new ProxyClass (new OldClassImpl());
pc.getUser ();
}
}
1.2静态工厂模式
//接口
public interface Traffic {
void drive();
}
//实现类1
public class TrafficCar implements Traffic {
public void drive () {
System.out.println("this is a car!");
}
}
// 实现类2
public class TrafficBus implements Traffic {
public void drive () {
System.out.println("this is a bus");
}
}
//静态工厂类
public class TrafficFactory {
public static Traffic traffic =null;//静态
//静态方法
public static Traffic getTraffic (String type) {
if(type.equals("car")){
traffic =new TrafficCar();
}else if(type.equals("bus")){
traffic =new TrafficBus();
}
return traffic;
}
}
//调用
public class Test {
public static void main(String[] args) {
Traffic carf= TrafficFactory.getTraffic ("car");
carf.drive ();
Traffic busf= TrafficFactory. getTraffic ("bus");
busf.drive ();
}
}
2.JDK和CGLIB动态代理区别
JDK:目标类和代理类实现了共同的接口
拦截器必须实现InvocationHandler接口,而这个接口中invoke方法体的内容就是代理对象方法体的内容
CGLIB:目标类是代理类的父类
拦截器必须实现MethodInterceptor接口,而接口中的intercept方法就是代理类的方法体,使用字节码增强机制创建代理对象的
七、多线程
1. 启动线程
在JAVA中,要开始一个线程,有两种方式。一是直接调用Thread实例的start()方法,继承Thread类;二是实现Runnable接口,将Runable实例传给一个Thread实例然后调用它的start()方法。
实现同步也有两种
一种是用同步方法,一种是用同步块, 同步方法就是在方法返回类型后面加上synchronized, 比如:public void synchronized add(){...}同步块就是直接写:synchronized (这里写需要同步的对象){...}
2.Thread 类中的start() 和 run() 方法有什么区别
start方法来启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码。
run方法当作普通方法的方式调用。程序还是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码。
3.如何避免死锁?
避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺 序(升序或降序)做操作来避免死锁。
4.Java中活锁和死锁有什么区别?
活锁和死锁类似,不同之处在于处于活锁的线程或进程的状态是不断改变的,活锁可以认为是一种特殊的饥饿。一个现实的活锁例子是两个人在狭小的走廊碰到,两个人都试着避让对方好让彼此通过,但是因为避让的方向都一样导致最后谁都不能通过走廊。简 单的说就是,活锁和死锁的主要区别是前者进程的状态可以改变但是却不能继续执行。
5.有三个线程T1,T2,T3,怎么确保它们按顺序执行。
在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。
6.Java多线程中调用wait() 和 sleep()方法有什么不同?
Java程序中wait 和 sleep都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。
7.写出3条你遵循的多线程最佳实践
多用同步类少用wait 和 notify;多用并发集合少用同步集合;避免锁定和缩小同步的范围。
8.什么是AQS
AQS是AbustactQueuedSynchronizer的简称,它是一个Java提供的底层同步工具类,用一个int类型的变量表示同步状态,并提供了一系列的CAS操作来管理这个同步状态。使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如ReentrantLock,Semaphore
9.Semaphore有什么作用
Semaphore就是一个信号量,它的作用是限制某段代码块的并发数。
Semaphore有一个构造函数,可以传入一个int型整数n,表示某段代码最多只有n个线程可以访问,如果超出了n,那么请等待,等到某个线程执行完毕这段代码块,下一个线程再进入。
10.分布式锁的实现,目前比较常用的有以下几种方案
10.1 可以保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行
10.2 这锁要是一把可重入锁(避免死锁)
10.3 这把锁最好是一把阻塞锁
10.4 有高可用的获取锁和释放锁功能
11.基于数据库实现分布式锁
借助数据中自带的锁来实现分布式的锁(select *** for update)
12.基于缓存(redis,memcached,tair)实现分布式锁
REDIS 的 SETNX()、EXPIRE() 方法( 设置过期时间)做分布式锁
13.基于Zookeeper实现分布式锁
每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。
14.notify和notifyAll方法的区别
notify只会唤醒等待该锁的其中一个线程。notifyAll:唤醒等待该锁的所有线程。
15.如何判断线程是否安全?
考虑原子性,可见性,有序性。
15.1 明确哪些代码是多线程运行的代码,
15.2 明确共享数据 对共享变量的操作是不是原子操作 , 当某一个线程对共享变量进行修改的时候,对其他线程是可见的
保证原子性的是加锁或者同步, 提供了volatile关键字来保证可见
性, synchronized和锁和 volatile都能保证有序性,jVM还通过被称为happens-before原则隐式地保证顺序性。
15.3 明确多线程运行代码中哪些语句是操作共享数据。
八、消息队列
1.消息队列作用
消息队列具有很好的削峰作用的功能——即通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务
三大作用: 解耦、异步、削峰
2.消息队列模型
2.1 点到点(P2P)模型:使用队列(Queue)作为消息通信载体;满足生产者与消费者模式,一条消息只能被一个消费者使用比如:我们生产者发送100条消息的话,两个消费者来消费一般情况下两个消费者会按照消息发送的顺序各自消费一半(也就是你一个我一个的消费。)
2.2 发布/订阅(Pub/Sub)模型
发布订阅模型(Pub/Sub) 使用主题(Topic)作为消息通信载体,类似于广播模式;发布者发布一条消息,该消息通过主题传递给所有的订阅者,在一条消息广播之后才订阅的用户则是收不到该条消息的。
3.ActiveMq和RocketMq区别
ActiveMq: 轻量级,有失误
RocketMq:10万级的吞吐量,可以做到数据0丢失。
4.mqtt与传统消息中间件区别
mqtt:面向移动端场景,有大量移动端的物联网环境。
传统消息中间件: 面向服务器端,服务器机器少且有大量的业务数据需要处理的情况。
5.mqtt简介
MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议。