JAVA面试总结
1:main方法启动至少有几个线程
答案建议:守护线程,main方法主线程,如果有thread会创建子线程 。
扩展:在java中,启动一个简单的main程序,并不是只是单单创建了一个main线程而已,JVM会自动创建一些辅助用的线程,主要有以下几个:
Attach Listener:Attach Listener线程是负责接收到外部的命令,而对该命令进行执行的并且吧结果返回给发送者。通常我们会用一些命令去要求jvm给我们一些反 馈信 息,如:java -version、jmap、jstack等等。如果该线程在jvm启动的时候没有初始化,那么,则会在用户第一次执行jvm命令时,得到启动。
Signal Dispatcher:前面我们提到第一个Attach Listener线程的职责是接收外部jvm命令,当命令接收成功后,会交给signal dispather线程去进行分发到各个不同的模块处理命令,并且返回处理结果。signal dispather线程也是在第一次接收外部jvm命令时,进行初始化工作。
Finalizer:这个线程也是在main线程之后创建的,其优先级为10,主要用于在垃圾收集前,调用对象的finalize()方法;很有可能由于在finalize()方法中误操作导致GC线程停止或不可控,这对GC线程来说是一种灾难;
Reference Handler:VM在创建main线程后就创建Reference Handler线程,其优先级最高,为10,它主要用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收问题。
相关文档:https://www.cnblogs.com/cwjbest/articles/6917436.html
2:jvm类的加载机制
答案建议:虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
相关文档:https://www.jianshu.com/p/9b8f16fceba4
引用----
String可以被重写吗
答案建议: 不可以,就算被重写,也会由于jvm的类加载机制调用基本类库的String ,因为jvm类加载器
默认从启动类加载器--扩展类加载器---应用类加载器 (双亲委派)
3:arraylist和linkedlist区别 likedlist为何有序 基于什么
答案建议:ArrayList是实现了基于动态数组的数据结构,每个元素在内存中存储地址是连续的;LinkedList基于链表的数据结构,每个元素内容包扩previous, next, element,也是由于这一性质支持了每个元素在内存中分布存储
在列表首位添加(删除)元素,LinkedList性能远远优于ArrayList,原因在于ArrayList要后移(前移)每个元素的索引和数组扩容(删除元素时则不需要扩容)。(测试的时候当然插入一次是看不出来什么的,我自己测试插入十万次,就会有数组扩容arraycopy的因素)而LinkedList则直接增加元素,修改原第一元素该节点的上一个节点即可,删除同理
在列表中间位置添加(删除)元素,总的来说位置靠前则LinkedList性能优于ArrayList,靠后则相反。出现这种情况的原因在于ArrayList性能主要损耗在后移(前移)该位置之后的元素索引,而LinkedList损耗在for循环从第一位检索该位置的元素
4:mq和kafka的区别 kafka的场景 技术选型 使用场景
答案建议:activemq场景(artimes 订单中台处理完接龙 发消息跟我们map的key为接龙状态 模型转换 接收到消息后再分别通知商家,用户或者团长);kafka场景监听订单维权信息(公司所有的维权信息,不只是单个营销数据量比较大) 对社区团购的维权单进行处理并同步到订单中台
activemq:Apache的ActiveMQ下的子项目 可采用poll方式消费消息 支持消息重试等 当消费者消费完消息后发送一个ack给消息中间件,消息中间件收到ack后将消息从消息队列中标记为已消费或者从队列中移除。
Kafka主要特点是基于Pull的模式来处理消息消费,追求高吞吐量(基于页缓存,消息在内存中分配和写入,IO操作交给最擅长的操作系统,采用sendfile零拷贝技术减少上线问切换等),一开始的目的就是用于日志收集和传输,多用于最求高吞吐的异步消息中,目前不支持失败重试需要业务自己处理
补充kafka
Kafka : 如果是追求系统最高吞吐量的是时候使用kafka
activemq 追求系统稳定以及工作流通知
5:中间件
答案建议:处于操作系统和应用程序之间的软件,将具体业务和底层逻辑解耦的组件。可参照消息中间件,tomcat,mycat等
相关文档:http://www.elecfans.com/dianzichangshi/20171201590210.html
6:dubbo的底层实现原理
答案建议:Dubbo :是一个rpc框架,soa框架.作为RPC:支持各种传输协议,如dubbo,hession,json,fastjson,底层采用mina,netty长连接进行传输!典型的provider和cusomer模式!作为SOA:具有服务治理功能,提供服务的注册和发现!用zookeeper实现注册中心
一、Dubbo相当与Spring Cloud
Dubbo是个微服务整体架构的框架,提供的功能包括服务注册发现,远程调用,监控等等。对标的项目是spring cloud。但Spring Cloud是一个系列的软件,有很多组件来拼装提供微服务的总体架构。Dubbo自己全封装了。
二、zookeeper集成在Dubbo中以后,相当于Spring Cloud中的Eureka
Dubbo的服务发现模块基于zookeeper实现。
Eureka是spring cloud之下一个专门负责微服务服务注册和发现的组件,Eureka就是为了服务发现而设计的。是Dubbo对应的概念的一个部分。
三、原本的Zookeeper
ZooKeeper是一种分布式协调服务,用于管理大型主机。在分布式环境中协调和管理服务是一个复杂的过程。ZooKeeper通过其简单的架构和API解决了这个问题 ,Zookeeper是用来保证分布式一致性的一个软件,(客户端watch服务端node节点数据变动从而保证了分布式的一致性)不是为了服务发现注册而设计的。只不过它的特性也可以被二次开发成服务发现注册中心(在zookeeper中,进行服务注册,实际上就是在zookeeper中创建了一个znode节点,该节点存储了该服务的IP、端口、调用方式(协议、序列化方式)等。该节点承担着最重要的职责,它由服务提供者(发布服务时)创建,以供服务消费者获取节点中的信息,从而定位到服务提供者真正网络拓扑位置以及得知如何调用)罢了。这是在概念上的区别,具体的区别太多了。在Duboo这里被改造成做注册用了。
补充zk作为注册中心:zookeeper提供了“心跳检测”功能,它会定时向各个服务提供者发送一个请求(实际上建立的是一个 socket 长连接),如果长期没有响应,服务中心就认为该服务提供者已经“挂了”,并将其剔除,比如100.19.20.02这台机器如果宕机了,那么zookeeper上的路径就会只剩/HelloWorldService/1.0.0/100.19.20.01:16888。
服务消费者会去监听相应路径(/HelloWorldService/1.0.0),一旦路径上的数据有任务变化(增加或减少),zookeeper都会通知服务消费方服务提供者地址列表已经发生改变,从而进行更新。
更为重要的是zookeeper 与生俱来的容错容灾能力(比如leader选举),可以确保服务注册表的高可用性。
使用 zookeeper 作为注册中心时,客户端订阅服务时会向 zookeeper 注册自身;主要是方便对调用方进行统计、管理。但订阅时是否注册 client 不是必要行为,和不同的注册中心实现有关,例如使用 consul 时便没有注册。
补充zk leader选举算法:服务器状态分为:LOOKING、FOLLOWING、LEADING、OBSERVING。
1:每个Server发出一个投票
2:接收来自各个服务器的投票: 集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自LOOKING状态的服务器。
3:处理投票 针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK,PK规则如下:
-
- 优先检查ZXID。(为事务id)ZXID比较大的服务器优先作为Leader。
- 如果ZXID相同,那么就比较myid(SID(服务器的唯一标识)myid较大的服务器作为Leader服务器。
4:统计投票:每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于Server1、Server2而言,都统计出集群中已经有两台机器接受了(2, 0)的投票信息,此时便认为已经选出了Leader。
5:改变服务器状态LEADER
补充zk作为注册中心和eureka的区别:
zk和eureka的区别
我们来比较一下,在CAP理论中,zk更看重C和P,即一致性和分区容错性。但Eureka更在意的是A和P,A为高可用。zk中有master和follower区别,当进入选举模式时,就无法正常对外提供服务。但Eureka中,集群是对等的,地位是相同的,虽不能保证一致性,但至少可以提供注册服务。 根据不同的业务场景,各有取舍吧。
一致性和分区容错cp
分区容错和高可用ap
Cap 一致性 高可用 容错
补充zk:
zk基于zab协议 更加看中cp 一致性以及分区容错性
相关文档:https://note.youdao.com/web/#/file/recent/note/WEBf210ef23ea146d966c84bd4cdcffcd1d
https://www.jianshu.com/p/e8800af25368
https://baijiahao.baidu.com/s?id=1630695618508872194&wfr=spider&for=pc
https://www.w3cschool.cn/zookeeper/zookeeper_overview.html
7:Spring,Springmvc,Springcloud和Springboot的区别和联系
答案建议:
1. spring是一个一站式的轻量级的java开发框架,核心是控制反转(IOC)和面向切面(AOP),针对于开发的WEB层(springMvc)、业务层(Ioc)、持久层(jdbcTemplate)等都提供了多种配置解决方案;
2. springMvc属于一个企业WEB开发的MVC框架,涵盖面包括前端视图开发、文件配置、后台接口逻辑开发等,XML、config等配置相对比较繁琐复杂;
3. springBoot框架相对于springMvc框架来说,更专注于开发微服务后台接口,不开发前端视图;
4. spring boot使用了默认大于配置的理念,集成了快速开发的spring多个插件,同时自动过滤不需要配置的多余的插件,简化了项目的开发配置流程,一定程度上取消xml配置,是一套快速配置开发的脚手架,能快速开发单个微服务;
5. SpringBoot专注于快速方便的开发单个个体微服务。 SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,为各个服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、精选决策、分布式会话等集成服务。 SpringBoot可以离开SpringCloud独立开发项目,但是SpringCloud离不开SpringBoot,属于依赖关系。SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注全局的服务治理框架。
相关文档:https://www.jianshu.com/p/4784b4fabd0e
补充spring
spring 生态中 spring为最核心的部分 其中又以ioc
aop 最为核心 ioc 控制反转 容器中有区分两个小容器 一个为自定义bean 对象容器 一个为 系统bean 容器 (见下图中一个系统IOC 以及一个自定义IOC注入流程)
aop 则是一种面向切面的思想, 具体的实现,如:事物,日志,拦截器,监听器,等等等(有些事情只会一回不可言传。确实说不出个所以然,应该是我内功不到家)
补充spring中自定义IOC以及系统IOC容器流程图
8:volatile,synchronize, lock的原理
答案建议:
volatile和synchronize的区别:Volatile使变量在多个线程间可见。一般,线程通过工作内存写入(读取)主内存中的变量,而Volatile将强制在主内存中写入(读取)变量。volatile是线程同步的轻量级实现,所以volatile的性能比Synchronized好。Volatile只能修饰变量,而Synchronized可以修饰方法以及代码块。多线程访问Volatile不会发生线程组塞,而Synchronized会出现阻塞。Volatile可以保证数据的可见性,但不能保证原子性;而Synchronized既可以保证原子性,也可以间接保证可见性(因为它会将私有内存和公共内存中的数据做同步)。
Volatile解决的是变量在多个线程之间的可见性,而Synchronized解决的是多个线程之间访问资源的同步性(只有获取锁的线程执行,其他线程等待);
Synchronized与ReentrantLock的区别:两者都是可重入锁,Synchronized是依赖于JVM实现的;而ReentrantLock是依赖于JDK实现的,是API层面的,需要Lock()和unLock()方法配合try/finally语句块来完成。ReentrantLock比Synchronized多了三项功能:等待可中断,可实现公平锁,可实现选择性通知(锁可绑定多个条件)。
三者在分布式中并不是线程安全的;分表分库中的全局唯一id保证id不重复,加锁和算法控制(twitter的snowflake以及美团的leaf)
相关文档:https://blog.csdn.net/weixin_43395526/article/details/91820680
9:hashmap,Hashtable,courrenthashmap jdk1.8优化 底层原理以及解决了什么问题
答案建议:hashmap:在1.8中对于哈希碰撞下的node结构做了优化,在发生哈希碰撞的极端情况下查询链表结构的最后一个元素时效率比较低,于是当哈希桶个数大于8时引入了红黑树
courrenthashmap:同HashTable相比,它的锁粒度更细,而不是像HashTable一样为每个方法都添加了synchronized锁。Java8中的ConcurrentHashMap废弃了Segment(锁段)的概念,而是用CAS和synchronized方法来实现。利用CAS来获取table数组中的单个Node节点,获取成功进行更新操作时,再使用synchronized处理对应Node节点所对应链表(或红黑树)中的数据。
hashtable将锁加在了整个table上;courrenthashmap将锁加载单个node节点上
CAS:内存值v;预期的旧值A;修改值B 只有当v 等于A时才做修改 。乐观锁技术
相关文档:https://www.jianshu.com/p/85d158455861
https://www.cnblogs.com/luoxn28/p/6059881.html
10:自定义一个线程池
答案建议:结合常用的threadPoolTaskExecutor的方法和变量
threadPoolTaskExecutor由concurrent包中的ThreadPoolExecutor的执行,流程如下:
如果当前运行的线程数小于corePoolSize,那么就创建线程来执行任务(执行时需要获取全局锁)。
如果运行的线程大于或等于corePoolSize,那么就把task加入BlockQueue。
如果创建的线程数量大于BlockQueue的最大容量,那么创建新线程来执行该任务。
如果创建线程导致当前运行的线程数超过maximumPoolSize,就根据饱和策略来拒绝该任务。
注意点:ThreadPoolTaskExecutor的代码可以发现,其主要是使用 BlockingQueue的一种实现LinkedBlockingQueue进行实现。
线程 超时关闭线程:线程池中的每一个线程被封装成一个Worker对象,ThreadPool维护的其实就是一组Worker对象,Worker继承了AQS,使用AQS来实现独占锁的功能。为什么不使用ReentrantLock来实现呢?可以看到tryAcquire方法,它是不允许重入的,而ReentrantLock是允许重入的:
lock方法一旦获取了独占锁,表示当前线程正在执行任务中;
如果正在执行任务,则不应该中断线程;
如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断;
线程池在执行shutdown方法或tryTerminate方法时会调用interruptIdleWorkers方法来中断空闲的线程,interruptIdleWorkers方法会使用tryLock方法来判断线程池中的线程是否是空闲状态;
之所以设置为不可重入,是因为我们不希望任务在调用像setCorePoolSize这样的线程池控制方法时重新获取锁。如果使用ReentrantLock,它是可重入的,这样如果在任务中调用了如setCorePoolSize这类线程池控制的方法,会中断正在运行的线程。
所以,Worker继承自AQS,用于判断线程是否空闲以及是否可以被中断。
https://blog.csdn.net/u010002184/article/details/80848322
https://m.imooc.com/mip/article/34450
线程执行拒绝策略时会丢失任务吗:(队列已满的情况下)
1、直接丢弃(DiscardPolicy) 任务允许丢失时使用
2、丢弃队列中最老的任务(DiscardOldestPolicy)。
3、抛异常(AbortPolicy)
4、将任务分给调用线程来执行(CallerRunsPolicy)。交给主线程继续执行,任务不会丢失
https://www.cnblogs.com/zhangtan/p/7607321.html
https://m.imooc.com/mip/article/34450
11:写个内存溢出的test
答案建议:首先了解下强引用和弱引用
强引用:特点:我们平常典型编码Object obj = new Object()中的obj就是强引用。通过关键字new创建的对象所关联的引用就是强引用。
当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了,具体回收时机还是要看垃圾收集策略。
弱引用:弱引用通过WeakReference类实现。
在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快回收弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
内存溢出,首先设置jvm的堆内存值,尽量设置小一点更容易发生内存溢出,
OutOfMemoryError:内存溢出 * 意思是对象所占据的空间超过了堆内存的空间了, * 因此模拟它的方式就是组装一个超级大的对象就行, * 于是通过大量循环来生成字符串就可以达到这个效果
或者集合内不断强引用对象
设置运行时jvm的参数-Xmx –Xms:指定最大堆和最小堆
* Run->Run Configuartion->PermGenTest->Arguments->VM arguments
* -XX:PermSize=5M -XX:MaxPermSize=5M -Xms5M -Xmx5M -XX:-UseGCOverheadLimit
* 2.设置jdk为1.7,因为jdk1.8已经不支持XX:PermSize参数
相关文档:https://www.jianshu.com/p/70e1900c1b33
https://blog.csdn.net/tengdazhang770960436/article/details/79179587
12:写个归并排序
答案建议:八大算法
冒泡排序:比较相邻的两个数据大小,让较大的数往下沉,较小的往上冒 for循环两次,每一轮循环,内循环都少比较一次
public void bubbleSort(int[] arr) {
int temp;//定义一个临时变量
for(int i=0;i<arr.length-1;i++){//冒泡趟数
for(int j=0;j<arr.length-i-1;j++){
//如果顺序不对,则交换两个元素
if(arr[j+1]<arr[j]){
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
public static void main(String[] args) {
Test t = new Test();
int arr[] = new int[]{13,26,22,22,35,18};
t.bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
快速排序:
1)选择一个基准元素,通常选择第一个元素或者最后一个元素,
2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的 元素值比基准值大。
3)此时基准元素在其排好序后的正确位置
4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。
归并排序:
public static int[] sort(int[] a,int low,int high){
int mid = (low+high)/2;
if(low<high){
sort(a,low,mid);
sort(a,mid+1,high);
//左右归并
merge(a,low,mid,high);
}
return a;
}
public static void merge(int[] a, int low, int mid, int high) {
int[] temp = new int[high-low+1];
int i= low;
int j = mid+1;
int k=0;
// 把较小的数先移到新数组中
while(i<=mid && j<=high){
if(a[i]<a[j]){
temp[k++] = a[i++];
}else{
temp[k++] = a[j++];
}
}
// 把左边剩余的数移入数组
while(i<=mid){
temp[k++] = a[i++];
}
// 把右边边剩余的数移入数组
while(j<=high){
temp[k++] = a[j++];
}
// 把新数组中的数覆盖nums数组
for(int x=0;x<temp.length;x++){
a[x+low] = temp[x];
}
}
https://blog.csdn.net/a3192048/article/details/80269862
13:threadlocal
14:threadpoolexcutor常用线程池的核心参数 。以及执行流程 。以及用到的哪种队列
15:何时不能用多线程 。服务之间调用何时用api何时用多线程
16:redis为何支持分布式锁 。分布式锁原理
17:虚拟机模型
18:快速排序
19:常用的保持心跳方法
20:多线程为何要加锁
21:jvm内存模型
22:熔断的方案
23:apollo的底层实现
24:es的底层实现基于倒排索引 score算法 同步es以及基于数据库binglog的canel同步 es和db是否能做到最终一致
25:shardingjdbc的原理 以及分表分库的策略
26:https的四次握手
27:前端的请求到处理结束结合项目或者springcloud讲个各个阶段的解决方案 zuul/negix/F5 eureka config 等
28:常用的设计模式 项目中
29:gc如何判断强引用和弱引用
30:调用链路的实现原理
31:为何用shardingjdbc不用mycat
32:数据库优化注意点
33:使用微服务的优点
34:exception和error差别 exception向上抛出的时候有什么变化
35:spring的@value注解的加载机制
36:java代码执行过程
37:内存溢出和内存泄露
38:mysql的隔离级别
39:redis的穿透和雪崩
40:项目的qps 日活量
41:讲下用到的技术栈
42:socket讲下
43:为何要用线程池 创建线程消耗哪些资源
44:反射原理
45:父类的private方法子类能用么 如何校验
46:调用链原理
47:spring的IOC和AOP
什么是DI机制?
依赖注入(Dependecy Injection)和控制反转(Inversion of Control)是同一个概念,具体的讲:当某个角色需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在spring中创建被调用者的工作不再由调用者来完成,因此称为控制反转。创建被调用者的工作由spring来完成,然后注入调用者
因此也称为依赖注入。
spring以动态灵活的方式来管理对象 , 注入的两种方式,设置注入和构造注入。
设置注入的优点:直观,自然 构造注入的优点:可以在构造器中决定依赖关系的顺序。
什么是AOP?
面向切面编程(AOP)完善spring的依赖注入(DI),面向切面编程在spring中主要表现为两个方面
1.面向切面编程提供声明式事务管理
2.spring支持用户自定义的切面
面向切面编程(aop)是对面向对象编程(oop)的补充,
面向对象编程将程序分解成各个层次的对象,面向切面编程将程序运行过程分解成各个切面。
AOP从程序运行角度考虑程序的结构,提取业务处理过程的切面,oop是静态的抽象,aop是动态的抽象,
是对应用执行过程中的步骤进行抽象,,从而获得步骤之间的逻辑划分。
aop框架具有的两个特征:
1.各个步骤之间的良好隔离性
2.源代码无关性
Spring的事务管理机制实现的原理,就是通过这样一个动态代理对所有需要事务管理的Bean进行加载,并根据配置在invoke方法中对当前调用的 方法名进行判定,并在method.invoke方法前后为其加上合适的事务管理代码,这样就实现了Spring式的事务管理。Spring中的AOP实 现更为复杂和灵活,不过基本原理是一致的。
jdk动态代理和cglib动态代理
https://www.cnblogs.com/superjt/p/4275462.html
49:springBoot源码分析
51:数据库死锁的原因和解决方案
52:单例模式
https://www.jianshu.com/p/12d1a151982e
53:jvm的root指什么,垃圾回收调用object的哪个方法
答案建议:1:程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看
做是当前线程所执行的字节码的行号指示器;
2:与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,
它的生命周期与线程相同。虚拟机栈描述的是Java 方法执行的内存模型:每个方法被执
行的时候都会同时创建一个栈帧(Stack Frame ①)用于存储局部变量表、操作栈、动态
链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在
虚拟机栈中从入栈到出栈的过程。
3:本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其
区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则
是为虚拟机使用到的Native 方法服务
4:对于大多数应用来说,Java 堆(Java Heap)是Java 虚拟机所管理的内存中最大的
一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的
唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存
5:方法区(Method Area)与Java 堆一样,是各个线程共享的内存区域,它用于存
储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽
然Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-
Heap(非堆),目的应该是与Java 堆区分开来。
6:运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有
类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool
Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放
到方法区的运行时常量池中。
可以作为gc root的对象:
1 、 虚拟机栈(栈帧中的本地变量表)中引用的对象。
2、 本地方法栈中JNI(即一般说的native方法)引用的对象。
3、 方法区中的静态变量和常量引用的对象。
垃圾回收之前调用object.finalize()方法
强制垃圾回收:System.gc()
https://www.cnblogs.com/winby/p/6627838.html
https://www.cnblogs.com/sank/p/10601282.html
54:runnable和callable的区别和应用场景
答案建议:
区别:
- callable可以抛异常, runnable不能
- callable可以有返回值, runnable不能
相同点:
- 两者都是接口;
- 两者都可用来编写多线程程序;
- 两者都需要调用Thread.start()启动线程
Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
55:深度复制和浅复制
答案建议: 浅拷贝 :创建一个新对象,然后将当前对象的非静态字段复制到该对象,如果字段类型是值类型(基本类型)的,那么对该字段进行复制;如果字段是引用类型的,则只复制该字段的引用而不复制引用指向的对象。此时新对象里面的引用类型字段相当于是原始对象里面引用类型字段的一个副本,原始对象与新对象里面的引用字段指向的是同一个对象。
简而言之:类实现默认的Object.clone()方法,拷贝对象时,对于引用类型的成员变量(属性)拷贝只是拷贝“值”即地址(引用),没有在堆中开辟新的内存空间。
因此,修改clonePerson里面的address内容时,原person里面的address内容会跟着改变。
“深拷贝” :类重写clone()方法,对于引用类型成员变量,重新在堆中开辟新的内存空间,简单地说,将引用类型的属性内容也拷贝一份新的。
如果我们想实现深拷贝,有两种方法,第一种是给需要拷贝的引用类型也实现Cloneable接口并覆写clone方法;第二种则是利用序列化
56:创建对象除了new还有什么方式
答案建议:1.用new语句创建对象,这是最常见的创建对象的方法。
2.运用反射手段,调用java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。
3.调用对象的clone()方法。
4.运用反序列化手段,调用java.io.ObjectInputStream对象的 readObject()方法。
5.xml配置
57:反射原理
58:字符串拼接在何时在堆中创建何时在方法区创建
https://blog.csdn.net/AlphaWun/article/details/92383416
59:sharddingjdbc的shard
60:一个未知大小的数组取出前100个要求时间复杂度较低
答案建议:
61:Apollo的底层实现以及如何做到同步配置的
答案建议:长连接实际上我们是通过Http Long Polling实现的,具体而言:
* 客户端发起一个Http请求到服务端
* 服务端会保持住这个连接30秒
* 如果在30秒内有客户端关心的配置变化,被保持住的客户端请求会立即返回,并告知客户端有配置变化的namespace信息,客户端会据此拉取对应namespace的最新配置
* 如果在30秒内没有客户端关心的配置变化,那么会返回Http状态码304给客户端
* 客户端在服务端请求返回后会自动重连
62:mysql的隔离级别 。事务的特性
答案建议:事务的基本要素(ACID)
1、原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。
2、一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
3、隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
4、持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
二、事务的并发问题
1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
三、MySQL事务隔离级别
mysql默认的事务隔离级别为repeatable-read
低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
Read Uncommitted 读未提交(select不加锁):
all事务都能看到其他未提交事务的执行结果。性能很菜。
读取未提交的数据,被称之为脏读
Read Committed 读已提交
(普通select快照读,锁select /update /delete 会使用记录锁,除了在外键约束检查(foreign-key constraint checking)以及重复键检查(duplicate-key checking)时会*区间)
除了Mysql之外的大多数数据库系统的默认隔离级别。一个事务只能看见已经提交事务所做的改变。解决了脏读的问题,但是依然有不可重复读的异常。
Repeatable Read可重复读:
普通select快照读,锁select /update /delete 根据查询条件情况,会选择记录锁,或者间隙锁/临键锁,以防止读取到幻影记录
mysql的默认隔离级别。保证了同一个事务的多个实例在并发读取数据的时候,会看到同样的数据行。解决了脏读和不可重复读的异常,存在幻读的异常。
InnoDB和Falcon、PBXT存储引擎通过MVCC(多版本并发控制)机制进行解决该问题。
Serializable 可串行化:
select隐式转化为select … in share mode,会被update与delete互斥;
强制事务排序,解决三个异常。其实就是在每一个读的数据行上加上共享锁,在这个级别,可能导致大量的超时现象和锁竞争
读锁(Share Locks共享锁 S锁)
一个事务对数据对A加上读锁,其他事务只能再对A加读锁不可加写锁。
保证了事务在读A的时候不能被修改。
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE;
写锁(Exclusive Locks 排它锁 X锁)
一个事务对A加了写锁,其他事务不能再加任何锁。
更新操作(insert update delete)过程中用写锁。
SELECT * FROM table_name WHERE ... FOR UPDATE;
当在InnoDB的默认隔离级别下(可重复读),如果检索条件有索引(包括主键索引)时,默认加锁方式为next-key锁(行锁,防止幻读);
若检索条件没有索引,更新数据时就会锁住整张表**(表锁)**。
事务隔离级别为串行化时,读写数据都会锁住整张表
数据库死锁问题:
InnoDB的默认隔离级别是RR(Repeatable Read)。
在这种隔离级别下,普通的select使用快照读,一种不加锁的一致性读。其底层是使用MVCC实现
相关文档:https://www.cnblogs.com/shihaiming/p/11044740.html
https://blog.csdn.net/mulinsen77/article/details/88943771
63:redis分布式锁,加入锁超时被回收掉,此时仍然存在线程安全问题如何解决(qps很大时会存在问题)
答案建议:因为业务代码耗时过长,超过了锁的超时时间,造成锁自动失效,然后另外一个线程意外的持有了锁。于是就出现了多个线程共同持有锁的现象。解决方案如下:
1、和释放锁的情况一致,我们需要先判断锁的对象是否没有变。否则会造成无论谁持有锁,守护线程都会去重新设置锁的LockTime。不应该续的不能瞎续。
2、守护线程要在合理的时间再去重新设置锁的LockTime,否则会造成资源的浪费。不能动不动就去续。
3、如果持有锁的线程已经处理完业务了,那么守护线程也应该被销毁。不能主人都挂了,守护者还在那里继续浪费资源。
https://blog.csdn.net/weixin_33943347/article/details/88009397
64:redis除了用于缓存和分布式锁还是哪些功能,单机时为何性能高,redis的多路复用,多路复用原理(select epoll)
答案建议:这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),且Redis在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了Redis具有很高的吞吐量。
首先,Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务,而 I/O 多路复用就是为了解决这个问题而出现的。
I/O 多路复用其实是在单个线程中通过记录跟踪每一个sock(I/O流) 的状态来管理多个I/O流。结合下图可以清晰地理解I/O多路复用。
IO多路复用模型是建立在内核提供的多路分离函数select基础之上的,使用select函数可以避免同步非阻塞IO模型中轮询等待的问题。
如上图所示,用户线程发起请求的时候,首先会将socket添加到select中,这时阻塞等待select函数返回。当数据到达时,select被激活,select函数返回,此时用户线程才正式发起read请求,读取数据并继续执行。
从流程上来看,使用select函数进行I/O请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的I/O请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个I/O请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
Reactor(反应器模式)
如上图,I/O多路复用模型使用了Reactor设计模式实现了这一机制。通过Reactor的方式,可以将用户线程轮询I/O操作状态的工作统一交给handle_events事件循环进行处理。用户线程注册事件处理器之后可以继续执行做其他的工作(异步),而Reactor线程负责调用内核的select函数检查socket状态。当有socket被激活时,则通知相应的用户线程(或执行用户线程的回调函数),执行handle_event进行数据读取、处理的工作。由于select函数是阻塞的,因此多路I/O复用模型也被称为异步阻塞I/O模型。注意,这里的所说的阻塞是指select函数执行时线程被阻塞,而不是指socket。一般在使用I/O多路复用模型时,socket都是设置为NONBLOCK的,不过这并不会产生影响,因为用户发起I/O请求时,数据已经到达了,用户线程一定不会被阻塞。
65:ReentrantLock底层基于AQS volatile+cas
答案建议:ReentrantLock是可重入锁,什么是可重入锁呢?可重入锁就是当前持有该锁的线程能够多次获取该锁,无需等待。可重入锁是如何实现的呢?这要从ReentrantLock的一个内部类Sync的父类说起,Sync的父类是AbstractQueuedSynchronizer(后面简称AQS)。
什么是AQS?
AQS是JDK1.5提供的一个基于FIFO等待队列实现的一个用于实现同步器的基础框架,这个基础框架的重要性可以这么说,JCU包里面几乎所有的有关锁、多线程并发以及线程同步器等重要组件的实现都是基于AQS这个框架。AQS的核心思想是基于volatile int state这样的一个属性同时配合Unsafe工具对其原子性的操作来实现对当前锁的状态进行修改。当state的值为0的时候,标识改Lock不被任何线程所占有。
AOS主要提供一个exclusiveOwnerThread属性,用于关联当前持有该锁的线程
AQS基于状态的标识以及FIFO等待队列方面的工作原理,
https://blog.csdn.net/KingBoyWorld/article/details/72427943
66:object.wait调用时机
答案建议:Object.wait()和Object.notify()和Object.notifyall()必须写在synchronized方法内部或者synchronized块内部,这是因为:这几个方法要求当前正在运行object.wait()方法的线程拥有object的对象锁。即使你确实知道当前上下文线程确实拥有了对象锁,两个方法都要求其执行线程必须持有该方法所属对象的内部锁,因此等待线程和通知线程是同步在同一对象之上的两种线程。
new runnable blocked 阻塞 waiting terminated
Wait 放弃cpu和锁 notify 唤醒县城,重新竞争锁
线程之间通信
- sleep()方法可以在任何地方使用;wait()方法则只能在同步方法或同步块中使用;
- sleep()是线程线程类(Thread)的方法,调用会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait()是Object的方法,调用会放弃对象锁,进入等待队列,待调用notify()/notifyAll()唤醒指定的线程或者所有线程,才会进入锁池,不再次获得对象锁才会进入运行状态;
https://blog.csdn.net/qq_33704186/article/details/90696741
https://blog.csdn.net/qq_31615049/article/details/80616540
67:一个二叉树,交换左右子节点
答案建议:import java.util.ArrayList;
import java.util.List;
public class bintree {
public bintree left;
public bintree right;
public bintree root;
// 数据域
private Object data;
// 存节点
public List<bintree> datas;
public bintree(bintree left, bintree right, Object data){
this.left=left;
this.right=right;
this.data=data;
}
// 将初始的左右孩子值为空
public bintree(Object data){
this(null,null,data);
}
public bintree() {
}
public void creat(Object[] objs){
datas=new ArrayList<bintree>();
// 将一个数组的值依次转换为Node节点
for(Object o:objs){
datas.add(new bintree(o));
}
// 第一个数为根节点
root=datas.get(0);
// 建立二叉树
for (int i = 0; i <objs.length/2; i++) {
// 左孩子
datas.get(i).left=datas.get(i*2+1);
// 右孩子
if(i*2+2<datas.size()){//避免偶数的时候 下标越界
datas.get(i).right=datas.get(i*2+2);
}
}
}
//先序遍历
public void preorder(bintree root){
if(root!=null){
System.out.println(root.data);
preorder(root.left);
preorder(root.right);
}
}
//中序遍历
public void inorder(bintree root){
if(root!=null){
inorder(root.left);
System.out.println(root.data);
inorder(root.right);
}
}
// 后序遍历
public void afterorder(bintree root){
if(root!=null){
System.out.println(root.data);
afterorder(root.left);
afterorder(root.right);
}
}
public static void main(String[] args) {
bintree bintree=new bintree();
Object []a={2,4,5,7,1,6,12,32,51,22};
bintree.creat(a);
bintree.preorder(bintree.root);
}
}
先序遍历:先访问根节点——左子树——右子树。
2.中序遍历:先访问左子树——根节点——右子树,按照这个顺序。
3.后序遍历:和前面差不多,先访问树的左子树——右子树——根节点
要求:从键盘输入数,存为二叉树结构并打印。
https://blog.csdn.net/skylibiao/article/details/81195219
68:一个abcde数组,在不新建数组的情况下变成deabc
答案建议:
69:redis雪崩的解决方案
答案建议:什么是缓存穿透
一般的缓存系统,都是按照key值去缓存查询,如果不存在对应的value,就应该去DB中查找 。这个时候,如果请求的并发量很大,就会对后端的DB系统造成很大的压力。这就叫做缓存穿透。关键词:缓存value为空;并发量很大去访问DB。
造成的原因
1.业务自身代码或数据出现问题;2.一些恶意攻击、爬虫造成大量空的命中,此时会对数据库造成很大压力。
解决方法
1.设置布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,
从避免了对底层存储系统的查询压力。
2. 如果一个查询返回的数据为空,不管是数据不存在还是系统故障,我们仍然把这个结果进行缓存,但是它的过期时间会很短
最长不超过5分钟。
二、雪崩
1.什么是雪崩
因为缓存层承载了大量的请求,有效的保护了存储 层,但是如果缓存由于某些原因,整体不能够提供服务,于是所有的请求,就会到达存储层,存储层的调用量就会暴增,造成存储层也会挂掉的情况。缓存雪崩的英文解释是奔逃的野牛,指的是缓存层当掉之后,并发流量会像奔腾的野牛一样,大量后端存储。
存在这种问题的一个场景是:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,大量数据会去直接访问DB,此时给DB很大的压力。
2.解决方法
(1)设置redis集群和DB集群的高可用,如果redis出现宕机情况,可以立即由别的机器顶替上来。这样可以防止一部分的风险。
(2)使用互斥锁
在缓存失效后,通过加锁或者队列来控制读和写数据库的线程数量。比如:对某个key只允许一个线程查询数据和写缓存,其他线程等待。单机的话,可以使用synchronized或者lock来解决,如果是分布式环境,可以是用redis的setnx命令来解决。
(3)不同的key,可以设置不同的过期时间,让缓存失效的时间点不一致,尽量达到平均分布。
(4)永远不过期
redis中设置永久不过期,这样就保证了,不会出现热点问题,也就是物理上不过期。
(5)资源保护
使用netflix的hystrix,可以做各种资源的线程池隔离,从而保护主线程池。
3.使用
四种方案,没有最佳只有最合适, 根据自己项目情况使用不同的解决策略。
70:euraka和a作为注册中心的区别
答案建议:CAP理论,两两互斥,zk注重是一致性和容错性,euraka注重高可用和容错性
71:设计模式遵循的原则,常用的设计模式
答案建议:六大原则
单一职责原则:职责P被分化为粒度更细的职责P1和P2
里氏替换原则 :所有引用基类的地方必须能透明地使用其子类的对象。
依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
接口隔离原则:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
迪米特法则:一个对象应该对其他对象保持最少的了解。
问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
开闭原则 :一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
代理模式 代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用
单例模式 工厂模式 facade 模式 责任链模式 迭代器模式 适配器模式
72:好的编码习惯有哪些
73:轻量级锁 偏向锁 重量级锁
策略模式
https://baijiahao.baidu.com/s?id=1601547440739500969&wfr=spider&for=pc&isFailFlag=1
本文地址:https://blog.csdn.net/LRXmrlirixing/article/details/107868098
上一篇: jquery实现textarea输入字符控制(仿微博输入控制字符)
下一篇: 第一次上线网页