面试题和答案Java
问过的问题
JVM:
- JVM调优:什么场景会触发FullGC,如何避免呢(Done)
- OutOfMemory的情况如何解决(Done)
- 线上频繁FULLGC 如何排查?何时会触发 FULLGC ,永久代如果满了,如何解决(Done)
- JVM的类加载机制。 和Tomcat容器的类加载机制。如果一个类已经被加载了,但是做过了修改,如何重新加载?(Done)
jvm内存模型
堆区+永久代(方法区) + 栈区(不同线程不同的线程栈)
- 堆区的组成:老年代+新生代(Eden区:S0区:S1区=8:1:1)
- 栈区的组成:N个线程栈,每个线程栈由 JVM栈、本地方法栈、程序计数器组成
- 方法区:存储类信息、静态成员变量、常量池(字面量和符号引用),线程共享区
java类的生命周期
- 编译期:java文件编译成class文件的过程:涉及格式校验、变量类型校验、捕获异常校验、插入式注解处理、简化运算
- 运行期:类加载、连接、初始化、创建堆中对象、卸载
类的加载器和核心类
- 核心类加载器
- BootstrapClassLoader 最顶层的加载类,负责加载lib下的rt.jar、resources.jar、charsets.jar和class等
- ExtentionClassLoader 加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件
- Application ClassLoader 也称为SystemAppClass 加载当前应用的classpath的所有类
- 自定义ClassLoader,可以突破class文件包路径的限制,需要重写findClass()缓存查找方法制定寻找路径即可
- 加载的特点
- 不同包路径下的类由不同的类加载器加载
- jvm懒加载,根据需要去动态加载
- 父加载器不是父类,双亲委托:自下而上,先挨个找缓存,到了顶层缓存中还没有,就开始初始化,从各自对应负责的包路径下查找,有就创建,没有给就子加载器加载
- 加载之后存在缓存当中
- 加载的结果
- 将class文件加载在内存中。
- 将静态数据结构(数据存在于class文件的结构)转化成方法区中运行时的数据结构(数据存在于JVM时的数据结构)。
- 在堆中生成一个代表这个类的java.lang.Class对象,作为数据访问的入口。
- 核心方法: loadClass、 findLoadedClass、parent.loadClass、findBootStrapClss、findClass
自定义类加载器
实现自定义class文件加载路径,实现,class文件包路径的限制
https://www.cnblogs.com/gdpuzxs/p/7044963.html
实现逻辑:写一个classloader类(构造函数里面需要传入包真实的路径),重写findClass方法,
实现类的每次重新加载,实现热加载
- 本来重写 findLoadedClass()即可,直接返回null,实现热加载,但是ClassLoader把该方法设置为final了,不希望去破坏这个规则
- 所以直接通过重写过的findClass来实现,相当于在上面的基础上做修改,findClass的路径为当前路径,每次loadClass,直接调用findClass方法即可,虽然只能修改本地的class类
- findClass需要defineClass来最终把字节码文件加载进内存当中,JVM默认相同的类加载器不能加载相同全路径的类,所以每次重新加载的时候需要new一个自定义加载器,否则会报错String currentPath=Class.class.getClass().getResource("/").getPath()MyClassLoader myClassLoader=new MyClassLoader(currentPath);Class c=myClassLoader.findClass("com.test.Action.Test");MyClassLoader myClassLoader2=new MyClassLoader(currentPath);Class c2=myClassLoader2.findClass("com.test.Action.Test");
tomcat的类加载模型
- 核心加载器
- CommonClassLoader(默认父加载器为AppClassLoader)。加载一些tomcat/lib下面的jar包,servlet-api.jar、jsp-api.jar等
- catalinaClassLoader(默认是空,有需要的话需要自己配置)
- sharedClassLoader(默认是空,有需要的话需要自己配置)
- WebAppClassLoader(tomcat下 WebRoot/应用程序/WEB-INF/lib 和 class包下的类
- 层级结构
- tomcat下每个应用程序都有一个独立的类加载器,WebAppClassLoader,因为不同应用程序的类不能乱加载,更不能公用啊。
- 但是有一些通用的类,比如JUnit、Log4j类,是不同应用程序公用的类,所以有sharedClassLoader出场,可以设置路径来实现这个类加载器,从而实现不同应用程序共同加载一些通用工具类
- 再上一层就是CommonClassLoader:下面由shardClassLoader和CatalinaClassLoader,一个是所有应用程序,一个是tomcat容器的扩展类加载器
- 再上一层就是AppClassLoader了,就是传统的模式往上走了
- 加载逻辑(源码 org.apache.catalina.loader.WebappClassLoader#loadClass)
tomcat的类加载机制是违反了双亲委托原则的:先本地缓存查找,再全局缓存查找,再系统加载,再自己加载,最后再父加载器加载
- 先在本地缓存中查找是否已经加载过该类(clazz = findLoadedClass0(name))
- 再查询JVM缓存中查找是否已经加载过该类( clazz = findLoadedClass(name))
- 让系统类加载器(AppClassLoader)尝试加载该类,主要是为了防止一些基础类会被web中的类覆盖(tomcat bin下包)(clazz = system.loadClass(name))
- web应用的类加载器将自行加载(findClass),这里也违背了双亲 (clazz = findClass(name);)
- 最后还是加载不到的话,则委托父类加载器(Common ClassLoader)去加载。(clazz = Class.forName(name, false, parentLoader);)
OOM的排查
引发OOM的常见情况:Perm区满了,或者Heap区满了。主要是分析dump的快照,分析对象,定位到代码,如果是正常业务,就扩大机器内存,如果代码问题,优化代码。
- 导致OutOfMemoryError异常的常见原因有以下几种:
- 内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
- 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
- 代码中存在死循环或循环产生过多重复的对象实体;
- 使用的第三方软件中的BUG;
- 启动参数内存值设定的过小
- OutOfMemoryError: PermGen space
- 问题分析: 加载了太多的类,比如第三方jar包
- 解决方案: 增加java虚拟机中的XX:PermSize和XX:MaxPermSize参数的大小
- OutOfMemoryError: Java heap space
- 检查程序,看是否有死循环或不必要地重复创建大量对象。找到原因后,修改程序和算法。
- 增加Java虚拟机中Xms(初始堆大小)和Xmx(最大堆大小)参数的大小。如:set JAVA_OPTS= -Xms256m -Xmx1024m
- 常见代码引起的OOM的情况
- 检查代码中是否有死循环或递归调用。
- 检查是否有大循环重复产生新对象实体。
- 检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
- 检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。
常用JVM异常排查手段
- 监控工具的使用,自带的JConsole、visualVM的使用
- 图形化显示 堆区内存变化、线程数、CPU使用等情况
- visualVM可以直接生成内存快照、线程快照,也可以直接帮你计算最大的20个Object
- 常用命令:
- jmap (-heap 查看堆区的配置信息)
- jstat(gcutil看即时的各个区域的变化情况、capacity查看容量情况)
gc日志分析(启动的时候设置参数,生成gc日志,然后用工具分析gc日志,比如FGC的次数、YGC的次数等信息)
快照分析(可以通过工具、命令、或者启动参数配置获取到快照,然后利用工具分析)
FullGC触发的情况
- 老年代空间不足:
- 真不足,新生代出来的对象进入老年代,而老年代的剩余空间不足
- 预判不足,统计得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间
- 连续不足,如果是CMS的话,即使内存剩余够。但是内存碎片太多,没有连续的内存装下原本可以装下的对象
- 紧急不足,CMS在FullGC的时候,因为是4个阶段清楚,未完成时新垃圾产生,而剩余空间不足,也会再次出发FullGC,而且是以Serial Old单线程的形势执行。
解决方案:调优时应尽量做到让对象在Minor GC阶段被回收
- 调大堆区的大小,默认MaxSize是总内存的1/4,初始值是1/64
- 代码里面,尽量不要有太大的对象产生,可以用分批读取,让该对象不直接进入老年代,而是被YGC回收掉
- 调大年轻代的大小
- 调大存活ratio,默认是15
- Perm区满了:解决方案:调大Perm区的大小,或者代码中不要加载过多的class
- 代码调用 System.gc()。可以设置jvm参数,禁止程序调用这个方法。
事务的4大特性(ACID)
- 原子性:要么全部成功,要么全部失败
- 一致性:事务执行前和执行后必须处于一致性状态,用户A和用户B的前加起来一共是5000; 无论AB用户之间是如何相互转换的,事务结束后两个用户的钱加起来还是5000,这就是事务的一致性。
- 隔离性:当多个用户并发访问数据库时,数据库为每一个用户开启的事务,不被其他事务的操作所干扰,多个并发事务之间要相互隔离。
隔离性差会导致常见的问题:脏读取、不可重复读、幻读 - 持久性:一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便在数据库系统遇到故障的情况下也不会丢失事物的操作。
TCP与UDP区别总结:
http://blog.csdn.net/xiaobangkuaipao/article/details/76793702
- TCP面向连接(如打电话要先拨号建立连接)确认信号互相通畅;UDP是无连接的,即发送数据之前不需要建立连接
- TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达,发送有个确认的过程;UDP尽最大努力交付,即不保证可靠交付
- Tcp通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。
- UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
- 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
- TCP对系统资源要求较多,UDP对系统资源要求较少
TCP的三次握手和四次挥手(双方SYN、FIN、ACK)
三次握手:客户端向服务端发起请求,服务端确认收到请求确认是否能接收,客户端确认能接收并反馈给服务端。通信通道建立。 socket.connect()
A –> B, seq=x,SYN=1,A请求和服务器B进行连接,并发送请求序列号x
B –> A, ACK=1,ack=x+1,SYN=1,seq=y, B收到并同意A的链接x请求,确认码是x+1,;然后服务器B请求和A进行连接,SYN=1.请求码是y
A –> B, ACK=1,ack=y+1,seq=x+1;A同意B的编号为y的链接请求,并标志该次的序列号为x+1四次挥手:客户端向服务端发起断开请求,服务端确认收到请求并且把缓存的数据全部返回给客户端,服务端发送同意终止,客户端收到2次请求后确认断开并告知服务器 socket.close()
A –> B, seq=x,FIN=1,A请求和服务器B断开连接,并发送请求序列号x
B –> A, ACK=1,ack=x+1,seq=u, B同意seq为x的断开请求
B –> A, FIN=1,seq=v,ack=x+1,B请求和A断开连接,请求码v,这是针对seq为x的断开连接的最终同意
A –> B, ACK=1,ack=v+1,seq=x+1,A确认收到了B的通知,等待2*MSL,断开连接
TCPIP协议封装的消息格式
https://blog.csdn.net/hongse_zxl/article/details/50036305
- 16位源端口号、16位目标端口号
- 32位请求序列号(seq)
- 32位确认序列号(ack)
- 4位首部大小,6位预留,6位Flag ,16位窗口大小
- 16位校验和、16位紧急指针
- 可选项和数据
TCP阻塞控制
为了保护Server端的带宽,TCP采用了滑动窗口机制,由server动态调整窗口容量,分批确认,从而实现控流,发送方和接收方各有一个活动窗口缓存,为了准备重传。
控流方式:
- 慢启动:启动时限制可发送包大小,然后指数增长到阈值,ack确认一个增长一些,再线性增长,防止一连接就大量包
- Nagle算法:发送端缓存满一个ack包时再发送,避免小包
- Cork算法:当滑动窗口值小于某值时,停止发送,一直到接收端调整到该阈值时才允许发送
TCP重传机制
- 超时重传(超过一段时间未收到ack的话,再一定次数范围内不停的重传)
- 快速重传 当接收方收到的数据包是不正常的序列号,那么接收方会重复把应该收到的那一条ACK重复发送,这个时候,如果发送方收到连续3条的同一个序列号的ACK,那么就会启动快速重传机制,把这个ACK对应的发送包重新发送一次。
- 选择重传(通过报文记录信息补发丢失的包)
SQL注入的防止
- 尽量使用预编译语句,比如PreparedStatement,或者是成熟的持久化框架,JPA、Mybatis
- 必须使用原生SQL的话,比如存储过程的参数,或者Mybatis要用 $拼接的时候,需要 正则表达式 校验
Collection 和Collections的区别
- Collections是工具类,常用的方法,sort(List), copy(tarList,srcList),min(List)等操作
- Collection是接口,子类接口有 List、Set、Queue,抽象方法:isEmpty()、clear()、contains()等方法