1.JVM内存分哪几个区,每个区的作用是什么
方法区、虚拟机栈、本地方法栈、堆、程序计数器
方法区:主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后
的代码等数据;该区域是被线程共享的;方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中;该区内很少发生垃圾回收,但是并不代表不发生 GC,在这里进行的 GC 主要是对方法区里的常量池和对类型的卸载
虚拟机栈:也就是栈内存,它为 java 方法服务,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口等信息;虚拟机栈是线程私有的,它的生命周期与线程相同;局部变量表里存储的是基本数据类型、returnAddress 类型(指向一条字节码指令的地址)和对象引用,这个对象引用有可能是指向对象起始地址的一个指针,也有可能是代表对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译器间确定;操作数栈的作用主要用来存储运算结果以及运算的操作数,它不同于局部变量表通过索引来访问,而是压栈和出栈的方式;每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接.动态链接就是将常量池中的符号引用在运行期转化为直接引用。
本地方法栈:本地方法栈和虚拟机栈类似,为Native方法服务。
堆:java 堆是所有线程所共享的一块内存,在虚拟机启动时创建,几乎所有的对象实例都在这里创建,因此该区域经常发生垃圾回收操作。
程序计数器:内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个 java 虚拟机规范没有规定任何 OOM 情况的区域。
2.创建线程有哪几种方式?分别有什么不同?
继承Thread类,重写run方法;
实现Runnable接口,重写run方法(比继承Thread类好用,实现接口还可以继承类,避免了单继承带来的局限性);
实现Callable接口,并重写call方法;
使用Executor框架创建线程池。Executor框架是juc里提供的线程池的实现。
继承于Thread类的线程类,可以直接调用start方法启动线程(使用static也可以实现资源共享).一个线程(对象)只能够执行一次start(),而且不能通过Thread实现类对象的run()去启动一个线程。
实现Runnable接口的类需要再次用Thread类包装后才能调用start方法。(三个Thread对象包装一个类对象,就实现了资源共享)。
Callable和Runnable功能差不多,但是相比Runnable来说还是有很多区别的,主要体现在以下3点:
(1)Callable的call方法有返回值并且可以抛异常,而Runnable的run方法就没有返回值也没有抛异常。
(2)Callable运行后可以拿到一个Future对象,这个对象表示异步计算结果,可以从通过Future的get方法获取到call方法返回的结果。但要注意调用Future的get方法时,当前线程会阻塞,直到call方法返回结果。
(3)Runnable是作为线程的构造参数运行的,Callable是作为线程池的submit方法的参数运行的。
3.说一下设计模式的六大原则,分别代表什么?
单一职责原则:一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因
开闭原则:一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展
里氏代换原则:所有引用基类(父类)的地方必须能透明地使用其子类的对象。
依赖倒置原则:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象,其核心思想是:要面向接口编程,不要面向实现编程。
每个类尽量提供接口或抽象类,或者两者都具备;
变量的声明类型尽量是接口或者是抽象类;
任何类都不应该从具体类派生;
使用继承时尽量遵循里氏替换原则。
接口隔离原则:使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
迪米特法则:一个软件实体应当尽可能少地与其他实体发生相互作用。只与你的直接朋友交谈,“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。
4.string、stringbuilder、stringbuffer区别
String是字符串常量,它的值是不可变的,每次对String的操作都会生成新的String对象
StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象,StringBuilder 的方法不是线程安全的。
5.描述HashMap底层原理?
HashMap由数组和链表来实现对数据的存储
HashMap采用Entry数组来存储key-value对,每一个键值对组成了一个Entry实体,Entry类实际上是一个单向的链表结构,它具有Next指针,可以连接下一个Entry实体,以此来解决Hash冲突的问题;HashMap里面实现一个静态内部类Entry,其重要的属性有 hash,key,value,next。HashMap里面用到链式数据结构,Entry类里面有一个next属性,作用是指向下一个Entry。
Jdk1.8之后HashMap采用数组+链表+红黑树实现。当链表长度超过阈值(8)时,将链表转换为红黑树。在性能上进一步得到提升。
6.描述Servlet生命周期
当需要Servlet来处理请求时,web容器会检查缓存中是否存在对应的Servlet,如果没有就创建,如果缓存中存在了,那么就直接拿来使用。所以说,Servlet是在接收到第一个处理请求时被创建的,也可以通过配置load-on-startup的值为1设置Servlet随服务器启动就创建对象。(特殊业务需求才使用程序启动自动创建Servlet对象,否则会增加服务器的启动时间,消耗性能)
当web容器创建Servlet成功后,会且只会调用一次init()方法,在后续每次需要这个Servlet处理请求时不再调用,可以利用init对Servlet进行初始化操作,web容器在创建Servlet对象时,会创建ServletConfig对象(代表Servlet配置),并在调用init方法时,把该对象当作参数传递进来,通过该对象可以获取Servlet的信息,所以调用init方法时可以通过ServletConfig获取Servlet的信息。每次服务器接收到一个Servlet请求时,服务器会产生一个新的线程,根据http请求类型,调用对应的doGet、doPost方法进行处理;
当web服务器被关闭时,会把Servlet进行销毁。在Servlet生命周期结束时会且只会调用一次destroy()方法把Servlet销毁,关闭数据库连接,停止后台线程,释放资源等。我们可以重写destroy()方法处理被销毁之前的业务。在调用destroy方法后,Servlet对象会被标记为垃圾回收,等待jvm虚拟机回收。
7.抽象类和接口的区别
抽象类可以有构造方法,接口中不能有构造方法;
抽象类中可以有普通成员变量,接口中没有普通成员变量;
抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法;
一个类可以实现多个接口,但只能继承一个抽象类。
抽象类中可以包含静态方法,接口中不能包含静态方法;
jdk1.8之后接口可以包含静态方法和默认方法
抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型;
8.JDBC操作数据库的基本步骤,写出过程
加载数据库驱动。
建立数据库连接。
创建数据库操作对象。
定义操作的SQL语句。
执行数据库操作。
获取并操作结果集。
关闭对象,回收数据库资源
9.get和post请求的区别?
Get是不安全的,因为在传输过程,数据被放在请求的URL中;Post的所有操作对用户来说都是不可见的。
Get传送的数据量较小,这主要是因为受URL长度限制;Post传送的数据量较大,一般被默认为不受限制。
Get限制Form表单的数据集的值必须为ASCII字符;而Post支持整个ISO10646字符集。
Get执行效率却比Post方法好。Get是form提交的默认方法。
10.Session和Cookie的区别
cookie数据存放在客户的浏览器上,session数据放在服务器上。
cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗考虑到安全应当使用session。
session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用COOKIE。
单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
11.描述一下三次握手四次挥手
三次握手:客户端发送一个报文到服务器,报文段中的SYN=1,并选择一个seq=x,(即该请求报文的序号为x) 。此时,客户端进入同步已发送状态(SYN-SEND).SYN报文段不能携带数据,但是要消耗掉一个序号。
服务器收到请求报文后,若同意建立连接,则回复报文中,SYN=1,ACK=1,并选择一个seq = y,且报文中确认号为x+1,序号为y.此时服务器进入同步已接收状态(SYN-RCVD);
客户端收到服务器的同步确认后,对服务器发送确认的确认。将ACK=1,确认号为y+1,而报文首部的序号为x+1,将该报文发出后,客户端进入已连接状态(ESTABLISHED)。
服务器收到客户端的确认后,也进入已连接状态。
12.用递归写出来阶乘
Public int factorial(int n){
If(n==1){
Return 1;
}else{
Return factorial(n-1)*n;
}
}
13.默写二分查找
Public int find(int x,int[] arr){
Int min = 0;
Int max = arr.length()-1;
While(true){
If(min==max){
Return min;
}
If(x==arr[int((max-min)/2)]){
Return int((max-min)/2);
}else if(x>arr[int((max-min)/2)]){
Min = int((max-min)/2);
}else if(x<arr[int((max-min)/2)]){
Max = int((max-min)/2);
}
}
}
14.默写单例模式的懒汉式和饿汉式
饿汉:
public class Singleton{
Private static Singleton instance = new Singleton();
Private Singleton(){}
Public static Singleton getInstance(){
Return instance;
}
}
懒汉:
public class Singleton{
Private static Singleton instance;
Private Singleton(){}
Public static Singleton getInstance(){
If(instance == null){
Instance = new Singleton();
}else{
Return instance;
}
}
}
双重检查懒汉:
public class Singleton{
Private static volatile Singleton instance;
Private Singleton(){}
Public static Singleton getInstance(){
If(instance == null){
Synchronized{
If(instance == null){
Instance = new Singleton();
}
}
}
Return instance;
}
}
15.海滩上有一堆桃子,五只猴子来分。第一只猴子把这堆桃子凭据分为五份,多了一个,
这只猴子把多的一个扔入海中,拿走了一份。第二只猴子把剩下的桃子又平均分成五份,又
多了一个,它同样把多的一个扔入海中,拿走了一份,第三、第四、第五只猴子都是这样做
的,问海滩上原来最少有多少个桃子? 3121个
int i = 1;//最后一只猴子拿走的桃子数
int count;
int n;
while(true){
count = 0;
n = i;
for(int x=0; x<4; x++){
if((5i+1)%4==0){
i=(5i+1)/4;
count++;
}else{
break;
}
}
if(count==4){
System.out.println(5*i+1);
break;
}
i=n+1;
}