文章目录
推荐优秀面试总结: java面试汇总总结
1:深拷贝和浅拷贝的区别是什么?
浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
深拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
2:String,StringBuffer和StringBuilder区别
String是字符串常量,final修饰;StringBuffer字符串变量(线程安全);
StringBuilder 字符串变量(线程不安全)
StringBuffer是对对象本身操作,而不是产生新的对象,因此在有大量拼接的情况下,建议使用StringBuffer.
3:jvm垃圾回收
JVM GC回收区域:只回收堆区和方法区内的基本类型数据和对象
3.1:jvm空间结构
默认情况下,新生代(Young generation)、老年代(Old generation)所占空间比例为 1 : 2 。
新生代它被分成三个空间:
· 1个伊甸园空间(Eden),存储新创建的对象
· 2个幸存者空间(Fron Survivor、To Survivor)
默认情况下,新生代空间的分配:Eden : Fron : To = 8 : 1 : 1
3.2:对象怎么判断是否应该回收
这就是所谓的对象存活性判断,常用的方法有两种:
1.引用计数法;被多少对象引用
2:对象可达性分析.由于引用计数法存在互相引用导致无法进行GC的问题,所以目前JVM虚拟机多使用对象可达性分析算法.
3.3:简单的解释一下垃圾回收
Java 垃圾回收机制最基本的做法是分代回收。内存中的区域被划分成不同的世代,对象根据其存活的时间被保存在对应世代的区域中。一般的实现是划分成3个世代:年轻、年老和永久。内存的分配是发生在年轻世代中的。当一个对象存活时间足够长的时候,它就会被复制到年老世代中。对于不同的世代可以使用不同的垃圾回收算法。进行世代划分的出发点是对应用中对象存活时间进行研究之后得出的统计规律。一般来说,一个应用中的大部分对象的存活时间都很短。比如局部变量的存活时间就只在方法的执行过程中。基于这一点,对于年轻世代的垃圾回收算法就可以很有针对性.
垃圾回收从理论上非常容易理解,具体的方法有以下几种:
- 标记-清除 先标记再清除,空间碎片化,但是效率高。
- 标记-复制 内存分为1:1两块,浪费空间。eden区域一个,Survivor两个,内存空间比8 : 1 : 1,每次分配内存,只使用Eden和其中一块Survivor空间,发生GC回收时,把Eden和其中一块Survivor空间中存活的对象,复制到另一块空闲的Survivor空间,然后直接把Eden和使用过的那块Survivor空间清理掉。适合新生代
- 标记-整理 对标记清除算法的优化,解决碎片化。适合老年代
3.4:新生代GC收集的执行顺序
1、绝大多数新创建的对象会存放在伊甸园空间(Eden)。
2、在伊甸园空间执行第 1 次GC(Minor GC)之后,存活的对象被移动到其中一个幸存者空间(Survivor)。
3、此后每次 Minor GC,都会将 Eden 和 使用中的Survivor 区域中存活的对象,一次性复制到另一块空闲中的Survivor区,然后直接清理 Eden 和 使用过的那块Survivor 空间。
4、从以上空间分配我们知道,Survivor区内存占比很小,当空闲中的Survivor空间不够存放活下来的对象时,这些对象会通过分配担保机制直接进入老年代。
5、在以上步骤中重复N次(N = MaxTenuringThreshold(年龄阀值设定,默认15))依然存活的对象,就会被移动到老年代。
从上面的步骤可以发现,两个幸存者空间,必须有一个是保持空的。
我们需要重点记住的是,新创建的对象,是保存在伊甸园空间的(Eden)。那些经历多次GC依然存活的对象会经由幸存者空间(Survivor)转存到老年代空间(Old generation)。
4:多线程
4.1:什么是乐观锁悲观锁
乐观锁:乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
悲观锁:悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。
5:== 和 equals 的区别是什么?
对于基本类型和引用类型 == 的作用效果是不同的,如下所示:
基本类型:比较的是值是否相同;
引用类型:比较的是引用是否相同;
而 equals 默认情况下是引用比较(hashcode值一样但是不一定为true),只是很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。
6 :Java反射
- 1、定义:
反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法。在java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。
这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
- 2、哪里会用到反射机制?
jdbc就是典型的反射
Class.forName(‘com.mysql.jdbc.Driver.class’);//加载MySQL的驱动类
这就是反射。如hibernate,struts等框架使用反射实现的。
- 3、 反射的实现方式
第一步:获取Class对象,有4种方法:
1)Class.forName(“类的路径”);
2)类名.class
3)对象名.getClass()
4)基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象
7:java的io阻塞
先介绍几个概念
- 1 阻塞非阻塞 指的是在客户端进程发出请求后是否等待响应
阻塞: 意味着 客户端提出一个请求以后,在得到回应之前,只能等待
非阻塞: 意味着 客户端提出一个请求以后,在得到回应之前,客户端还可以做其他事情,可以继续提其他请求 - 2同步异步 指的是服务器端是否立刻处理IO请求
同步:意味着 服务器接受一个请求后,在返回结果以前不能接受其他请求
异步:意味着 服务器接受一个请求后,尽管还没有返回结果,但是可以继续接受其他请求
这样的理解其实是过于以偏概全的,因为这只是消息通知场景中的解释,但是通过代入客户端,服务器端更加方便初学者理解,因此在这里,暂且先这样解释。
Java中IO的方式通常分为同步阻塞的BIO,同步非阻塞的NIO,异步非阻塞的AIO,是没有异步阻塞的。
1、BIO同步阻塞
如果你还记得我们在学习程序设计语言之初,完成的socket编程,大概就会了解到BIO的基本工作原理。
一个socket连接一个处理线程,这个线程负责相关数据传输操作,每个服务器需要多个这样的处理线程,然而这种情况下,当多个socket向服务器申请建立连接时,受限于操作系统所允许的最大线程数量的限制,服务器不能提供相应数量的处理线程,没有分配到处理线程的连接就会阻塞等待,所以BIO是阻塞的。
又因为,当进行IO操作时,由Java自己本身处理IO的读写,所以是同步的。
2、NIO同步非阻塞
在BIO的基础上,NIO作出了改进。考虑到每一个socket连接只有在部分时间才进行了数据传输,大多数时间都是空闲的,而在空闲的时间依然要占用线程,这就造成了浪费。
当客户端的socket连接到服务器端时,不再是每个连接分配一个处理线程,而是服务器端会专门开辟一个”注册中心”统一对其进行管理。当检测到有IO事件请求发生的时候,服务器此时才启动一个处理线程对其进行处理,这种方法解决了因为线程数量的限制,导致socket接入阻塞的问题,因此是非阻塞的。
3、AIO异步非阻塞
在NIO中,当Java对IO请求进行处理时,可能会需要对后端资源(比如数据库连接)进行等待,并发量小的时候还好,一旦并发量增大,则也会对服务器的性能造成影响,因此,有人提出了AIO的概念。
与NIO不同的时,对于IO请求的处理,Java将其委托给了操作系统,不再阻塞等待,当操作系统完成了相应的IO处理之后,再去通知服务器,启动线程继续对结果进行处理。因此是异步的。
kafkaServer就是基于java nio和Reactor设计模式开发的网络模型。
kafkaServer中的同步和异步数据处理。
kafka关于是同步还是异步数据发送取决于参数:producer.type=sync
同步就是每条消息都发送,异步就是数据进行缓存到队列中再发送,异步发送时机取决于参数:
queue.buffering.max.ms=5000
queue.buffering.max.messages=10000
8:java的双亲委派机制
双亲委派机制是JVM加载类信息的一种方式。
加载某个类的class文件时,Java虚拟机采用的是双亲委派模式。
即把请求先交由父类处理,它是一种任务委派模式。
什么是双亲委派机制
当某个特定的类加载器它在接到需要加载类的请求时,这个类会首先查看自己已加载完的类中是否包含这个类,如果有就返回,没有的话就会把加载的任务交给父类加载器加载,以此递归,父类加载器如果可以完成类加载任务,就返回它,当父类加载器无法完成这个加载任务时,才会不得已自己去加载。这种机制就叫做双亲委派机制。
9:java设计模式
9.1:单例模式
单例模式包括懒汉式和饿汉式两种。
单例模式就是确保一个类在任何情况下都只有一个实例,并提供一个全局访问点。
懒汉式和饿汉式两种方式的优缺点:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题。
懒汉式为了解决线程不安全采用了双重检查机制。
1、饿汉式
//饿汉式单例类.在类初始化时,已经自行实例化
public class Singleton1 {
private Singleton1() {}
private static final Singleton1 single = new Singleton1();
//静态工厂方法
public static Singleton1 getInstance() {
return single;
}
}
2、懒汉式
就是使用实例时候再初始化
public class DoubleCheck {
//1.声明私有静态同步变量
private static volatile DoubleCheck instance;
//2.私有化构造
private DoubleCheck (){}
//3.提供公共静态方法,懒加载
//加入双重检查代码,解决线程安全
public static synchronized DoubleCheck getInstance() {
if (instance == null) {
synchronized (DoubleCheck .class) {
if (instance == null) {
instance = new DoubleCheck ();
}
}
}
return instance;
}
}
10:1JDK 和 JRE 有什么区别?
JDK:Java Development Kit 的简称,Java 开发工具包,提供了 Java 的开发环境和运行环境。
JRE:Java Runtime Environment 的简称,Java 运行环境,为 Java 的运行提供了所需环境。
具体来说 JDK 其实包含了 JRE,同时还包含了编译 Java 源码的编译器 Javac,还包含了很多 Java 程序调试和分析的工具。简单来说:如果你需要运行 Java 程序,只需安装 JRE 就可以了,如果你需要编写 Java 程序,需要安装 JDK。
11:面向对象的基本特性
1、封装
封装性就是尽可能的隐藏对象内部细节,对外形成一道边界,只保留有限的接口和方法与外界进行交互。封装的原则是使对象以外的部分不能随意的访问和操作对象的内部属性,从而避免了外界对对象内部属性的破坏。
(1)可以通过对类的成员设置一定的访问权限,实现类中成员的信息隐藏。
private:类中限定为private的成员,只能被这个类本身访问。
default:类中不加任何访问权限限定的成员属于缺省的(default)访问状态,可以被这个类本身和同一个包中的类所访问。
protected:类中限定为protected的成员,可以被这个类本身、它的子类(包括同一个包中以及不同包中的子类)和同一个包中的所有其他的类访问。
public:类中限定为public的成员,可以被所有的类访问。
(2)封装的优点
良好的封装能够减少耦合
类内部的结构可以自由修改
可以对成员变量进行更精确的控制
隐藏信息,实现细节
(3)代码实例
将 id、name 和 age 属性设置为私有的,只能本类才能访问,其他类都访问不了,如此就对信息进行了隐藏。
提供set方法进行赋值,提供get方法进行取值。
赋值方法set中的this的作用是解决显式参数与局部变量同名的问题。
package com.nezha.javase;
public class Student {
//将 id、name 和 age 属性设置为私有的,只能本类才能访问,其他类都访问不了,如此就对信息进行了隐藏。
private Integer id;
private String name;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
2、继承
子类的对象拥有父类的全部属性与方法,称作子类对父类的继承。
Java中父类可以拥有多个子类,但是子类只能继承一个父类,称为单继承。
继承实现了代码的复用。
Java中所有的类都是通过直接或间接地继承java.lang.Object类得到的。
子类不能继承父类中访问权限为private的成员变量和方法。
子类可以重写父类的方法,即命名与父类同名的成员变量。
Java中通过super来实现对父类成员的访问,super用来引用当前对象的父类。super 的使用有三种情况:
访问父类被隐藏的成员变量
调用父类中被重写的方法
调用父类的构造函数
3、多态
对象的多态性是指在父类中定义的属性或方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为。这使得同一个属性或方法在父类及其各个子类中具有不同的语义。
Java的多态性体现在两个方面:由方法重载实现的静态多态性(编译时多态)和方法重写实现的动态多态性(运行时多态)。
编译时多态:在编译阶段,具体调用哪个被重载的方法,编译器会根据参数的不同来静态确定调用相应的方法。
运行时多态:由于子类继承了父类所有的属性(私有的除外),所以子类对象可以作为父类对象使用。程序中凡是使用父类对象的地方,都可以用子类对象来代替。一个对象可以通过引用子类的实例来调用子类的方法。
4、重载
方法重载是让类以统一的方式处理不同数据类型的手段。
一个类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义。调用方法时通过传递给它们的不同参数个数和参数类型来决定具体使用哪个方法。
返回值类型可以相同也可以不相同,无法以返回型别作为重载函数的区分标准。
5、重写
子类对父类的方法进行重新编写。如果在子类中的方法与其父类有相同的的方法名、返回类型和参数表,我们说该方法被重写 (Overriding)。
如需父类中原有的方法,可使用super关键字,该关键字引用了当前类的父类。
子类函数的访问修饰权限不能低于父类的。
12:java的数据类型
Java中的数据类型整体是2大类:基本数据类型和引用数据类型,其中基本数据类型有3种8个子类,引用类型有3种
1KB=1024B
1B=8byte字节
基本类型和引用类型区别
1、基本类型会有默认值,所有引用类型的默认值都是null
2、值传递不一样,在方法等传参时引用类型传递的是引用的地址,但是对于基本类型来说传递的是值。所以注意在方法传参时候值的传递和引用区别,需要返回传递参数修改结果的不同。
3、不同的数据类型占用不同的存储空间,能更加合理的使用空间。