1、Java中的8种基本类型是什么?
整型:byte(8bit) short(16bit) int(32bit) long(64bit)
浮点型:float(32bit) double(64bit)
逻辑型:boolean (1bit)
字符型:char(16bit)
2、==和 equals()的区别
==比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。比较的是真正意义上的指针操作。
equals用来比较的是两个对象的内容是否相等
3、string buffer和string builder的区别
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
每次对String的操作都会生成新的String对象,和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
4、java string对象 “+”和append链接两个字符串之间的差异;
相比String每次+都重新创建一个String对象,重新开辟一段内存不同,StringBuilder和StringBuffer的append都是直接把String对象中的char[]的字符直接拷贝到StringBuilder和StringBuffer的char[]上,效率比String的+高得多。当然,当StringBuilder和StringBuffer的char[]长度不够时,也会重新开辟一段内存。
5、静态类的特征、优点;
静态有一些特点:
1.全局唯一,任何一次的修改都是全局性的影响
2.只加载一次,优先于非静态
3.使用方式上不依赖于实例对象。
4.生命周期属于类级别,从JVM 加载开始到JVM卸载结束。
优点:不需要在进行实例化。静态变量的值,直接赋新值即可,不需要参数传递,之后可以直接进行参数引用即可;静态方法可以直接通过"类名.方法"的形式进行方法调用。通常方法被多次调用,并且方法中没有动态方法引用的时候使用比较方便。
缺点:初始化加载到内存,如果后续没被引用,加大了内存负担和程序运行负担,影响程序运行效率(一般很小),并且静态变量如果多处被引用赋值,很可能导致参数值混乱(如果是不变的值,最后加上final修饰,强制不能修改)。
6、JAVA静态内部类和非静态内部类的区别:
非静态内部类看做外部类的非静态成员,静态内部类可以看做外部类的静态成员。
(1)非静态内部类实例化必须有关联的外部类的实例化对象,静态内部类不需要。
(2)非静态内部类对象中隐式保存了指向外部类对象的引用,因此可以访问外部类的所有成员(方法和字段,无论静态还是非静态)。而静态内部类只能访问外部类的静态成员。
(3)非静态内部类中不能有静态的成员(方法或字段),静态内部类中可以有。
静态内部类只是嵌套在外部类里面,仅此而已,外部类对象和静态内部类对象之间并没有什么关系。
7、接口和抽象类的区别
抽象类是单一继承,接口是多重实现,子类只能有一个父类,而子类可以实现多个接口。
抽象类表示“从属”,是对种类的抽象,实现接口表示“组合”关系,是对行为的抽象。
接口中全是抽象方法,抽象类中可以有抽象方法,也可有方法体的方法。
接口中无构造方法,抽象类可有构造方法。
接口中的不可以有private方法和变量,接口中的变量只能为public static final,方法必须是 public abstract的(注意不能是static类型的),抽象类中可以有private方法和变量,因为抽象类中可以有具体的方法实现,在这些具体方法实现中可以用自己定义为private的变量和private方法。它不同于接口,因为接口中不能有方法的具体实现,其所有的方法都要求实现它的类来具体实现,所以,接口中的私有变量和私有方法就永远不会用到,所以接口中不会有private变量和private方法。
8、HashMap和Hashtable有什么区别?(百度)
HashMap允许键和值是null,而Hashtable则不允许键或者值是null。
Hashtable是同步的,而HashMap不是,所以HashMap更适用于单线程环境,Hashtable则适用于多线程环境。
9、Override 和 Overload 的区别(百度)
它们都是Java中多态的表现形式。
重写(Override)是父类与子类之间多态性的一种表现。当子类中定义的某方法与其父类的某方法有相同的方法名和参数,我们就说该方法被重写 (Override),当我们调用子类的对象使用该方法时,将调用子类重写后的方法,父类中的方法则被覆盖。
重载(Overload)是一个类中多态性的一种表现。如果在一个类中定义了多个相同方法名的方法,但它们的方法参数(参数个数或参数类型货参数顺序)不一致,则称为方法的重载。方法的重载与返回值的类型无关,与参数列表有关。
10、JAVA7之前 switch
括号中只能放int类型,之后可以放String和枚举类型。(其中byte short char可以自动提升为int类型,因此也可以放到括号里)。
11、类加载过程,即通过class文件创建相应的Class对象的过程。
一、装载(以二进制的形式读入class文件到方法区中,并在堆中实例化Class类的对象)
二、链接 (包括验证 准备 和 解析)
三、初始化
12、算法复杂度
13、遍历Set
set过程中删除元素不可使用set.remove() 而应使用迭代器删除iterator.remove()
14、递归的删除一个文件夹(百度)
/**
* 删除文件夹下所有的文件
*/
public static void delete(String path){
File f=new File(path);
//如果是目录,先递归删除
if(f.isDirectory()){
String[] list=f.list();
for(int i=0;iGC垃圾收集器
15在java语言中,可作为GC Roots的对象包括下面几种:(可达性分析算法)
虚拟机栈(栈帧中的本地变量表)中引用的对象;
方法区中类静态属性引用的对象;
方法区中常量引用的对象;
本地方法栈中JNI(即一般说的native方法)引用的对象。
16、什么是强引用,软引用,弱引用,虚引用?(百度)
⑴强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 ps:强引用其实也就是我们平时A = new A()这个意思。
⑵软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存(网页缓存,图片缓存)。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
SoftReference sr = new SoftReference(new String(“hello”));
⑶弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
WeakReference sr = new WeakReference(new String(“hello”));
⑷虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
二、多线程,多进程
1、要区分线程控制 线程同步和线程通信
线程控制是sleep() yield()和join()等操作
线程同步是volatile和synchronized
线程通信是wait() notify() 阻塞队列等.
某种意义上,线程同步也是线程通信的一种。
2、每个线程拥有自己的栈空间,所有线程共享堆空间.
栈是线程私有的,堆是线程共享的.
3、线程和进程的区别
进程是系统资源分配的基本单位,线程是CPU调度的基本单位.
每个进程具有自己独立的地址空间,进程之间是相互独立的。
所有线程之间共享父进程的地址空间。
进程上下文切换比线程开销大得多。
4、进程间通信的方法
共享内存:将一块公共的物理内存映射到多个进程的私有地址空间。
socket通信
消息队列:对消息队列有写权限的进程可以向消息队列中添加新的消息,对消息队列有读权限的进程可以从消息队列中读取新的消息。
5、线程通信问题要考虑同步监视器的锁池和等待池。
锁池和等待池中的线程都处于阻塞状态。
不同线程竞争CPU时间片问题。
6、为什么这些操作线程的方法要定义在object类中呢?
简单说:因为synchronized中的这把锁可以是任意对象,所以任意对象都可以调用wait()和notify();所以wait和notify属于Object。
7、线程池参数
核心线程池大小
最大线程池大小
最长等待时间
任务队列大小
拒绝处理任务的处理器
核心工作线程是否超时退出
三、锁
1、产生死锁的四个必要条件:
(1) 互斥条件:使用的资源是不能共享的。
(2) 请求与保持条件(hold and wait):线程持有一个资源并等待获取一个被其他线程持有的资源。
(3) 不剥夺条件:线程正在持有的资源不能被其他线程强行剥夺。
(4) 循环等待条件:线程之间形成一种首尾相连的等待资源的关系。
这四个条件是死锁的必要条件 ,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
2、synchronized和lock区别
1、synchronized是在JVM层面上实现的,而ReentrantLock是在代码层面上实现的。
2、lock比起synchronized增加了 响应中断锁等候 和 定时锁等候
3、synchronized(重量级锁)是悲观锁,lock为乐观锁。
4、lock可以设置为公平锁,即每次等待时间最长的线程获得锁。
3、如何解决死锁?
有三个方法:
1.进程在申请资源时,一次性得请求他所需要的所有资源。若无法满足则不能执行。
2.进程在申请新的资源时,释放已占有的资源。后面若还需要它们,则需要重新申请。
3.将系统中的资源顺序编号,规定进程只能依次申请资源。
四、Spring
1、SpringMVC
DispathcerServlet的 doServie方法处理请求:
1、设置上下文信息即request对象。
2、利用 handler mapping 找到请求对应的处理器。
3、将处理器进行适配
4、调用处理器(即页面控制器)的具体的业务对象的方法处理请求,返回ModelAndView
5、逻辑视图名指定的逻辑视图渲染数据,结果返回给用户。
五、数据库
1、数据库索引
分为 聚集索引 和 辅助索引,聚集索引决定数据的物理存储顺序,辅助索引是和数据分离出来的。
数据库索引从另一方面又分为B+树索引和哈希索引,B+树索引为传统意义上的索引是主要索引,3哈希索引是自适应的不能人为控制。
2、数据库引擎MySQL有两种InnoDB和MyISAM,区别有:
1、InnoDB支持事务,MyISAM不支持
2、InooDB支持行级锁,MyISAM不支持
3、InnoDB有聚集索引(聚集索引以主键为索引),索引决定数据的物理存储顺序,辅助索引的叶节点里存储的是主键值。而MyISAM数据和索引分开,索引中叶节点里存储的是行号。
3、事务
是一条或者多条数据库操作的集合,具有ACID四个特性。即原子性、一致性、隔离性 和持久性。
4、四种事务隔离级别
事务隔离级别
脏读
不可重复读
幻读
读未提交(read-uncommitted)
是
是
是
不可重复读(read-committed)
否
是
是
可重复读(repeatable-read)
否
否
是
串行化(serializable)
否
否
否
mysql中默认事务隔离级别是可重复读,不会锁住读取到的行。
读取已提交内容 和 可重复读 两种隔离级别通过MCVV(多版本并发控制)实现.
读取已提交内容:MVCC 读不加锁 写加锁
(读取历史版本)相对于可重复读,是最近的历史版本。
可重复读:MVCC+Read View +Gap锁 读不加锁写加锁(读取历史版本)
Read View保证事务过程中读取的一致性,即可重复读
Gap锁(解决当前读)+Read View(解决快照读) 解决幻读问题
MySQL的可重复读模式下,不会出现幻读。
在读取提交内容 和 可重复读 模式下,读操作都不加锁, 在串行化模式下读加锁
六、计算机网络
1、TCP 三次握手 四次挥手
SYN→
←ACK SYN
ACK→
FIN→
←ACK
←FIN
ACK→
http://www.centos.bz/wp-content/uploads/2012/08/100327002629.png
http://www.centos.bz/wp-content/uploads/2012/08/100327022731.jpg
2、TCP 三次握手 四次挥手 的本质,交换了什么,如何保证建立连接?????
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器 进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入 ESTABLISHED状态,完成三次握手。
通过这样的三次握手,客户端与服务端建立起可靠的双工的连接,开始传送数据。
三次握手的最主要目的是保证连接是双工的,可靠更多的是通过重传机制来保证的。
3、HTTP状态码
2XX 成功
3XX 重定向
4XX 请求错误
5XX 6XX 服务器错误
4、TCP第三次握手失败怎么办?
答:当客户端收到服务端的SYN+ACK应答后,其状态变为ESTABLISHED,并会发送ACK包给服务端,准备发送数据了。如果此时ACK在网络中丢失,过了超时计时器后,那么Server端会重新发送SYN+ACK包,重传次数根据/proc/sys/net/ipv4/tcp_synack_retries来指定,默认是5次。如果重传指定次数到了后,仍然未收到ACK应答,那么一段时间后,Server自动关闭这个连接。但是Client认为这个连接已经建立,如果Client端向Server写数据,Server端将以RST包响应,方能感知到Server的错误。
七、设计模式
1、设计模式
单例模式 工厂模式 策略模式 适配器模式 生产者消费者模式 观察者模式
java有哪些新建对象的方法
使用new关键字:这是我们最常见的也是最简单的创建对象的方式,通过这种方式我们还可以调用任意的构参函数(无参的和有参的)。比如:Student = new Student();
使用Class类的newInstance方法:我们也可以使用Class类的newInstance方法创建对象,这个newInstance方法调用无参的构造器创建对象,如:Student student2 = (Student)Class.forName(“根路径.Student”).newInstance(); 或者:Student stu = Student.class.newInstance();
使用Constructor类的newInstance方法:本方法和Class类的newInstance方法很像,java.lang.relect.Constructor类里也有一个newInstance方法可以创建对象。我们可以通过这个newInstance方法调用有参数的和私有的构造函数。如: Constructor constructor = Student.class.getInstance(); Student stu = constructor.newInstance(); 这两种newInstance的方法就是大家所说的反射,事实上Class的newInstance方法内部调用Constructor的newInstance方法。这也是众多框架Spring、Hibernate、Struts等使用后者的原因。
使用Clone的方法:无论何时我们调用一个对象的clone方法,JVM就会创建一个新的对象,将前面的对象的内容全部拷贝进去,用clone方法创建对象并不会调用任何构造函数。要使用clone方法,我们必须先实现Cloneable接口并实现其定义的clone方法。如:Student stu2 = stu.clone();这也是原型模式的应用。
使用反序列化:当我们序列化和反序列化一个对象,JVM会给我们创建一个单独的对象,在反序列化时,JVM创建对象并不会调用任何构造函数。为了反序列化一个对象,我们需要让我们的类实现Serializable接口。如:ObjectInputStream in = new ObjectInputStream (new FileInputStream(“data.obj”)); Student stu3 = (Student)in.readObject();
clone是浅拷贝还是深拷贝?两种拷贝有什么区别?
clone方法执行的是浅拷贝。
浅拷贝:我们这里说的浅拷贝是指我们拷贝出来的对象内部的引用类型变量和原来对象内部引用类型变量是同一引用(指向同一对象)。但是我们拷贝出来的对象和新对象不是同一对象。
简单来说,新(拷贝产生)、旧(元对象)对象不同,但是内部如果有引用类型的变量(比如string),新、旧对象引用的都是同一引用。
深拷贝:全部拷贝原对象的内容,包括内存的引用类型也进行拷贝
数组和链表区别?
数组在内存中连续,链表不连续;
数组静态分配内存(固定大小),链表动态分配内存(不指定大小);
数组元素在栈区(访问速度快,自由度小),链表元素在堆区(速度慢,自由度大);
数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n);
数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1)。
数组的特点是:寻址容易,插入和删除困难。
链表的特点是:寻址困难,插入和删除容易。
综合以上,对于快速访问数据,不经常有添加删除操作的时候选择数组实现,而对于经常添加删除数据,对于访问没有很高要求的时候选择链表。
hashMap怎么实现?用了什么结构?怎么扩容?负载因子是什么(重点)
实现原理:拉链法,我们可以理解为“链表的数组” ,HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对和next引用。(数组为主体,链表来解决哈希冲突)
Next
// 存储时:
int hash = key.hashCode(); // 这个hashCode方法这里不详述,只要理解每个key的hash是一个固定的int值
int index = hash % Entry[].length;
Entry[index] = value;
// 取值时:
int hash = key.hashCode();
int index = hash % Entry[].length;
return Entry[index];
举例:12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。
简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度依然为O(1),因为最新的Entry会插入链表头部,数组中存储的是最后插入的元素。只需要简单改变引用链即可,而对于查找操作来讲,此时就需要遍历链表O(n),然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。
打个比方, 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C。(c->b->a)
//实际存储的key-value键值对的个数
transient int size; (用transient关键字标记的成员变量不参与序列化过程)
//当前HashMap的容量大小,默认为16
int initialCapacity;
//阈值,当table == {}时,该值为初始容量(初始容量默认为16);当table被填充了,也就是为table分配内存空间后,threshold一般为 capacity*loadFactory。HashMap在进行扩容时需要参考threshold,后面会详细谈到
int threshold; = capacity*loadFactory
//threshold = capacity*loadFactory,当hashMap的size==threshold且发生了哈希冲突时,就会开始扩容
//负载因子,代表了table的填充度有多少,默认是0.75(大了节省内存空间,但容易导致哈希冲突,降低查询性能;小了查询性能好,但浪费内存空间)
final float loadFactor;
//用于快速失败,由于HashMap非线程安全,在对HashMap进行迭代时,如果期间其他线程的参与导致HashMap的结构发生变化了(比如put,remove等操作),需要抛出异常ConcurrentModificationException
transient int modCount;
Hashmap的扩容:
需要满足两个条件:当前数据存储的数量(即size())大小必须大于等于阈值;当前加入的数据是否发生了hash冲突。
每次扩容大小是之前的两倍。initialCapacity = initialCapacity * 2;(2^n)
哈希冲突:如果两个不同对象的hashCode mod length的结果相同(hashcode不相同),这种现象称为hash冲突。
若hashcode相同(即key相同),那么value会被新的值覆盖。
HashMap的equals方法重写为什么需要重写hashcode函数?如果不改写会怎么样?
首先equals与hashcode间的关系是这样的:
A、如果两个对象相同(即用equals比较返回true),那么它们的hashCode值一定要相同;
B、如果两个对象的hashCode相同,它们并不一定相同(即用equals比较返回false) 。但是同一个类的不同对象,当两者拥有相同hashcode的时候,则一定相等。
假设两个对象,重写了其equals方法,其相等条件是属性相等,就返回true。如果不重写hashcode方法,其返回的依然是两个对象的内存地址值,必然不相等。这就出现了equals方法相等,但是hashcode不相等的情况。这不符合hashcode的规则。
hashMap和hashTable区别?
HashMap允许键和值是null,而Hashtable则不允许键或者值是null。
Hashtable是同步的,而HashMap不是。
所以HashMap更适用于单线程环境,Hashtable则适用于多线程环境。
HashMap的keynull时,将元素添加到Entry[0]链表的表头;Key!=null但valuenull
在对应位置插入null值。
多线程情况下使用hashMap,应该做什么改变?ConcurrentHashMap和HashTable区别?哪个性能好
三种解决方案:
HashTable替换HashMap
Collections.synchronizedMap将HashMap包装起来
ConcurrentHashMap替换HashMap
ConcurrentHashMap和HashTable区别:
它们都可以用于多线程的环境,但是当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。因为ConcurrentHashMap引入了分割(segmentation),不论它变得多么大,仅仅需要锁定map的某个部分,而其它的线程不需要等到迭代完成才能访问map。简而言之,在迭代的过程中,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。
性能比较:ConcurrentHashMap性能更好!HashTable效率低下
缺点:当遍历ConcurrentMap中的元素时,需要获取所有的segment 的锁,使用遍历时慢。锁的增多,占用了系统的资源。使得对整个集合进行操作的一些方法(例如 size() 或 isEmpty() )的实现更加困难,因为这些方法要求一次获得许多的锁,并且还存在返回不正确的结果的风险。
二分查找,数组,链表,哈希表查找元素的时间复杂度
方法
查找
插入
二分查找
O(logN)
O(lgN+N/2)
数组
O(1)
O(N)
链表
O(N)
O(1)
哈希表(HashMap)
O(1)
O(1)
synchronized能修饰什么?
修饰synchronized(obj任意自定义对象)
public int synMethod(int a1){
synchronized(a1) {
//一次只能有一个线程进入
}
}
使用synchronized(obj)进行同步操作,只要别的线程不访问这个同步代码块就不会堵塞!也就是说可以访问别的同步代码块。
修饰synchronized(this)
public void bar() {
synchronized(this) {
//access both x and y here
}
//do something here - but don't use shared resources
}
当一个线程访问类的一个synchronized (this)同步代码块时,其它线程对同一个类中其它的synchronized (this)同步代码块的访问将是堵塞,但是可以访问其他的非同步代码块。
修饰synchronized(.class)
synchronized (.class) {
}
对整个class进行加锁,整个类一次只能由一个线程访问!对于含有静态方法和静态变量的代码块的同步,我们通常用synchronized(*.class)来加锁.
方法声明时使用(放在范围操作符(public等)之后,返回类型声明(void等)之前):public synchronized void synMethod() {
//方法体
}
对整个class进行加锁,整个类一次只能由一个线程访问!
10、线程池有哪几种?分别的应用情况
A. newCachedThreadPool:创建一个缓存线程池
底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可用线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
适用:执行很多短期异步的小程序或者负载较轻的服务器
B. newFixedThreadPool:创建一个定长线程池
底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue() 无解阻塞队列
通俗:创建可容纳固定数量线程的池子,每个线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
适用:执行长期的任务,性能好很多
C. newSingleThreadExecutor: 创建一个单线程线程池
底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue() 无解阻塞队列
通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
适用:一个任务一个任务执行的场景
D. newScheduledThreadPool: 创建一个定长线程池
底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列
通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
适用:周期性执行任务的场景
线程池:我们可以把并发执行的任务传递给一个线程池,来替代为每个并发执行的任务都启动一个新的线程。(存放线程的容器)
为什么要用线程池:
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(线程太少浪费资源,太多占内存)
一个线程池包括以下四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
11、YoungGC是什么?(OldGC?)
MinorGC == YoungGC
MajorGC == OldGC
新生代 GC(Minor GC/ YoungGC):
指发生在新生代的垃圾收集动作(GC)。因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。
触发条件:当Eden区满时,触发Minor GC。 Survivor满不会引发GC。
老年代 GC(Major GC / Full GC/ OldGC):
指发生在老年代的 GC,出现了 Major GC,经常会伴随至少一次的 Minor GC(但非绝对的,在 ParallelScavenge 收集器的收集策略里就有直接进行 Major GC 的策略选择过程) 。MajorGC 的速度一般会比 Minor GC 慢 10倍以上。
触发条件:
当年老代满时会引发Full GC,Full GC将会同时回收年轻代、年老代,
当永久代满时也会引发Full GC,会导致Class、Method元信息的卸载
/*******************************************/
新生代(Young Generation)
新生代的目标就是尽可能快速的收集掉那些生命周期短的对象,一般情况下,所有新生成的对象首先都是放在新生代的。
老年代(Old Generation)
老年代存放的都是一些生命周期较长的对象,就像上面所叙述的那样,在新生代中经历了N次垃圾回收后仍然存活的对象就会被放到老年代中。
永久代(Permanent Generation)
永久代主要用于存放静态文件,如Java类、方法等。
/*******************************************/
12、垃圾回收机制?
A. 那些内存需要回收?(对象是否可以被回收的两种经典算法: 引用计数法 和 可达性分析算法)(java用了可达性分析算法)
a. 引用计数法:
引用计数算法是通过判断对象的引用数量来决定对象是否可以被回收。
任何引用计数为0的对象实例可以被当作垃圾收集。
b. 可达性分析算法:
可达性分析算法是通过判断对象的引用链是否可达来决定对象是否可以被回收。
程序把所有的引用关系看作一张图,通过一系列的名为 “GC Roots” 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain)。当一个对象到 GC Roots 没有任何引用链相连(用图论的话来说就是从 GC Roots 到这个对象不可达)时,则证明此对象是不可用的,如下图所示。
/*******************************************/
在Java中,可作为 GC Roots 的对象包括以下几种:
虚拟机栈(栈帧中的局部变量表)中引用的对象;
方法区中类静态属性引用的对象;
方法区中常量引用的对象;
本地方法栈中Native方法引用的对象;
/*******************************************/
B. 什么时候回收? (堆的新生代、老年代、永久代的垃圾回收时机,MinorGC 和 FullGC)
C. 如何回收?(三种经典垃圾回收算法(标记清除算法、复制算法、标记整理算法)及分代收集算法 和 七种垃圾收集器)
1、标记清除算法(产生内存碎片)
标记-清除算法分为标记和清除两个阶段。该算法首先从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象并进行回收,如下图所示。
/*******************************************/
效率问题:标记和清除两个过程的效率都不高;
空间问题:产生太多不连续的内存碎片
http://static.zybuluo.com/Rico123/mkqv5cf4vg8v72wvxtz0abwz/%E6%A0%87%E8%AE%B0-%E6%B8%85%E9%99%A4%E7%AE%97%E6%B3%95
/*******************************************/
2、复制算法——新生代
复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
这种算法适用于对象存活率低的场景,比如新生代。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。该算法示意图如下所示:
http://static.zybuluo.com/Rico123/0gzyeoiz3mf7n20jitv2g2aw/%E5%A4%8D%E5%88%B6%E7%AE%97%E6%B3%95
事实上,现在商用的虚拟机都采用这种算法来回收新生代。因为研究发现,新生代中的对象每次回收都基本上只有10%左右的对象存活,所以需要复制的对象很少,效率还不错。
3、标记整理算法(不会产生内存碎片)——老年代
复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。标记整理算法的标记过程类似标记清除算法,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,类似于磁盘整理的过程,该垃圾回收算法适用于对象存活率高的场景(老年代),其作用原理如下图所示。
http://static.zybuluo.com/Rico123/yn5j5ojk6mz9axjutk4hogxq/%E6%A0%87%E8%AE%B0-%E6%95%B4%E7%90%86%E7%AE%97%E6%B3%951.jpg
标记整理算法与标记清除算法最显著的区别是:标记清除算法不进行对象的移动,并且仅对不存活的对象进行处理;而标记整理算法会将所有的存活对象移动到一端,并对不存活对象进行处理,因此其不会产生内存碎片。标记整理算法的作用示意图如下:
http://static.zybuluo.com/Rico123/b26me40cjhiqkeb2vl6jb807/%E6%A0%87%E8%AE%B0%E6%95%B4%E7%90%86%E7%AE%97%E6%B3%95%E7%A4%BA%E6%84%8F%E5%9B%BE.png
4、分代收集算法(结合复制算法和标记清楚算法/标记整理算法)
基于这样一个事实:不同的对象的生命周期(存活情况)是不一样的,而不同生命周期的对象位于堆中不同的区域,因此对堆内存不同区域采用不同的策略进行回收可以提高 JVM 的执行效率。
商用虚拟机使用的都是分代收集算法:新生代对象存活率低,就采用复制算法;老年代存活率高,就用标记清除算法或者标记整理算法。Java堆内存一般可以分为新生代、老年代和永久代三个模块,如下图所示:
http://static.zybuluo.com/Rico123/bqgcx5anvacebj1yxpaufk0x/%E5%88%86%E4%BB%A3%E6%94%B6%E9%9B%86%E7%AE%97%E6%B3%95%E6%80%BB.jpg
GC算法总结:
七种垃圾回收器:
Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;
ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。
13、Java中final、finally和finalize的区别
final修饰符(关键字)。A. 被final修饰的类,不能被子类继承。【因此一个类不能既被abstract声明,又被final声明】 B. 被final修饰的方法,在使用的过程中不能被修改,只能使用,即不能方法重写 C. 被声明为final的变量必须在声明时给出变量的初始值,使用的过程中不能被修改,只能读取。
finally是在异常处理时提供finally块来执行任何清除操作。不管有没有异常被抛出、捕获,finally块都会被执行。try块中的内容是在无异常时执行到结束。catch块中的内容,是在try块内容发生catch所声明的异常时,跳转到catch块中执行。finally块则是无论异常是否发生,都会执行finally块的内容,所以在代码逻辑中有需要无论发生什么都必须执行的代码,就可以放在finally块中。
finalize是方法名。java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者被执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。
14、栈、队列的底层实现原理
栈(stack)又名堆栈,它是一种先进后出(FILO)的线性表。
<1>优点:存取速度比堆快,仅次于寄存器,栈数据可以共享;
<2>缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
可以用链表或数组实现。
JAVA中通过数组来实现队列。
【java工具包中的Stack是继承于Vector(矢量队列)的,由于Vector是通过数组实现的,这就意味着,Stack也是通过数组实现的,而非链表。】
队列(queue),它是一种先进先出(FIFO)的线性表。
可以用链表(LinkedList)或数组(Array/ArrayList)实现。
JAVA中通过LinkedList来实现队列。
15、ArrayList和LinkedList的大致区别:
1.ArrayList是实现了基于动态数组的数据结构,LinkedList是基于链表结构。
2.对于随机访问的get和set方法,ArrayList要优于LinkedList,因为LinkedList要移动指针。
3.对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。
所以在我们进行对元素的增删查操作的时候,进行查操作时用ArrayList,进行增删操作的时候最好用LinkedList。
16、常用设计模式:
【单例模式 工厂模式 策略模式 适配器模式 生产者消费者模式 观察者模式】
1、单例模式 Singleton
如果一个类始终只能创建一个实例,则这个类被称为单例类,这种模式就被称为单例模式。
一般建议单例模式的方法命名为:getInstance(),可以有参数,一般无参数。
优点:
1)减少创建Java实例所带来的系统开销
2)便于系统跟踪单个Java实例的生命周期、实例状态等。
2、简单工厂模式 StaticFactory Method
由一个工厂对象决定创建出哪一种产品类的实例。
优点:
让对象的调用者和对象创建过程分离,当对象调用者需要对象时,直接向工厂请求即可。
3、工厂模式 Factory Method
程序可以为不同产品类提供不同的工厂,不同的工厂类生产不同的产品(也就是一个工厂生产一个产品,通过选择不同的工厂获得不同的产品。)
4、抽象工厂模式 Abstract Factory Method
为了解决客户端代码与不同工厂类耦合的问题。在工厂类的基础上再增加一个工厂类,该工厂类不制造具体的被调用对象,而是制造不同工厂对象。(建立一个生产工厂的工厂,把原来工厂模式的工厂作为方法)
5、代理模式 Proxy Method
当客户端代码需要调用某个对象时,客户端实际上不关心是否准确得到该对象,它只要一个能提供该功能的对象即可,此时我们就可返回该对象的代理(Proxy)。
6、命令模式 Command Method
某个方法需要完成某一个功能,完成这个功能的大部分步骤已经确定了,但可能有少量具体步骤无法确定,必须等到执行该方法时才可以确定。在Java中,传入该方法的是一个对象,该对象通常是某个接口的匿名实现类的实例,该接口通常被称为命令接口,这种设计方式也被称为命令模式。
7、策略模式 Strategy Method
策略模式用于封装系列的算法,这些算法通常被封装在一个被称为Context的类中,客户端程序可以自由选择其中一种算法,或让Context为客户端选择一种最佳算法——使用策略模式的优势是为了支持算法的自由切换。
可以考虑使用配置文件来指定用户使用哪种具体算法——这就彻底分离客户端代码和具体算法策略。
8、观察者模式 Observer Method
观察者模式定义了对象间的一对多依赖关系,让一个或多个观察者对象观察一个主题对象。当主题对象的状态发生变化时,系统能通知所有的依赖于此对象的观察者对象,从而使得观察者对象能够自动更新。
在观察者模式中,被观察的对象常常也被称为目标或主题(Subject),依赖的对象被称为观察者(Observer)。
9、生产者消费者模式 ProducerConsumer Method
生产者和消费者在同一时间段内共用同一存储空间,生产者向空间(仓库/缓冲区)里生产数据,而消费者取走数据。
优点:
1)解耦
2)支持并发
生产者与消费者是两个独立的并发体,他们之间是用缓冲区作为桥梁连接。
3)支持忙闲不均
当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。 等生产者的制造速度慢下来,消费者再慢慢处理掉。
1.1:面向对象的特征(必会)
面向对象的特征:封装、继承、多态、抽象。
封装:就是把对象的属性和行为(数据)结合为一个独立的整体,并尽可能隐藏对象的内部实现细节,就是把不想告诉或者不该告诉别人的东西隐藏起来,把可以告诉别人的公开,别人只能用我提供的功能实现需求,而不知道是如何实现的。增加安全性。
继承:子类继承父类的数据属性和行为,并能根据自己的需求扩展出新的行为,提高了代码的复用性。
多态:指允许不同的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息就是函数调用)。封装和继承几乎都是为多态而准备的,在执行期间判断引用对象的实际类型,根据其实际的类型调用其相应的方法。
抽象:表示对问题领域进行分析、设计中得出的抽象的概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。在Java中抽象用 abstract 关键字来修饰,用 abstract 修饰类时,此类就不能被实例化,从这里可以看出,抽象类(接口)就是为了继承而存在的。
1.2:基本数据类型有哪些(了解)

1.3:JDK JRE JVM 的区别(必会)

JDK(Java Development Kit)是整个 Java 的核心,是java开发工具包,包括了 Java 运行环境 JRE、Java 工具和 Java 基础类库。
JRE(Java Runtime Environment)是运行 JAVA 程序所必须的环境的集合,包含java虚拟机和java程序的一些核心类库。
JVM 是 Java Virtual Machine(Java 虚拟机)的缩写,是整个 java 实现跨平台的最核心的部分,能够运行以 Java 语言写作的软件程序。
1.4:重载和重写的区别(了解)
重载: 发生在同一个类中,方法名必须相同,参数类型不同.个数不同.顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。(同类同名不同参数)
重写: 发生在父子类中,方法名.参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类, 访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。(同名同参不同类)
1.5:java中==和equals的区别(必会)
== 的作用:
基本类型:比较的就是值是否相同
引用类型:比较的就是地址值是否相同
equals 的作用:
引用类型:默认情况下,比较的是地址值。
特殊情况:String、Integer、Date这些类库中equals被重写,比较的是内容而不是地址!
面试题:请解释字符串比较之中 “ == ” 和 equals() 的区别?
答: ==:比较的是两个字符串内存地址(堆内存)的数值是否相等,属于数值比较; equals():比较的是两个字符串的内容,属于内容比较。
1.6:String 、StringBuffer、StringBuilder三者之间的区别(必会)
String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)
String 中的String 类中使用 final 关键字修饰字符数组来保存字符串,private final char value[] ,String对象是不可变的,也就可以理解为常量,线程安全。
AbstractStringBuilder是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。
StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
小结:
(1)如果要操作少量的数据用 String;
(2)多线程操作字符串缓冲区下操作大量数据用 StringBuffer;
(3)单线程操作字符串缓冲区下操作大量数据用 StringBuilder。
1.7:接口和抽象类的区别
实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
构造函数:抽象类可以有构造函数*;接口不能有。
main 方法:抽象类可以有 main 方法,并且我们能运行它;接口不能有 main 方法。
实现数量:类可以实现很多个接口;但是只能继承一个抽象类。
访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符.
1.8:String常用的方法都有哪些(了解)
indexOf():返回指定字符的索引。
charAt():返回指定索引处的字符。
replace():字符串替换。
trim():去除字符串两端空白。
split():分割字符串,返回一个分割后的字符串数组。
getBytes():返回字符串的 byte 类型数组。
length():返回字符串长度。
toLowerCase():将字符串转成小写字母。
toUpperCase():将字符串转成大写字符。
substring():截取字符串。
equals():字符串比较。
compareTo():比较两个字符串的字典
hashCode:返回此字符串的哈希代码
toCharArray():将此字符串转换为一个新的字符数组
toString():这个对象(这已经是一个字符串!)是自己回来了。
trim():返回一个字符串,去掉前后空格。
lastIndexOf(();返回在指定字符的最后一个发生的字符串内的索引
1.9:反射(必会)
在 Java 中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的 属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息 以及动态调用对象方法的功能成为 Java 语言的反射机制。通俗点说就是解耦合。
1):获取 Class 对象的 3 种方法 :
1.1):调用某个对象的 getClass()方法
Person p=new Person();
Class clazz=p.getClass();
1.2):调用某个类的 class 属性来获取该类对应的 Class 对象
Person p=new Person();
Class clazz=p.class();
1.3):使用 Class 类中的 forName()静态方法(最安全/性能最好)
Class clazz=Class.forName(“类的全路径”); (最常用)
2):获取 Class 对象的构造方法 :->如果没有权限,可以使用SetAccessible
2.1):批量获取
获取这个类中的所有“构造方法”,包括私有、受保护的。
public Constructor>[] getDeclaredConstructors() 获取这个类中的所有“**公有**构造方法” public Constructor>[] getConstructors()
2.2):单个获取
获取这个类中的某个“构造方法”,包括私有、受保护的。
public Constructor>[] getDeclaredConstructor() 获取这个类中的某个“共有构造方法” public Constructor>[] getConstructor()
3):获取 Class 对象的成员属性并赋值和取值 :->如果没有权限,可以使用SetAccessible
3.1):批量获取
获取这个类中所有的公有成员属性
public Field[] getFields()
获取这个类中所有的成员属性
public Field[] getDeclaredFields()
3.2):单个获取
获取这个类中某个公有成员属性
public Field getField(String name)
获取这个类中的某个成员属性
public Field getDeclaredField(String name)
设置属性的值:获取到成员属性的返回值.set(Object target,Object value).
获取属性的值:获取到成员属性的返回值.get(Object target,Object value).
4):获取 Class 对象的成员方法 :->如果没有权限,可以使用SetAccessible
4.1):批量获取
获取这个类中所有的公有成员方法
public 方法[] getMethods()
获取这个类中所有的成员方法
public 方法[] getDeclaredMethods()
4.2):单个获取
获取这个类中某个公有成员方法
public 方法 getMethod(String name,
获取这个类中的某个成员方法
public 方法 getDeclaredMethod(String name)
调用方法:获取成员方法后返回值.inuoke(Object targetObj,Object…实参列);
1.10:什么是单例模式,有哪几种。
单例模式:某个类的实例在多线程的环境下只会被创建一次出来
单例模式有饿汉式单例模式、懒汉式模式、双检锁模式三种
1.11:jdk1.8的新特性
1:Lambda表达式
Lambda允许把函数作为一个方法的参数。

2:方法引用
方法引用允许直接引用已有的java类或对象的方法或构造方法。

3:函数式接口
有且仅有一个抽象方法的接口叫做函数式接口,函数式接口可以被隐式转换为 Lambda 表达式。
通常函数式接口上会添@FunctionalInterface 注解。
接口允许定义默认方法和静态方法
从 JDK8 开始,允许接口中存在一个或多个默认非抽象方法和静态方法。
4:Strieam API
新添加的 Stream API(java.util.stream)把真正的函数式编程风格引入到 Java 中。这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选,排序,聚合等。
常用方法有
1):引用类型数组流 Stream.of();
2):List集合中:
遍历:forEach()终结方法,没有返回值
遍历:过滤 filter() 拼接方法,返回Stream流
取出前n个元素 Iimit(n) 返回一个新流对象
跳过前n个元素 skip(n)返回一个新流对象
转换泛型 map() 返回一个新流对象
合并流 concat(Stream s1.Stream s2)
5:日期/时间类改进
之前的 JDK 自带的日期处理类非常不方便,我们处理的时候经常是使用的第三方工具包,比如 commons-lang包等。不过 JDK8 出现之后这个改观了很多,比如日期时间的创建、比较、调整、格式化、时间间隔等。
这些类都在 java.time 包下,LocalDate/LocalTime/LocalDateTime。
6:Optional 类
Optional 类是一个可以为 null 的容器对象。如果值存在则 isPresent()方法会返回 true,调用 get()方法会返回该对象。
1.12:java的异常(了解)

Throwable是所有Java程序中错误处理的父类,有两种资类:Error和Exception。
Error:表示由JVM所侦测到的无法预期的错误,由于这是属于JVM层次的严重错误,导致JVM无法继续执行,因此,这是不可捕捉到的,无法采取任何恢复的操作,顶多只能显示错误信息。
Exception:表示可恢复的例外,这是可捕捉到的。
1.非运行时异常 (编译异常):是RuntimeException以外的异常** 类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
2.运行时异常:都是RuntimeException类及其子类异常
NullPointerException(空指针异常)
IndexOutOfBoundsException(下标越界异常)。
这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
常见的RunTime异常几种如下:
NullPointerException - 空指针引用异常
ClassCastException - 类型强制转换异常。
IllegalArgumentException - 传递非法参数异常。
ArithmeticException - 算术运算异常
ArrayStoreException - 向数组中存放与声明类型不兼容对象异常
IndexOutOfBoundsException - 下标越界异常
1.13:BIO、NIO、AIO 有什么区别?
BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
1.14:Threadloal的原理(高薪常问)
ThreadLocal: 为共享变量在每个线程中创建一个副本,每个线程都可以访问自己内部的副本变量。通过threadlocal保证线程的安全性。
ThreadLocal :类中有一个静态内部类ThreadLocalMap(其类似于Map),用键值对的形式存储每一个线程的变量副本,ThreadLocalMap中元素的key为当前ThreadLocal对象,而value对应线程的变量副本。
1.15:同步锁、死锁、乐观锁、悲观锁(高薪常问)
同步锁:当多个线程同时访问同一个数据时,很容易出现问题。为了避免这种情况出现,我们要保证线程同步互斥,就是指并发执行的多个线程,在同一时间内只允许一个线程访问共享数据。Java 中可以使用 synchronized 关键字来取得一个对象的同步锁。
死锁:多个线程同时被阻塞,他们中的一个或者全部都在等待某个资源被释放。
乐观锁: 总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。(在这里可以和面试官延伸到CAS机制)
悲观锁: 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。(这里就可以和面试官延伸到synchronized的实现原理、和Lock的区别,和volatile的区别。或者直接点就是多线程)
1.16:说一下 synchronized 底层实现原理?
synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。
java中每一个对象都可以作为锁,这是synchronized实现同步的基础。
普通同步方法,锁是当前实例对象。
静态同步方法,锁是当前类的class对象
同步方法块,锁是括号里面的对象
1.17:synchronized 和 volatile 的区别是什么?
volatile本质是在告诉JVM当前变量是在寄存器(工作内存)中的值是不确定的,需要从主内存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞。
synchronized通过加锁的方式,使得其在需要原子性、可见性和有序性三个特性的时候都可以作为其中一种解决方案,看起来是万能的。volatile通过在volatile变量的操作前后插入内存屏障的方式,保证了变量在并发场景下的可见性和有序性。
volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
1.18:synchronized 和 Lock 有什么区别?
首先,synchronized是java中的关键字,而Lock是一个类
synchronized无法判断是否获取锁的状态,但是Lock可以通过lock()和unlock()方法来判断锁的状态。
synchronized会自动释放锁(执行完相应的代码块或者出现异常就会释放锁),Lock需在finally中手工释放锁(unock()方法)。否则就会容易造成线程死锁。
用synchronized关键字的俩个线程。线程1和线程2,如果线程1获得锁,线程2等待,,如果线程一堵塞,线程2就会一直等待下去。而这个Lock锁就不一定会等待下去,如果尝试获取不到锁。线程可以不用一直等待就结束了。
synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
synchronized锁适合代码少量的同步问题,而Lock锁适合大量同步的代码的同步问题。
1.19:手写冒泡排序!(必会)


1.20:final、finally、finalize的区别
final:最终的意识,可以修饰类,成员变量,成员方法。
修饰类,类不能被继承
修饰变量,变量是常量
修饰方法,方法不能被重写。
finally:是异常处理的一部分,用来释放资源。一般来说,代码肯定会执行。特殊情况:在执行到finally之前JVM退出了
finalize:是Object类的一个方法,用于垃圾回收。
1.21:如果catch语句里面有return,请问finally里面的代码还会执行嘛。如果会,请问是return前,还是return后。
会执行,在return前。
2.1:常见的数组结构
常见的数组结构有:数组、栈、队列、链表、树、散列、堆、图等

数组是最常见的数据结构,数组的特点是长度固定,数组的大小固定后就没有办法扩容,数组只能存储一种类型的数据。添加删除的操作慢,因为要移动其他的元素
栈是一种基于先进后出的数据结构,是一种只能在一段进行插入和删除操作的特殊线性表。它按照先进后出的原值存入数据,先进入的数据被压在栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个弹出来)
队列是一种基于先进先出的数据结构,是一种只能在一端进行插入,在另一端进行删除操作的特殊性线表,它按照先进先出的原则存储数据,先进的数据,在读取数据时先被读取出来,
链表是一种物理存储单元上非连续,非顺序的存储结构,其物理结构不能只表示数据元素的逻辑顺序,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表是有一系列的节点(链表中的每一个元素称为节点)组成,节点可以在运行时动态生成,根据指针的指向。链表能形成不同的结构,例如单链表、双向链表、循环列表等
树是我们计算机中非常重要的一种数据结构,同时使用树这种数据结构,可以描述现实生活中的很多事物,例如家谱、单位的组织架构等。有二叉树、平衡树、红黑树、B树、B+树
散列表,也叫哈利表,是根据关键码和值直接进行访问的数据结构,通过key和value来映射到集合中的一个位置,这样就可以很快找到集合中的对应元素。
堆是计算机学科中一类特殊的数据结构的同城,堆通常可以被看作是一颗完全二叉树的数组对象。
图是由一组顶点和一组能够将俩个顶点相连的边组成的。
2.2:集合和数组的区别
区别:数组长度固定,集合长度可变
数组中存储的是同一种数据类型的元素,可以存储基本数据类型,也可以存储引用数据类型
集合村粗的都是对象,而且对象的数据类型可以不一致,在开发中一般当对象比较多的时候,使用集合来存储对象。
2.3:java中集合的体系

Connection接口:
1):List:有序,可重复
1.1)ArrayList
优点:底层数据结构是数组。查询快,增删慢。
缺点:线程不安全,效率高
1.2):Vector
优点:底层数据结构是数组查询快,增删慢。
缺点:线程安全,效率低(已经舍弃了)。
1.3):LinkedList
优点:底层数据结构是链表,查询慢,增删快。
缺点:县城不安全,效率高
2):Set 无序,唯一
2.1):HashSet
底层数据结构是哈希表。(无序,唯一)
如何来保证元素的唯一性?
依赖俩个方法 hashCode()和equals()
2.2):LinkedHashSet
底层数据结构是链表和哈希表。
由链表保证元素有序
由哈希表保证元素唯一
2.3):TreeSet
底层数据结构是红黑树。(唯一,有序)
1):如何保证元素排序的呢?
自然排序
比较器排序
2):如何保证元素的唯一性的呢?
根据比较的返回值是否是0来决定
Map接口:
1):HashMap
基于hash表的Map接口实现,非线程安全、高效、支持null值和null键,线程不安全
2):HashTable
线程安全、低效、不支持null键和null值
3):LinkedHashMap
线程不安全,是HashMap的一个子类,保存了已记录的插入顺序。
4):TreeMap
能够把它保存的记录根据键排序,默认是键值的升序排序,线程不安全
2.4:List和Map、Set的区别
List和set是存储单列数据的集合,Map是存储键值对这样的双列数据的集合;
List中存储的数据是有顺序的,并且值允许重复
Map中存储的数据是无序的,它的键是不允许重复的,但是值是允许重复的;
Set中存储的数据是无顺序的,并且并不允许重复,但元素在集合中的位置是由元素的hashcode决定,即位置是固定的(set集合是根据hashcode来进行数据存储的,所以位置是固定的,但是这个位置不是用户可以控制的,所以对于用户来说set中的元素还是无序的)。
2.5:HashMap的底层原理
HashMap在JDK1.8以前的实现方式是数组+链表。
但是在JDK1.8后对HashMap进行可底层优化,改为了由数组+链表+红黑树实现,只要的目的是提高查找效率


1. Jdk8数组+链表或者数组+红黑树实现,当链表中的元素超过了 8 个以后, 会将链表转换为红黑树,当红黑树节点 小于 等于6 时又会退化为链表。
2. 当new HashMap():底层没有创建数组,首次调用put()方法示时,底层创建长度为16的数组,jdk8底层的数组是:Node[],而非Entry[],用数组容量大小乘以加载因子得到一个值,一旦数组中存储的元素个数超过该值就会调用rehash方法将数组容量增加到原来的两倍,专业术语叫做扩容,在做扩容的时候会生成一个新的数组,原来的所有数据需要重新计算哈希码值重新分配到新的数组,所以扩容的操作非常消耗性能.
默认的负载因子大小为0.75,数组大小为16。也就是说,默认情况下,那么当HashMap中元素个数超过160.75=12的时候,就把数组的大小扩展为216=32,即扩大一倍。
在我们Java中任何对象都有hashcode,hash算法就是通过hashcode与自己进行向右位移16的异或运算。这样做是为了计算出来的hash值足够随机,足够分散,还有产生的数组下标足够随机,
map.put(k,v)实现原理
(1)首先将k,v封装到Node对象当中(节点)。
(2)先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
(3)下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal()。如果所有的equals()方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。
map.get(k)实现原理
(1)先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
(2)在通过数组下标快速定位到某个位置上。重点理解如果这个位置上什么都没有,则返回null。如果这个位置上有单向链表,那么它就会拿着参数K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。
2.6:HashMap和HashTable、ConcurrentHashMap区别
区别对比一(HashMap和HashTable区别)
1):HashMap是非线程安全的,HashTable是线程安全的。
2):HashMap的键和值都允许有null值存在,而Table不行。
3):因为线程安全的问题,HashMap效率比HashTable的要高。
4):HashTable是同步的,而HashMap不是,因此HashMap适合于单线程环境,而HashTable适合多线程环境。
5):一般不建议使用HashTable,因为是遗留类,内部实现很多没优化,即使在多线程环境下,现在也有同步的ConcurrentHashMap替代。没有必要因为是多线程而使用HashTable。
区别对比二(HashTable和CincurrenHashMap区别):
1):HashTable 使用的是 Synchronized 关键字修饰,ConcurrentHashMap 是JDK1.7使用了锁分段技术来保证线程安全的。JDK1.8ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。
2):synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。
3.1:什么事线程?线程和进程的区别
线程:是进程的一个实体,是cpu调度和分配的基本单位,是比进程更少的可以独立运行的基本单位
进程:具有一定独立功能的程序关于某个数据集合上的异常运行活动,是操作系统进行资源分配和调度的一个独立单位。
特点:线程的划分尺度小于进程,这使多线程程序拥有高并发性,进程在运行时各自内存单元相互独立,线程之间,内存共享,这使多线程编程可以拥有更好的性能和用户体验。
3.2:创造线程有几种方式
1):继承Thread类并重写run方法,实现简单但是不可以继承其他类
2):实现Runnable接口并重写run方法,避免了单继承局限性,编程更加灵活,实现解耦。
3):实现Callable接口并重写Call方法,创建线程,可以获取线程执行结果的返回值,并且抛出异常
4):使用线程池创建(Executor接口)
3.3:Runnable和Callable的区别
Runnable接口run方法没有返回值,Callable接口call方法有返回值,支持泛型
Runnable接口run方法只能抛出运行时异常,且无法捕获处理;Callable接口Call方法允许抛出异常,可以获取异常信息。
3.4:如何启动一个新线程、调用start和run方法的区别?

线程对象调用run方法不开启线程,仅是对象调用方法。
线程对象调用start开启线程,并让jvm调用run方法在开启的线程中执行
调用start方法可以启动线程,并且使得线程进入就绪状态,而run方法只是thread的一个普通方法,还是在主线程中执行。
3.5:线程有哪几种状态,以及各种状态之间的转换?

1. 第一是new->新建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
2. 第二是Runnable->就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。
3. 第三是Running->运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
4. 第四是阻塞状态。阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(1)等待 – 通过调用线程的wait() 方法,让线程等待某工作的完成。
(2)超时等待 – 通过调用线程的sleep() 或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
(3)同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
5. 第五是dead->死亡状态: 线程执行完了或者因异常退出了run()方法,该线程结束生命周期.
3.6:线程相关的基本方法
线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等
1.线程等待(wait)
调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中
断才会返回,需要注意的是调用 wait()方法后,会释放对象的锁。因此,wait 方
法一般用在同步方法或同步代码块中。
2.线程睡眠(sleep)
sleep 导致当前线程休眠,与 wait 方法不同的是 sleep 不会释放当前占
有的锁,sleep(long)会导致线程进入 TIMED-WATING 状态,而 wait()方法
会导致当前线程进入 WATING 状态.
3.线程让步(yield)
yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争
CPU 时间片。一般情况下,优先级高的线程有更大的可能性成功竞争得到 CPU
时间片,但这又不是绝对的,有的操作系统对 线程优先级并不敏感。
4.线程中断(interrupt)
中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的
一个中断标识位。这个线程本身并不会因此而改变状态(如阻塞,终止等)
5.Join 等待其他线程终止
join() 方法,等待其他线程终止,在当前线程中调用一个线程的 join() 方
法,则当前线程转为阻塞状态,回到另一个线程结束,当前线程再由阻塞状态变
为就绪状态,等待 cpu 的宠幸.
6.线程唤醒(notify)
Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意的,并在对实现做出决定时发生,线程通过调用其中一个 wait() 方法,在对象的监视 器上等待,直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程,
被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。类 似的方法还有 notifyAll() ,唤醒再次监视器上等待的所有线程。
3.7:wait()和sleep()的区别
1:来自不同的类
wait来自Object类
sleep来自Thread类
2:关于锁的释放
wait在等待的过程中会释放锁
sleep在等待的过程中不会释放锁
3:使用的范围
wait必须是在同步代码块中使用
sleep可以再任何地方使用
4:是否需要捕获异常
wait不需要捕获异常;
sleep需要捕获异常
4.1:为什么需要线程池
在实际使用中,线程是很占用系统资源的,如果对线程管理不完善的话很容易导致系统问题。因此,在大多数并发框架中都会使用线程池来管理线程,使用线程池管理线程主要有如下好处:
1、使用线程池可以重复利用已有的线程继续执行任务,避免线程在创建和销毁时造成的消耗
2、由于没有线程创建和销毁时的消耗,可以提高系统响应速度
3、通过线程可以对线程进行合理的管理,根据系统的承受能力调整可运行线程数量的大小等
4.2:线程池的分类

1):newCachedThreadPool:创建一个可进行缓存重复利用的线程池
2):newFixedThreadPool:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,线程池中的线程处于一定的量,可以很好的控制线程的并发量
3):newSingleThreadExecutor:创建一个使用单个 worker 线程的Executor ,以无界队列方式来运行该线程。线程池中最多执行一个线程,之后提交的线程将会排在队列中以此执行
4):newSingleThreadScheduledExecutor:创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期执行
5):newScheduledThreadPool:创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行
6):newWorkStealingPool:创建一个带并行级别的线程池,并行级别决定了同一时刻最多有多少个线程在执行,如不传并行级别参数,将默认为当前系统的CPU个数
4.3:核心参数
corePoolSize:核心线程池的大小
maximumPoolSize:线程池能创建线程的最大个数
keepAliveTime:空闲线程存活时间
unit:时间单位,为keepAliveTime指定时间单位
workQueue:阻塞队列,用于保存任务的阻塞队列
threadFactory:创建线程的工程类
handler:饱和策略(拒绝策略)
4.4:线程池的原理

线程池的工作过程如下:
当一个任务提交至线程池之后,
4.5:拒绝策略
ThreadPoolExecutor.AbortPolicy(系统默认): 丢弃任务并抛出RejectedExecutionException异常,让你感知到任务被拒绝了,我们可以根据业务逻辑选择重试或者放弃提交等策略
ThreadPoolExecutor.DiscardPolicy: 也是丢弃任务,但是不抛出异常,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。
ThreadPoolExecutor.DiscardOldestPolicy: 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程),通常是存活时间最长的任务,它也存在一定的数据丢失风险
ThreadPoolExecutor.CallerRunsPolicy:既不抛弃任务也不抛出异常,而是将某些任务回退到调用者,让调用者去执行它。
4.6:线程池的关闭
关闭线程池,可以通过shutdown和shutdownNow两个方法
原理:遍历线程池中的所有线程,然后依次中断
1、shutdownNow首先将线程池的状态设置为STOP,然后尝试停止所有的正在执行和未执行任务的线程,并返回等待执行任务的列表;
2、shutdown只是将线程池的状态设置为SHUTDOWN状态,然后中断所有没有正在执行任务的线程
虚拟机,一种能够运行java字节码的虚拟机。
5.1:JDK1.8 JVM运行时内存

程序计数器:
线程私有的(每个线程都有一个自己的程序计数器), 是一个指针. 代码运行, 执行命令. 而每个命令都是有行号的,会使用程序计数器来记录命令执行到多少行了.
Java虚拟机栈:
线程私有的(每个线程都有一个自己的Java虚拟机栈). 一个方法运行, 就会给这个方法创建一个栈帧, 栈帧入栈执行代码, 执行完毕之后出栈(弹栈)
本地方法栈:
线程私有的(每个线程都有一个自己的本地方法栈), 和Java虚拟机栈类似, Java虚拟机栈加载的是普通方法,本地方法加载的是native修饰的方法.
native:在java中有用native修饰的,表示这个方法不是java原生的.
堆:
线程共享的(所有的线程共享一份). 存放对象的,new的对象都存储在这个区域.还有就是常量池.
元空间: 存储.class 信息, 类的信息,方法的定义,静态变量等.而常量池放到堆里存储
JDK1.8和JDK1.7的jvm内存最大的区别是, 在1.8中方法区是由元空间(元数据区)来实现的, 常量池.
1.8不存在方法区,将方法区的实现给去掉了.而是在本地内存中,新加入元数据区(元空间).
5.2:JDK1.8堆内存结构

Young 年轻区(代): Eden+S0+S1, S0和S1大小相等, 新创建的对象都在年轻代
Tenured 年老区: 经过年轻代多次垃圾回收存活下来的对象存在年老代中.
Jdk1.7和Jdk1.8的区别在于, 1.8将永久代中的对象放到了元数据区, 不存永久代这一区域了.
5.3:Gc垃圾回收**
JVM的垃圾回收动作可以大致分为两大步,首先是「如何发现垃圾」,然后是「如何回收垃圾」。说明一点, 线程私有的不存在垃圾回收, 只有线程共享的才会存在垃圾回收, 所以堆中存在垃圾回收.
5.3.1 如何发现垃圾
Java语言规范并没有明确的说明JVM使用哪种垃圾回收算法,但是常见的用于「发现垃圾」的算法有两种,引用计数算法和根搜索算法。
1.引用计数算法
该算法很古老(了解即可)。核心思想是,堆中的对象每被引用一次,则计数器加1,每减少一个引用就减1,当对象的引用计数器为0时可以被当作垃圾收集。
优点:快。
缺点:无法检测出循环引用。如两个对象互相引用时,他们的引用计数永远不可能为0。
2.根搜索算法(也叫可达性分析)
根搜索算法是把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即可以当作垃圾。
Java中可作为GC Root的对象有
1.虚拟机栈中引用的对象
2.本地方法栈引用的对象
2.方法区中静态属性的引用对象
3.方法区中常量的引用对象
5.3.2 如何回收垃圾
Java中用于「回收垃圾」的常见算法有4种:
1.标记-清除算法(mark and sweep)
分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收掉所有被标记的对象。
缺点:首先,效率问题,标记和清除效率都不高。其次,标记清除之后会产生大量的不连续的内存碎片。
2.标记-整理算法
是在标记-清除算法基础上做了改进,标记阶段是相同的,但标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。
优点:内存被整理后不会产生大量不连续内存碎片。
3.复制算法(copying)
将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉。
缺点:可使用的内存只有原来一半。
4.分代收集算法(generation)
当前主流JVM都采用分代收集(Generational Collection)算法, 这种算法会根据对象存活周期的不同将内存划分为年轻代、年老代、永久代,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。

(1)年轻代(Young Generation)
1.所有新生成的对象首先都是放在年轻代的。
2.新生代内存按照8:1:1的比例分为一个eden区和两个Survivor(survivor0,survivor1)区。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。
3.当survivor1区不足以存放eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。
4.新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)
(2)年老代(Old Generation)
1.在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
2.内存比新生代也大很多(大概是2倍),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率比较高。
(3)持久代(Permanent Generation)
用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,从JDK8以后已经废弃, 将存放静态文件,如Java类、方法等这些存储到了元数据区.
5.4:JVM调优参数
这里只给出一些常见的性能调优的参数及其代表的含义。(大家记住5.6个就行, 并不需要都记住.)

-Xmx3550m:设置JVM最大可用内存为3550M。
-Xms3550m:设置JVM初始内存为3550m。注意:此值一般设置成和-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss256k:设置每个线程的栈大小。JDK5.0以后每个线程栈大小为1M,以前每个线程栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。

-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4。(该值默认为2)
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4。
这里是最基本的java基础面试题。下一章Web面试题
面向对象性:两个基本概念:类、对象;三大特性:封装、继承、多态
健壮性:吸收了C/C++语言的优点,但去掉了其影响程序健壮性的部分(如指针、内存的申请与释放等),提供了一个相对安全的内存管理和访问机制
跨平台性:通过Java语言编写的应用程序在不同的系统平台上都可以运行。“Write once , Run Anywhere”
System.out.println();打印完后,会换行。
System.out.print();打印完后,不会换行。
答:可以。但最多只有一个类名声明为public,与文件名相同。
class Something {
public static void main(String[] something_to_do) {
System.out.println("Do something ...");
}
}
答案: 正确。从来没有人说过Java的class名字必须和其文件名相同。但public class的名字必须和文件名相同。
目的是为了在控制台的任何文件路径下,都可以调用jdk指定目录下的所有指令。
JDK包含JRE,JRE包含JVM.
GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,
忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。
对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用
有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。
当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。可以。程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。
是能够定义成为一个中文的,因为java中以unicode编码,一个char占16位, 所以放一个中文是没问题的
不正确。精度不准确,应该用强制类型转换,如下所示:float f=(float)3.4
基本数据类型包括byte、int、char、long、float、double、boolean和short。
java.lang.String是java中定义的一个类,类都属于引用数据类型。
java中的保留字,现在没有在java中使用
2<<3
能,因为Java一个字符是2个字节,每一个字符使用Unicode编码表示
不正确,float f = 3.4F;
14
基本数据类型有:byte,short,int,long,float,double,char,boolean
String是引用数据类型,不是基本数据类型
public static void main(String[] args) {
char x = 'x';
int i = 10;
System.out.println(true? x : i);
System.out.println(true? 'x' : 10);
}
120
x
如果其中有一个是变量,按照自动类型转换规则处理成一致的类型;
如果都是常量,如果一个是char,如果另一个是[0~65535]之间的整数按char处理;
如果一个是char,另一个是其他,按照自动类型转换规则处理成一致的类型;
public static void main(String[] args) {
int a = 8, b = 3;
System.out.println(a>>>b);
System.out.println(a>>>b | 2);// |是按位或的意思,1|2=3
}
1
3
class SuanShu{
public static void main(String[] args){
int a = 23;
int b = 12;
System.out.println(a + "+" + b + "=" + (a+b));
int sum = a + b;
System.out.println(a + "+" + b + "=" + sum);
int sub = a - b;
//System.out.println(a + "-" + b + "=" + a-b);//错误,原因是a + "-" + b + "=" + a的结果是字符串,字符串不能进行减法
System.out.println(a + "-" + b + "=" + (a-b));
System.out.println(a + "-" + b + "=" + sub);
int mul = a * b;
System.out.println(a + "*" + b + "=" + a*b);
System.out.println(a + "*" + b + "=" + mul);
//整数相除,只保留整数部分
int div = a / b;
System.out.println(a + "/" + b + "=" + a/b);
System.out.println(a + "/" + b + "=" + div);
double d = (double)a/b;//先把a的类型进行转换,转换成double类型,然后再和b相除
System.out.println(a + "/" + b + "=" + d);
int yu = a % b;
System.out.println(a + "%" + b + "=" + yu);
System.out.println("------------------特殊的取模----------------------");
System.out.println(5%2);//1
System.out.println(-5%-2);//-1
System.out.println(-5%2);//-1
System.out.println(5%-2);//1
System.out.println("------------------负号----------------------");
int num1 = 12;
int num2 = -num1;
System.out.println("num2=" + num2);
System.out.println("------------------自增----------------------");
int i = 0;
System.out.println("自增之前i=" + i);
i++;
System.out.println("自增第一次之后i=" + i);
++i;
System.out.println("自增第二次之后i=" + i);
int j = ++i;//把i自增1,然后结果赋值给j,或者说,先算++i,然后再赋值
System.out.println("自增第三次之后i=" + i);
System.out.println("j="+j);
int k = i++;//先算赋值,把i的值赋值给k,i原来是3,把3赋值给k,然后i在自增1,i变成4
System.out.println("自增第四次之后i=" + i);
System.out.println("k="+k);
//面试题:陷阱题
i = i++;//先赋值,把i原来的值重新赋值给i,不变,然后i自增,但是这个自增后的值没有在放回变量i的位置
System.out.println("自增第五次之后i=" + i);
}
}
写出输出的结果.
class Demo{
public static void main(String[] args){
int x=0,y=1;
if(++x==y-- & x++==1||--y==0)
System.out.println("x="+x+",y="+y); //x = 2,y = 0;
else
System.out.println("y="+y+",x="+x);
}
}
答案一:
int temp = m; m = n; n = temp;
- 1
- 2
- 3
答案二:
m = m + n; n = m - n; m = m - n;
- 1
- 2
- 3
答案三:
m = m ^ n; n = m ^ n; m = m ^ n;
- 1
- 2
- 3
//方式一:自动实现
String str1 = Integer.toBinaryString(60);
String str2 = Integer.toHexString(60);
//方式二:手动实现
int i1 = 60;
int i2 = i1&15;//除16取余
String j = (i2 > 9)? (char)(i2-10 + 'A')+"" : i2+"";
int temp = i1 >>> 4;//除16取商
i2 = temp & 15;//商除16取余
String k = (i2 > 9)? (char)(i2-10 + 'A')+"" : i2+"";
System.out.println(k+""+j);
写出输出结果:
class Demo{
public static void main(String[] args){
int a=3,b=8;
int c=(a>b)?a++:b++;
System.out.println("a="+a+"\tb="+b+"\tc="+c); //
int d=(a>b)?++a:++b;
System.out.println("a="+a+"\tb="+b+"\td="+d); //
int e=(a<b)?a++:b++;
System.out.println("a="+a+"\tb="+b+"\te="+e); //
int f=(a<b)?++a:++b;
System.out.println("a="+a+"\tb="+b+"\tf="+f); //
}
}
short s1 = 1; s1 = s1 + 1;有什么错?
short s1 = 1; s1 += 1;有什么错
short s1 = 1; s1 = s1 + 1; (s1+1运算结果是int型,需要强制转换类型)
short s1 = 1; s1+= 1;(可以正确编译)
switch(expr1)中,expr1是一个整数表达式。因此传递给 switch 和 case 语句的参数应该是 int、 short、char 或者 byte。long不能作用于swtich.
JDK1.7新加入了String类型。
答案一:switch可以作用在byte上,不能作用在long上,JDK1.7之后可以作用在String上。
答案二:switch支持的类型byte,short,int,char,JDK1.5之后支持枚举,JDK1.7之后支持String类型。
可以。int i = 12; int[] myInt = new int[i];
.length属性获取
数组没有length()这个方法,有length的属性。String有length()这个方法
对基本数据类型而言,输出的往往是变量的值;
对于像数组这一类复杂的数据类型,输出的是其堆空间中存储位置的hashCode值
String[] stringArray = new String[3]; // 各元素的值默认为null
for (int i = 0; i < stringArray.length; i++) { // 对各元素进行初始化,但没有赋值。
stringArray[i] = new String();
System.out.println(stringArray[i]);
}
答案:空 (有别于null)
练习:
1、从键盘输入本组学员的成绩,放到数组中
2、用for循环显示所有学员的成绩
3、排序:从低到高
4、查找是否有正好60分的,如果有返回位置
5、复制成绩最低三名构成新数组
6、用工具类打印成绩最低三名成绩
/*
练习:
1、从键盘输入本组学员的成绩,放到数组中
2、用foreach显示所有学员的成绩
3、排序:从低到高
4、查找是否有正好60分的,如果有返回位置
5、复制成绩最低三名构成新数组
6、用工具类打印成绩最低三名成绩
*/
import java.util.Scanner;
import java.util.Arrays;
class TestArraysExer{
public static void main(String[] args){
//1、声明一个数组并创建一个数组
int[] scores = new int[5];
//2、从键盘输入成绩
Scanner input = new Scanner(System.in);
for(int i=0; i<scores.length; i++){
//成绩存在数组的元素中
//为元素赋值
System.out.print("请输入第" + (i+1) + "个学员的成绩:");
scores[i] = input.nextInt();
}
//3、显示成绩
//用foreach显示所有学员的成绩
System.out.println("本组学员的成绩如下:");
for(int s = 0; s < scores.length;i++){
System.out.println(scores[s]);
}
//4、排序:从低到高
Arrays.sort(scores);
System.out.println("排序后的结果:" + Arrays.toString(scores));
//5、查找60分
int index = Arrays.binarySearch(scores, 60);
if(index<0){
System.out.println("没有正好60分的");
}else{
System.out.println("60分的索引位置:" + index);
}
//6、复制成绩最低三名构成新数组
//int[] newArray = Arrays.copyOfRange(scores, 0, 3);
int[] newArray = Arrays.copyOf(scores, 3);
//7、用工具类打印成绩最低三名成绩
System.out.println("成绩最低的三名同学是:" + Arrays.toString(newArray));
}
}
答:面向对象有三大特点:封装、继承、多态。(如果要回答四个,可加上 抽象性 这一特点)
1.继承性: 继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。
2.封装性: 封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。
3. 多态性: 多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。
4.抽象性: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。
分配:由JVM自动为其分配相应的内存空间
释放:由JVM提供垃圾回收机制自动的释放内存空间
垃圾回收机制(GC:GarbageCollection):将垃圾对象所占用的堆内存进行回收。Java的垃圾回收机制是JVM提供的能力,由单独的系统级垃圾回收线程在空闲时间以不定时的方式动态回收。
垃圾对象:不再被任何引用指向的对象。
能,通过调用System.gc();或Runtime.getRuntime().gc();
不是,该调用并不会立刻启动垃圾回收机制开始回收,但会加快垃圾回收机制的运行。
public class TestGC{
public static void main(String[] args)throws Exception{
for(int i=0; i<10; i++){
MyClass m = new MyClass();//这里本次循环完,本次创建的对象就成为垃圾了
System.out.println("创建第" + (i+1) + "的对象:" + m);
}
//通知垃圾回收机制来收集垃圾
System.gc();
//为了延缓程序结束
for(int i=0; i<10; i++){
Thread.sleep(1);
System.out.println("程序在继续....");
}
}
}
class MyClass{
//这个方法是垃圾回收机制在回收它的对象时,自动调用,理解成对象留临终遗言的方法
public void finalize(){
System.out.println("轻轻的我走了.....");
}
}
构造器Constructor不能被继承,因此不能重写Override,但可以被重载Overload
是值传递。Java编程语言只有值传递参数。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的内容可以在被调用的方法中改变,但对象的引用是永远不会改变的。
java没有多继承,但可以通过接口的形式来达到多继承的目地。
定义类A 和类B 如下:
class A {
int a = 1;
double d = 2.0;
void show() {
System.out.println("Class A: a=" + a + "\td=" + d);
}
}
class B extends A {
float a = 3.0f;
String d = "Java program.";
void show() {
super.show();
System.out.println("Class B: a=" + a + "\td=" + d);
}
}
(1) 若在应用程序的main 方法中有以下语句: A a=new A(); a.show(); 则输出的结果如何?
(2)若在应用程序的main 方法中定义类B 的对象b: A b=new B(); b.show(); 则输出的结果如何?
答:输出结果为:
1)Class A: a=1 d=2.0
2)Class A: a=1 d=2.0 Class B: a=3.0 d=Java program。
重载(overload)和重写(overried,有的书也叫做“覆盖”)的区别?
方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写(Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了。如果在一个类中定义了多个同名的方法,它们或有
不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。
Overloaded的方法是可以改变返回值的类型
class Demo{
int show(int a,int b){return 0;}
}
下面那些函数可以存在于Demo的子类中。
A.public int show(int a,int b){return 0;}//可以,覆盖。
B.private int show(int a,int b){return 0;}//不可以,权限不够。
C.private int show(int a,long b){return 0;}//可以,和父类不是一个函数。没有覆盖,相当于重载。
D.public short show(int a,int b){return 0;}//不可以,因为该函数不可以和给定函数出现在同一类中,或者子父类中。
E.static int show(int a,int b){return 0;}//不可以,静态只能覆盖静态。
class Super {
public int get() {
return 4;
}
}
class Demo15 extends Super {
public long get() {
return 5;
}
public static void main(String[] args) {
Super s = new Demo15();
System.out.println(s.get());
}
}
编译失败,因为子类父类中的get方法没有覆盖。但是子类调用时候不能明确返回的值是什么类型。
所以这样的函数不可以存在子父类中。
Java 提供两种不同的类型:引用类型和原始类型(或内置类型)。
int是java的原始数据类型,Integer是java为int提供的封装类。Java为每个原始类型提供了封装类。
原始类型封装类型及其对应的包装类:boolean Boolean,char Character,byte Byte,short Short,int Integer,long Long,float Float,double Double
引用类型和原始类型的行为完全不同,并且它们具有不同的语义。
引用类型和原始类型具有不同的特征和用法,它们包括:大小和速度问题,这种类型以哪种类型的数据结构存储,当引用类型和原始类型用作某个类的实例数据时所指定的缺省值。对象引用实例变量的缺省值为
null,而原始类型实例变量的缺省值与它们的类型有关。