简单说下什么是跨平台?
由于各种操作系统所支持的指令集不是完全一致,所以在操作系统上使用JVM来提供统一接口,屏蔽不同系统之间的差异。
Java有几种基本数据类型?
8种,分别是byte(1)、short(2)、int(4)、long(8)、float(4)、double(8)、char(2)、boolean(4)
面向对象的特征?
封装:把对象的属性和行为封装在模块中,对外提供访问方式。
抽象:数据抽象(类的属性);过程抽象(类的方法)
继承:子类继承父类,对父类进行扩展,缺点是耦合度高。
多态:方法覆盖与方法重载。
为什么要有包装类型?
让基本类型也具有对象的特征。
char(Character)、int(Integer),其他六种都是小写换大写。
装箱:把基本的数据类型转换成对应的包装类型。Integer i = Integer.valueOf(1)
拆箱:把包装类型转换为基本数据类型。int j = i.intValue()
Integer i = 1;//自动装箱,实际上在编译时会调用Integer.valueOf(1)
int j = i;//自动拆箱,实际上在编译时会调用 i.intValue()
基本数据类型和包装类的区别?
- 声明方式:包装类型需要使用new来创建对象,而基本数据类型不需要。
- 存储方式:基本数据类型存储在栈中,而包装类型将对象放在堆中,通过引用来使用。
- 初始值:基本数据类型int为0,boolean为false,而包装类型的初始值为null。
- 使用方式:基本数据类型直接赋值直接使用,而包装类型在集合中(Collection、Map)使用。
==和equals的区别?
== 用来判断两个变量之间的值是否相等,变量分为基本数据类型和引用类型。如果是基本数据类型的变量直接比较值,而引用类型需要比较对应的内存地址的首地址。
equals用来比较两个对象的某些特征,实际上就是调用对象的equals方法来比较,需要重写。
String、StringBuilder、StringBuffer的区别?
数据的可变性
String底层使用一个不可变的字符数组 private final char value[]; 所以它内容不可变。
StringBuffer和StringBuilder底层使用的是可变的字符数组:char[] value; 所以内容可变。
线程的安全性
StringBuffer的append()方法有同步锁,所以是线程安全的,StringBuilder没有,线程不安全。
StringBuffer和StringBuilder的相同点
StringBuilder与StringBuffer都继承自AbstractStringBuilder。
操作可变字符串速度:StringBuilder > StringBuffer > String。
讲一下Java中的集合?
Java中的集合分为Collection和Map两大类。
Collection又分为List(有序,元素允许重复)和Set(无序,元素不可重复)
Set根据equals和hashcode判断,如果一个对象要存储在Set中,必须重写equals和hashcode方法。
Map的主要实现类有:
HashMap:基于哈希表(数组+链表+红黑树JDK1.8)实现的无序集合,线程不安全。(推荐使用)
TreeMap:基于红黑树实现的有序集合(默认升序),线程不安全。
LinkedHashMap:继承自HashMap的有序集合(默认为插入顺序),线程不安全。
HashTable:使用synchronize来做线程安全,全局只有一把锁,在线程竞争激烈的情况下效率很低。
ConcurrentHashMap:使用了分段锁技术来提高了并发度,线程安全。(推荐使用)
Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
ConcurrentHashMap将整个Map分为N个segment(类似HashTable),可以提供相同的线程安全,但是效率提升N倍,默认N为16。
jdk8 放弃了分段锁而是用了Node锁,降低锁的粒度,提高性能,并使用CAS操作来确保Node的一些操作的原子性,取代了锁。
但是ConcurrentHashMap的一些操作使用了synchronized锁,而不是可重入锁,虽然说jdk8的synchronized的性能进行了优化,但是我觉得还是使用可重入锁能更多的提高性能。
ArrayList和LinkedList的区别?
ArrayList是基于动态数组实现的,查找快(索引/下标),增删慢(要移动元素,更新索引)
LinkedList是基于链表实现的,增删快(改变指针),查找慢(要移动指针)
HashMap和HashTable的区别?
1、HashTable线程同步,HashMap非线程同步。
2、HashTable不允许<键,值>有空值,HashMap允许<键,值>有空值。
3、HashTable使用Enumeration,HashMap使用Iterator。
4、HashTable中hash数组的默认大小是11,增加方式的old*2+1,HashMap中hash数组的默认大小是16,增长方式一定是2的指数倍。
HashMap可以通过下面的语句进行同步:
Map m = Collections.synchronizeMap(hashMap);
线程池的作用?
1、限制线程个数,避免线程过多导致系统运行缓慢或崩溃。
2、不需要频繁的创建和销毁,节约资源、响应更快。
线程的创建方式?
方法1:继承Thread类,作为线程对象存在。
public class CreatThreadDemo1 extends Thread{
/**
* 构造方法: 继承父类方法的Thread(String name);方法
* @param name
*/
public CreatThreadDemo1(String name){
super(name);
}
@Override
public void run() {
while (!interrupted()){// 判断该线程是否被中断
System.out.println(getName()+"线程执行了...");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
CreatThreadDemo1 d1 = new CreatThreadDemo1("first");
CreatThreadDemo1 d2 = new CreatThreadDemo1("second");
d1.start();
d2.start();
d1.interrupt(); //中断第一个线程
}
}
方法2:实现Runnable接口,作为线程任务存在。
public class CreatThreadDemo2 implements Runnable {
@Override
public void run() {
while (true){
System.out.println("线程执行了...");
}
}
public static void main(String[] args) {
//将线程任务传给线程对象
Thread thread = new Thread(new CreatThreadDemo2());
//启动线程
thread.start();
}
}
方法3:匿名内部类创建线程对象。
public class CreatThreadDemo3 extends Thread{
public static void main(String[] args) {
//创建无参线程对象
new Thread(){
@Override
public void run() {
System.out.println("线程执行了...");
}
}.start();
//创建带线程任务的线程对象
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程执行了...");
}
}).start();
//创建带线程任务并且重写run方法的线程对象
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("runnable run 线程执行了...");
}
}){
@Override
public void run() {
System.out.println("override run 线程执行了...");
}
}.start();
}
}
方法4:线程池创建线程
public class CreatThreadDemo4 {
public static void main(String[] args) {
//创建一个具有10个线程的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
long threadpoolUseTime = System.currentTimeMillis();
for (int i = 0;i<10;i++){
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程执行了...");
}
});
}
long threadpoolUseTime1 = System.currentTimeMillis();
System.out.println("多线程用时"+(threadpoolUseTime1-threadpoolUseTime));
//销毁线程池
threadPool.shutdown();
threadpoolUseTime = System.currentTimeMillis();
}
}
还有通过定时器Timer,java8的stream,创建带返回值的线程等等。
sleep()和wait()的区别?
1、sleep()是Thread类的方法;wait()是Object类的方法。
2、sleep()必须指定时间;wait()可以指定也可以不指定。
3、sleep()不释放同步锁,wait()释放同步锁。
4、sleep()可以在任意地方使用;wait()只能在同步方法或者同步代码块中使用。
Java线程池用过没有?
Executors提供了四种方法来创建线程池。
1、newFixedThreadPool():创建固定大小的线程池。
2、newCachedThreadPool():创建无限大小(可更改)的线程池。
3、newSingleThreadPool():创建单个线程池,线程池中只有一个线程。
4、newScheduledThreadPool():创建固定大小的线程池,可以延迟或定时的执行任务。
手写一个线程池?
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
threadPool.execute(() -> {
for (int i = 0; i< 20;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
});
threadPool.shutdown();
}
Math.round(-2.5)等于多少?
Math.round()是取距离最近的一个整数。注意:负数向上舍入。所以本题结果是 -2。
面向对象六大原则?
1、单一职责 SRP:让每个类只专心处理自己的方法。
2、开闭原则 OCP:软件中的对象(类,模块,函数等)应该对扩展开放,对修改关闭。
3、里式替换 LSP:子类可以去扩展父类,但是不能改变父类原有的功能。
4、依赖倒置 DIP:应该通过调用接口或抽象类(比较高层),而不是调用实现类(细节)
5、接口隔离 ISP:把接口分成满足依赖关系的最小接口,实现类中不能有不需要的方法。
6、迪米特原则 LOD:高内聚、低耦合。
String s = "hello"和String s = new String("hello"); 有什么区别?
String s = new String("hello");可能创建两个对象也可能创建一个对象。如果常量池中有hello字符串常量的话,则仅仅在堆中创建一个对象。如果常量池中没有hello对象,则堆上和常量池都需要创建。
String s = "hello"这样创建的对象,JVM会直接检查字符串常量池是否已有"hello"字符串对象,如没有,就分配一个内存存放"hello",如有了,则直接将字符串常量池中的地址返回给栈。(没有new,没有堆的操作)
什么是序列化?
将对象状态信息转换为可以存储或者传输的形式的过程。
Java有哪些序列化方式?
- 实现Serializable接口
- Json序列化
- FastJson序列化
- ProtoBuff序列化
……
JVM如何判断对象为垃圾对象?
引用计数算法:
给对象添加一个引用计数器,当有一个地方引用它时,计数器值就加1;当引用失效时,计数器就减1;任何时候计数器都为0的对象就是不可能再被使用的。(缺点:循环引用问题)
可达性分析算法:
通过一系列的称为“GC roots”的对象作为起始点,从这些节点,开始向下搜索,搜索所走过的路径称为引用链,当一个对象到 GC root 没有任何引用链相连(用图论的话来说,就是从 GC roots 到这个对象不可达),则证明此对象是不可用的,需要标记两次。
JVM的新生代、老年代、持久代都存储哪些东西?
新生代:
方法中new一个对象,就会先进入新生代。
老年代:
新生代中经历了N次垃圾回收仍然存活的对象就会被放到老年代中。
大对象一般直接放入老年代。
当Survivor空间不足。需要老年代担保一些空间,也会将对象放入老年代。
永久代:
指的就是方法区。
可达性分析算法中,哪些对象可作为GC Roots对象?
虚拟机栈中引用的对象
方法区静态成员引用的对象
方法区常量引用对象
本地方法栈JNI引用的对象
什么时候出发MinGC和FullGC?
MinGC:
当Eden区满时,触发Minor GC.
FullGC:
调用System.gc时,系统建议执行Full GC,但是不必然执行
老年代空间不足
方法区空间不足
通过Minor GC后进入老年代的平均大小大于老年代的剩余空间
堆中分配很大的对象,而老年代没有足够的空间
说几个常见的垃圾收集器?
新生代
Serial垃圾收集器:单线程-复制算法
ParNew垃圾收集器:多线程-复制算法
Parallel Scavenge垃圾收集器:关注可控制的吞吐量(高效)-复制算法
老年代
Serial Old垃圾收集器:单线程-标记整理算法
Parallel Old垃圾收集器:多线程-标记整理算法
CMS垃圾收集器:多线程-标记清除算法
1、初始标记(CMS initial mark): 仅只标记一下GC Roots能直接关联到的对象, 速度很快。
2、并发标记(CMS concurrent mark): GC Roots Tracing过程。
3、重新标记(CMS remark): 修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。
4、并发清除(CMS concurrent sweep): 已死对象将会就地释放。
CMS优点:并发收集,低停顿。
CMS缺点:可能由于CPU数小而引发吞吐量问题,可能回收失败,也会产生碎片。
1、CMS默认启动的回收线程数=(CPU数目+3)*4,当CPU数>4时, GC线程最多占用不超过25%的CPU资源, 但是当CPU数<=4时, GC线程可能就会过多的占用用户CPU资源, 从而导致应用程序变慢, 总吞吐量降低。
2、由于并发进行,CMS在收集与应用线程会同时会增加对堆内存的占用,也就是说,CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败时,将触发担保机制,串行老年代收集器将会以STW的方式进行一次GC,从而造成较大停顿时间。
3、标记清除算法无法整理空间碎片,会产生大量碎片,老年代空间会随着应用时长被逐步耗尽,最后将不得不通过担保机制对堆内存进行压缩。
G1垃圾收集器
同优秀的CMS垃圾回收器一样,G1也是关注最小时延的垃圾回收器,也同样适合大尺寸堆内存的垃圾收集,官方也推荐使用G1来代替选择CMS。G1最大的特点是引入分区的思路,弱化分代的概念,合理利用垃圾收集各个周期的资源,解决了其他收集器甚至CMS的众多缺陷。
因为每个区都有E(eden)、S(survivor)、O(old generation)代,所以在G1中,不需要对整个Eden等代进行回收,而是寻找可回收对象比较多的区,然后进行回收(虽然也需要STW操作,但是花费的时间是很少的),保证高效率。
新生代收集
G1的新生代收集跟ParNew类似,如果存活时间超过某个阈值,就会被转移到S/O区。
年轻代内存由一组不连续的heap区组成, 这种方法使得可以动态调整各代区域的大小。
老年代收集
初始标记:在G1中, 该操作附着一次年轻代GC, 以标记Survivor中有可能引用到老年代对象的Regions.
扫描根区域 (Root Region Scanning: 与应用程序并发执行)
扫描Survivor中能够引用到老年代的references. 但必须在Minor GC触发前执行完
并发标记:在整个堆中查找存活对象, 但该阶段可能会被Minor GC中断。
重新标记:完成堆内存中存活对象的标记。
清理:在含有存活对象和完全空闲的区域上进行统计(STW)、擦除Remembered Sets(使用Remembered Set来避免扫描全堆,每个区都有对应一个Set用来记录引用信息、读写操作记录)(STW)、重置空regions并将他们返还给空闲列表(free list)(Concurrent)
G1相比于CMS收集器的突出改进:
1、基于标记-整理算法,不产生内存碎片。
2、可以精准控制停顿时间,在不牺牲吞吐量的前提下,实现低停顿垃圾回收。
常见的垃圾收集组合?
说一下TCP三次握手?
第一次握手:客户端发送数据包将SYN置1,表示希望建立连接,seq=x。发完后进入SYN_SEND状态。
第二次握手:服务器收到请求后,通过SYN确认是建立连接请求,然后发送一个响应包,将SYN=1 ACK=1 seq=y ack=x+1,然后进入SYN_RCVD状态。
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
为什么要三次握手?
1、保持信息对等。
2、防止请求超时导致脏连接。
两次握手会怎样?
如果两次握手就创建连接,传输完数据并释放连接后,第一个超时的连接请求才到达服务器,服务器会认为是客户端创建新连接的请求,然后创建连接。此时客户端的状态不是SYN_SENT,所以会直接丢弃服务器传来的确认数据,导致最后只是服务器单方面建立了连接。
说一下四次挥手?
1、客户端想要关闭连接,然后发送FIN信号并带上seq信息给服务器。
2、服务器应答ACK告诉客户端可以断开,但是要等我把数据发送完喽。注意这时候客户端进入FIN_WAIT_2状态。
3、服务器将数据发送完后发送FIN+ACK给客户端,告诉客户端OK了,然后自己进入CLOSE_WAIT状态。
4、客户端收到后,给服务器发送ACK确认收到,然后自己进入TIME_WAIT状态。
为什么不直接关闭而是进入TIME_WAIT呢?
1、客户端要确认服务器能收到ACK信号。(如果不确认这一点,服务器会认为客户端没有收到自己的FIN+ACK报文,所以会重发)
2、防止失效请求。(为了防止已失效的连接的请求数据包和正常的混淆)
四次挥手白话文:
客户端:我断连接了阿。
服务器:噢,好的知道了,不过要等等,我还有一些数据没传完,我传完了告诉你。
服务器:小老弟,我传完了,可以关闭了,收到请回复(没有回复的话我过会重新喊你)
客户端:收到(我得再等等,要确认我哥收到了我的回复)
TCP和UDP的区别?
- TCP面向连接,UDP面向非连接
- TCP提供可靠的服务(数据传输无差错、不丢失、不重复、按序到达),UDP不可靠
- TCP面向字节流,UDP面向报文
- TCP数据传输慢,UDP数据传输快
- TCP首部开销20字节,UDP8字节
在浏览器中输入网址之后执行会发生什么?
1、DNS解析,找到对应ip地址。
2、客户端发起http/https请求,然后交给传输层。
3、传输层将请求分成报文段,添加目标源和端口,并随机用一个本地接口封装进报头,然后交给网络层。
4、网络层加上双方的ip地址信息,并负责路由分发。
5、链路层中,包通过链路层发送到路由器,通过邻居协议查找给定IP地址的MAC地址,然后发送ARP请求查找目的地址,如果得到回应后就可以使用ARP的请求应答交换的IP数据包进行传输了,然后发送IP数据包到达服务器的地址。
IP地址的分类?
A类地址:以0开头,第一个字节范围:0~127(1.0.0.0 - 126.255.255.255);
B类地址:以10开头,第一个字节范围:128~191(128.0.0.0 - 191.255.255.255);
C类地址:以110开头,第一个字节范围:192~223(192.0.0.0 - 223.255.255.255);
内部地址:10.0.0.0—10.255.255.255, 172.16.0.0—172.31.255.255, 192.168.0.0—192.168.255.255。