41.Java中的HashSet内部是如何工作的?
HashSet的内部采用HashMap来实现。由于Map需要key和value,所以HashSet中所有key都有一个默认的value,类似于HashMap,HashSet不允许重复的key,只允许有一个null key,意思是HashSet中只允许存储一个null对象。
public class HashSet<E> extends AbstractSet<E> implements Set<E>,Cloneable{
static final long serialVersionUID = -50247444067133321676L;
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
public HashSet(){
map = new HashMap<>();
}
public HashSet(int initialCapacity){
map = new HashMap<>(initialCapacity);
}
public Iterator<E> iterator(){
return map.keySet().iterator();
}
public int size(){
return map.size();
}
public boolean isEmpty(){
return map.isEmpty();
}
public boolean add(E e){
//把传进来的E放在key,新建的静态final的PRESENT对象放在value
//不管放入多少个元素,这些元素都存在map里面,这个map里面所有的value值都是同一个对象
return map.put(e,PRESENT) == null;
}
...
}
42.什么是序列化,怎么序列化,为什么序列化,反序列化会遇到什么问题?
java对象序列化是指将java对象转换为字节序列的过程,而反序列化则是将字节序列转换为java对象的过程。
我们知道,不同进程/程序间进行远程通信时,可以相互发送各种类型的数据,包括文本,图片,音频,视频等,而这些数据都会以二进制序列的形式在网络上传送。
那么java对象不能直接传输,就需要转换为二进制序列传输,所以需要序列化。
java中序列化和反序列化的方式:
首先要序列化的类必须实现接口java.io.Serializable。这个接口中没有具体的方法,只是标记该类可以序列化,实现了这个接口就会要求添加一个serialVersionUID的版本号。这个版本号的作用是在反序列化时用来验证两个进程中是否使用了同一个类。
java中的序列化是通过ObjectOutputStram类的writeObejct()方法将对象直接写出。
反序列化是通过ObejctInputStram类的readObject()从流中读取对象。
43.什么情况下会发生栈内存溢出。
栈是线程私有的,他的生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口等信息。局部变量表又包含基本数据类型,对象引用类型(局部变量表编译器完成,运行期间不会变化)
所以我们可以理解为栈溢出就是方法执行时创建的栈帧超过了栈的深度。那么最有可能的就是递归调用产生这种结果。
44.JVM的内存结构,Eden和Survivor比例。
JVM的内存结构:线程共享区域有:方法区和堆。线程私有区域有java栈(虚拟机栈),本地方法栈。程序计数器。
Eden和Survivor比例:8:1:1
HotSpot JVM把年轻代分为三部分:1个Eden区和2个Survivor区(分别加from和to)。默认比例为8:1:1,一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被转移到Survivor区。使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面,复制算法不会产生内存碎片。
45.JVM内存为什么要分新生代,老年代。新生代中为什么要分为Eden和Survivor
JVM内存分为新生代,老年代和持久代的唯一原因就是优化GC性能。
如果没有分代,那我们所有的对象都在一块,GC的时候我们要找到哪些对象没用,这样就会对堆的所有区域进行扫描。而我们的很多对象都是朝生夕死的,如果分代的话,我们新创建的对象放到某一个地方,当GC的时候先把这块对下个区域进行回收,这样就会腾出很大的空间出来,再有策略的调整对象的“年龄”移动对象的位置,这样的话GC的工作就会更有效率。而我们经常说的JVM调优很大程度上就是GC的调优。
上面说的很多对象都是朝生夕死的,所以要设置一个Eden区,作为新生代的对象存放区,这部分区域的80%的对象会被GC回收。在一次GC之后还存活的对象要从Eden中清出,可以直接移动到老年代中,老年代的空间要比新生代的空间大很多。老年代的一次GC就是FullGC。所以又设置了Survivor区。所以Eden区的对象被GC之后,任然存活的对象会被复制到Survivor区。
Survivor为什么要设置两个区域主要是为了节省空间,不出现碎片空间。
46.JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代,说说你知道的集中主要的JVM参数。
CG执行流程:
OOM:内存溢出
对象晋升老年代有三种可能:
1.大对象:所谓的大对象是指需要大量连续的内存空间的java对象,++最典型的大对象就是很长的字符串以及数组++,大对象对虚拟机的内存分配就是坏消息,尤其是朝生夕死的对象。
2.长期存活的对象:当对象达到成年,经历过15次GC,对象就晋升为老年代
3.动态对象年龄判断:如果survivor空间某个年龄对象的大小大于survivor空间的一半,年龄大于或等于的直接进入老年代。
jvm参数:
Xms:初始堆大小
Xmx:堆最大内存
Xss:栈内存
47.你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms和G1,包括原理,流程,优缺点。
1.垃圾收集器:
Serial收集器:单线程的收集器,收集垃圾时,必须stop the world,使用复制算法
ParNew收集器:Serial收集器的多线程版本,也需要stop the world,复制算法
Parallel Scavenge收集器:新生代收集器,并发多线程收集器,目标是达到一个可控的吞吐量
CMS(Concurrent Mark Sweep)收集器:是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量的空间碎片。
G1收集器:标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选标记,不会产生空间碎片,可以精确的控制停顿。
2.CMS和G1区别:
CMS收集器是老年代收集器,可以配合新生代的Serial和ParNew收集器一起使用;
G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用
CMS收集器以最小的停顿时间为目标的收集器;
G1收集器可预测垃圾回收的停顿时间
CMS收集器是使用“标记-清楚”算法进行的垃圾回收,容易产生内存碎片
48.垃圾回收算法的实现原理。
1.标记-清除算法:会产生内存碎片
2.复制算法:不会产生内存碎片,但是长对象移动降低效率
3.标记-整理算法:结合前两种(G1收集器使用)
4.分代收集算法:
是一种划分的策略,把java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代,每次GC都会有大量死亡对象,就选复制算法,而老年代中对象存活率高,没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。
49.如何排查内存溢出的错误
1.首先控制台查看错误日志
2.然后使用jdk自带的jvisualvm工具查看系统的堆栈日志
3.定位出内存溢出的空间:堆,栈还是永久代
4.如果是堆内存溢出,看是否创建了超大对象
5.如果是栈内存溢出,看是否创建大对象或产生死循坏
50.简单讲讲tomcat结构,以及其他类加载器流程,线程模型。
tomcat的大体架构:
server.xml配置:
<server port="8005" shutdown="SGUTDOWN"> //给8005发送SHUTDOWN tomcat就会停止
<listener className="org.apache.catalina.startup.VersionLoggerListener" />
<listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<listener className="org.apache.catalina.core.JasperListener" />
<listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResource> //公共资源
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResource>
<Service name="catalina">
<Connector port="8080" protocol="HTTP/1.1" //处理http请求
connectionTimeout="20000"
redirectPort="8443" /> //重定向https请求
<connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> //处理其他请求
<Engine name="Catalina" defaultHost="localhost">
<Realm className="oag.apache.catalina.realm.LockOutRealm"> //用户认证
<Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase" />
</Realm>
//虚拟主机
<Host name="localhost" appBase="wabapps" //根目录 unpackWARs="true" // autoDeploy="true">
<Value className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log." suffix=".txt" pattern="%h %l %u %t "%r"%s %b" />
</Host>
</Engine>
</Service>
</Server>
XML架构图:
模型图:
Tomcat中最顶层的容器时Server,代表整个服务器,从上图中可以看出,一个Server可以包含至少一个Service,用于具体提供服务。
Service主要包含两个部分:Connector和Container。从上图中可以看出Tomcat的心脏就是这两个组件,他们的作用如下:
1.Connector用于处理链接相关的事情,并提供Socket与Request和Response相关的转化;
2.Container用于封装和管理Servlet,以及具体处理Request请求。
51.一个Service只有一个Container,但是可以有多个Connectors,为什么?
因为一个服务可以有多个链接,如同时提供Http和Https链接,也可以提供向相同协议不同端口的连接。
总结:
1.每一个tomcat都只有一个Server,表示整个服务环境。一个Server中可以有多个Service。Server就掌管着Service的生死。
2.Service是对外提供服务的,每一个Service中可以有多个Connector和一个Container(Engine)
3.Connector主要用来接收请求,解析请求内容,封装request和response,然后将准备好的数据交给Container处理。
4.Container就是我们经常说的容器,里面可以有多个Host,一个Host表示一个虚拟主机。Container处理完成请求之后将响应内容返回给Connecter,再由Connecter响应客户端。
扩展内容:
Connector的架构:
Connector最底层使用的是Socket来进行链接的,Request和Response是按照HTTP协议来封装的,所以Connector同时需要实现TCP/IP协议和HTTP协议。
1.Connector中具体用ProtocolHandler来处理请求的,不同的ProtocolHandler代表不同的链接类型
2.Endpoint用于处理底层Socket的网络链接,用来实现TCP/IP协议
Acceptor:用于监听和接受请求
Handler:请求的初步处理。并且调用Processor
AsyncTimeout:检查异步的Request请求是否超时。
3.Processor用于将Endpoint接收到的Socket封装成Request,用来实现HTTP协议
4.Adapter用于将Request交给Container进行具体处理,即将请求适配到Servlet容器。
Container架构:
Container就是一个Engine。Container用于封装和管理Servlet,以及具体处理Request请求。
1.Engine:Container,用来管理多个站点,一个Service最多只能有一个Engine
2.Host:虚拟主机,一个Container可以有多个虚拟主机
3.Context:一个应用程序,就是我们平时开发的一个项目,或者说一套web应用程序
4.Wrapper:用来包装Servlet,每一个Wrapper都包装着一个Servlet
52.类加载流程
JVM类加载流程:双亲委派模型
Tomcat类加载器(违背了双亲委派模型):
Bootstrap引导类加载器:加载JVM启动所需的类,以及标准扩展类(位于jre/lib/ext下)
System系统类加载器:加载tomcat启动的类,比如bootstrap,jar,通常在catalina.bat或者catalina.sh中指定。位于CATALINA_HOME/bin下。
Common通用类加载器:加载tomcat使用以及应用通用的一些类,位于CATALINA_HOME/lib下
webapp应用类加载器:每个应用在部署后,都会创建一个唯一的类加载器。该类加载器会加载位于WEB-INF/lib下的jar文件中的class和WEB-INF/classes下的class文件。
JSP类加载器:tomcat会为每个JSP创建一个类加载器。
当应用需要某个类时,则会按下面的顺序进行类加载:
1.使用bootstrap引导类加载器加载JVM基本类和扩展类
2.使用system系统类加载器加载tomcat启动的类
3.使用应用类加载器在WEB-INF/classes中加载
4.使用应用类加载器在WEB-INF/lib中加载
5.使用common类加载器在CATALINE_HOME/lib中加载
53.为什么tomcat要给每一应用设置一个独立的类加载器,为什么要给每个jsp文件设置一个独立的类加载器?
一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一个,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
web容器要支持jsp的修改,我们知道,jsp文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp常见,所以,web容器需要支持jsp修改后不用重启。
54.tomcat为什么违背双亲委派模型
tomcat为了实现隔离性,没有总遵守这个约定,每个webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器。
55.tomcat线程模型
BIO模式:阻塞式I/O模型,表示Tomcat使用的是传统java I/O模型(即java.io包及其子包)。Tomcat7一下版本默认情况下是以bio模式运行的,由于每个请求都要创建一个线程来处理,线程开销大,不能处理高并发的场景,在三种模式中性能也最低。启动tomcat看到如下日志,表示使用的是BIO模式
NIO模式:是java SE1.4以及后续版本提供的一种新的I/O操作方式(即ava.nio包及其子包)。是一个基于缓冲区,并能提供非阻塞I/O操作的java API,它拥有比传统I/O操作(bio)更好的并发运行性能。
APR模式(首选):利用JNI从操作系统级别解决异步IO问题,大幅度提高服务器的处理和相响应性能,也是Tomcat运行高并发应用的首选模式。
AIO(NIO2):异步非阻塞,使用链接数目多且比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持
配置方法:在tomat conf下找到server.xml
56.BIO线程模型:
BIO模型下Connector结构:
1.Http11Protocol组件,是HTTP协议1.1版本的抽象,它包含客户端连接,接收客户端消息报文,报文解析处理,对客户端响应等整个过程。它主要包含JIoEndpoint组件和Http11Processor,启动时JIoEndpoint组件内部的Acceptor组件将启动某个端口的监听,一个请求到来后将被扔进线程池Executor,线程池进行任务处理,处理过程中将通过Http11Processor组件对Http协议解析并传递到Engine容器继续处理。
2.Mapper组件,可以通过请求地址找到对应的servlet
3.CoyoteAdapter组件,将一个Connect和Container适配起来的适配器。
组件之间的关系模型图:
57.NIO线程模型
Connector结构:
这个结构就是比BIO多了一个Poller轮询器,主要用于轮询Acceptor注册的事件列表。
LimitLatch:和BIO一样。
Accptpr:接受请求之后,会将事件注册在注册列表中。并且计数+1
Poller池:负责提供和创建Poller线程
Poller:主要用来轮询事件列表中的事件,判断链接是否可读可写。然后生成任务定义器,放入Executor线程池。
Executor:线程池负责执行线程。
SocketProcessor和Http11Processor都是和BIO一样。
58.tomcat如何调优,涉及哪些参数?
系统调优:。。。
JVM调优:JVM主要调节GC的扫描时间和次数。主要分为堆内存大小的调整和回收机的选择。
Tomcat本身调优:
1.架构/结构优化:动静分离,所谓动静分离就是将所有的静态资源的请求响应处理放在一个独立的服务器上,比如nginx,tomcat只负责jsp和servlet的加载和处理。
2.线程模型选择:tomcat6只有BIO,tomcat7,8都开始支持ARP模式,但是7默认选择BIO,8没有检测到ARP配置的情况下会选择NIO。
BIO方式适用于连接数目较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中。
NIO是一个居于缓冲区,并能提供非阻塞I/O操作的Java API。适用于连接数目多且短的架构
AIO适用于连接数目多且长(异步非阻塞)的结构,充分调用OS参与并发操作,编程比较复杂tomcat8开始支持。
ARP利用JNI(java native interface)调用本都API,大幅度提高了tomcat的IO性能,是tomcat的“大杀器”,但是如果要使用ARP就要安装对应的组件。
3.并发(线程配置)优化:开启tomcat线程池,Connector本身调优
其他优化:禁用AJP连接器,使用Nginx+tomcat的架构,所以用不着AJP协议,所以把AJP连接器禁用
59.Spring加载流程。
BeanFactory和FactoryBean的区别?
BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean。在Spring中,所有的Bean都是由BeanFactory(IOC)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,他的实现与设计模式中的工厂模式和修饰器模式类似。
BeanFactory解释:
BeanFactory定义了IOC容器最基本形式,并提供了IOC容器应遵守的最基本的接口。spring中有很多不同类型的BeanFactory,比如:ApplicationContext,ClassPathXmlApplicationContext,AnnotationConfigApplicationContext等等。这些类都实现了对应的接口。
不过每一个BeanFactory都有各自不同的功能,我们一般都是根据场景的不同来使用不同的BeanFactory来加载spring容器或者启动spring容器获取Bean。
FactoryBean解析:
FactoryBean是spring容器中的一个对象。专门用来创建特殊对象。一般情况下spring都是通过反射来创建对象的。但是如果某个对象的创建过程过于复杂或者无法按照传统的方式实例化,我们就可以使用FactoryBean。
创建FactoryBean对象 eg:
//要创建的类
public class User{}
//创建User对象的工厂Bean
@Component //交给spring管理
public class UserFactoryBean implements FactoryBean<User>{
@Override
public User getObejct(){
return new User(); //返回一个对象,spring会将返回的对象放入spring容器中
}
@Override
public Class<?> getObjectType(){
return User.class;
}
@Override
public boolean isSingleton(){
return true; //默认单例
}
}
//从容器中取出user对象
public static void main(String[] args){
//创建spring容器
AnnotationCOnfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
//获取User对象
sout(ac.getBean(Uesr.class)); //user object
}
60.BeanPostProcess(bean级别)和BeanFactoryPostProcess(工厂级别)的区别?
Spring提供了两种后处理bean的扩展接口,分别为BeanPostProcess和BeanFactoryPostProcess。
BeanPostProcess:后置处理Bean,他是bean级别的,可以在SpringBean初始化之前和之后对应的Bean或者程序进行增强。
@Component
public class MyBeanPostProcessor implements BeanPostProcessor{
public Object postProcessBeforeInitialication(Object bean,String beanName){
sout("postProcessBeforeInitialication - " + beanName);
return bean;
}
public Object postProcessAfterInitialication(Obejct bean,String beanName){
sout("postProcessAfterInitialication"+beanName);
return bean;
}
}
BeanFactoryPostProcess:主要用于增强(修改(创建A对象指向B对象,B并没有交给Spring管理),删除)工厂的功能。可以在创建SpringBean之前修改这个Bean的信息,只有spring4个基本的不能修改。可以不让某些类创建对象,或改变对象指向。
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor{
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory){
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
sout("--BeanFactoryPostProcessor--");
AbstractBeanDefinition beanDefintion = (AbstractBeanDefinition)beanFactory.getBeanDefinition(A);
beanDefintion.setBeanClass(B.class);
sout(Arrays.toString(beanDefintionNames));
sout("--BeanFactoryPostProcessor--")
}
}
61.javaBean(java对象)和SpringBean区别
java对象:使用new或者其他方式直接创建,由JVM统一管理。一个java对象就只有生成这个对象的类中申明的所有属性和功能。
springBean:通过反射创建,由spring容器统一管理。拥有自己类中申明的属性和方法之外还有spring给其增加/修改的属性和方法。
java对象的加载和创建流程:
springBean的加载和创建流程(主要参考BeanDefault里面的内容创建对象):
62.扩展:AnnotationConfigApplicationContext底层:
public AnnotationConfigApplicationContext{
this(); //调用本类的无参的构造方法
//1.创建和注册spring启动需要的对象信息
//2.加载注册AppConfig类,获取配置信息
register(annotatedClasses);
//1.扫描所有的类
//2.解读所有类并且为每个类创建一个BeanDefintion对象存入beanDefinitionMap中
//3.检验所有的BeanDefinition
//4.根据检验结果创建对应的对象,注入属性,执行AOP操作,并且将创建的对象存入singletonObjects
refresh();
}
public void refresh(){
synchronized(this.startupShutdownMonitor){
prepareRefresh();
//获取当前使用的内部bean工厂对象
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
try{
postProcessBeanFactory(beanFatory);
//扫描包,创建BeanDefinition
invokeBeanFactoryPostProcessors(beanFactory);
//注册所有的拦截器处理Bean
registerBeanPostProcessors(beanFactory);
initMessageSource();
initApplicationEvenMulticaster();
}
}
}