赞
踩
8.25 快手一面:
1、自我介绍
2、项目中人脸检测算法是否了解
3、hashMap怎么实现?用了什么结构?
4、编程:非递归实现二叉树后序遍历;递归实现n的全排列
8.29 美团电面一面:
1、java有哪些新建对象的方法
2、clone是浅拷贝还是深拷贝?两种拷贝有什么区别?
3、equals重写为什么需要重写hashcode函数?如果不改写会怎么样?
4、数组和链表区别?
5、hashMap怎么实现?用了什么结构?怎么扩容?负载因子是什么
6、hashMap和hashTable区别?多线程情况下使用hashMap,应该做什么改变?concurrentHashMap和hashTable区别?哪个性能好
7、二分查找,数组,链表,哈希表查找元素的时间复杂度
8、synchronized能修饰什么?
9、线程池有哪几种?分别的应用情况
10、younggc是什么?(oldgc?)
11、垃圾回收机制?
百度:
1、抽象类和接口的区别
2、== 和 equals()的区别
3、string buffer和string builder的区别
4、spring框架,springboot微服务;
5、静态类的特征、优点;
6、java string对象 “+”和append链接两个字符串之间的差异;
7、Java中的8种基本类型是什么? (*)
8、HashMap和Hashtable有什么区别?(***)
9、Override 和 Overload 的区别(**)
10、什么是强引用,软引用,弱引用,虚引用?(***)
11、递归的删除一个文件夹(**)
快手:
1、快排,动规,线程池,垃圾回收机制,spring,和mysql
2、
Integer a=17;
Integer b=17;
Integer c=1000;
Integer d=1000;
System.out.println(a==b);
System.out.println(c==d);
为什么输出是true & false
-128~127是true,超出就是false。
一、java基础
1、Java中的8种基本类型是什么?(百度)
整型:byte(8) short(16) int(32) long(64)
浮点型:float(32) double(64)
逻辑型:boolean (1)
字符型:char(16)
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;i<list.length;i++){
//递归删除目录下的文件
delete(path+"//"+list[i]);
}
}
//最后删除最外的空文件夹
f.delete();
}
GC垃圾收集器
15在java语言中,可作为GC Roots的对象包括下面几种:(可达性分析算法)
虚拟机栈(栈帧中的本地变量表)中引用的对象;
方法区中类静态属性引用的对象;
方法区中常量引用的对象;
本地方法栈中JNI(即一般说的native方法)引用的对象。
16、什么是强引用,软引用,弱引用,虚引用?(百度)
⑴强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 ps:强引用其实也就是我们平时A = new A()这个意思。
⑵软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存(网页缓存,图片缓存)。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
SoftReference<String> sr =
new
SoftReference<String>(
new
String(
"hello"));
⑶弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
WeakReference<String> sr = new
WeakReference<String>(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→
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、设计模式
单例模式 工厂模式 策略模式 适配器模式 生产者消费者模式 观察者模式
clone方法执行的是浅拷贝。
浅拷贝:我们这里说的浅拷贝是指我们拷贝出来的对象内部的引用类型变量和原来对象内部引用类型变量是同一引用(指向同一对象)。但是我们拷贝出来的对象和新对象不是同一对象。
简单来说,新(拷贝产生)、旧(元对象)对象不同,但是内部如果有引用类型的变量(比如string),新、旧对象引用的都是同一引用。
深拷贝:全部拷贝原对象的内容,包括内存的引用类型也进行拷贝
数组在内存中连续,链表不连续;
数组静态分配内存(固定大小),链表动态分配内存(不指定大小);
数组元素在栈区(访问速度快,自由度小),链表元素在堆区(速度慢,自由度大);
数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n);
数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1)。
数组的特点是:寻址容易,插入和删除困难。
链表的特点是:寻址困难,插入和删除容易。
综合以上,对于快速访问数据,不经常有添加删除操作的时候选择数组实现,而对于经常添加删除数据,对于访问没有很高要求的时候选择链表。
实现原理:拉链法,我们可以理解为“链表的数组” ,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会被新的值覆盖。
首先equals与hashcode间的关系是这样的:
A、如果两个对象相同(即用equals比较返回true),那么它们的hashCode值一定要相同;
B、如果两个对象的hashCode相同,它们并不一定相同(即用equals比较返回false) 。但是同一个类的不同对象,当两者拥有相同hashcode的时候,则一定相等。
假设两个对象,重写了其equals方法,其相等条件是属性相等,就返回true。如果不重写hashcode方法,其返回的依然是两个对象的内存地址值,必然不相等。这就出现了equals方法相等,但是hashcode不相等的情况。这不符合hashcode的规则。
HashMap的key==null时,将元素添加到Entry[0]链表的表头;Key!=null但value==null
在对应位置插入null值。
三种解决方案:
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) |
public int synMethod(int a1){
synchronized(a1) {
//一次只能有一个线程进入
}
}
使用synchronized(obj)进行同步操作,只要别的线程不访问这个同步代码块就不会堵塞!也就是说可以访问别的同步代码块。
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) {
}
对整个class进行加锁,整个类一次只能由一个线程访问!对于含有静态方法和静态变量的代码块的同步,我们通常用synchronized(*.class)来加锁.
//方法体
}
对整个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<Runnable>() 无解阻塞队列
通俗:创建可容纳固定数量线程的池子,每个线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
适用:执行长期的任务,性能好很多
C. newSingleThreadExecutor: 创建一个单线程线程池
底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列
通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
适用:一个任务一个任务执行的场景
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、标记清除算法(产生内存碎片)
标记-清除算法分为标记和清除两个阶段。该算法首先从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象并进行回收,如下图所示。
/*******************************************/
效率问题:标记和清除两个过程的效率都不高;
空间问题:产生太多不连续的内存碎片
/*******************************************/
2、复制算法——新生代
复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
这种算法适用于对象存活率低的场景,比如新生代。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。该算法示意图如下所示:
事实上,现在商用的虚拟机都采用这种算法来回收新生代。因为研究发现,新生代中的对象每次回收都基本上只有10%左右的对象存活,所以需要复制的对象很少,效率还不错。
3、标记整理算法(不会产生内存碎片)——老年代
复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。标记整理算法的标记过程类似标记清除算法,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,类似于磁盘整理的过程,该垃圾回收算法适用于对象存活率高的场景(老年代),其作用原理如下图所示。
标记整理算法与标记清除算法最显著的区别是:标记清除算法不进行对象的移动,并且仅对不存活的对象进行处理;而标记整理算法会将所有的存活对象移动到一端,并对不存活对象进行处理,因此其不会产生内存碎片。标记整理算法的作用示意图如下:
4、分代收集算法(结合复制算法和标记清楚算法/标记整理算法)
基于这样一个事实:不同的对象的生命周期(存活情况)是不一样的,而不同生命周期的对象位于堆中不同的区域,因此对堆内存不同区域采用不同的策略进行回收可以提高 JVM 的执行效率。
商用虚拟机使用的都是分代收集算法:新生代对象存活率低,就采用复制算法;老年代存活率高,就用标记清除算法或者标记整理算法。Java堆内存一般可以分为新生代、老年代和永久代三个模块,如下图所示:
GC算法总结:
七种垃圾回收器:
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()方法是在垃圾收集器删除对象之前对这个对象调用的。
1)减少创建Java实例所带来的系统开销
2)便于系统跟踪单个Java实例的生命周期、实例状态等。
1)解耦
2)支持并发
生产者与消费者是两个独立的并发体,他们之间是用缓冲区作为桥梁连接。
3)支持忙闲不均
当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。 等生产者的制造速度慢下来,消费者再慢慢处理掉。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。