1、抽象类和接口的区别?
首先了解什么是抽象类和接口:
➢ 抽象类:被 abstract 修饰的类就是抽象类,用来捕捉子类的通用性。它不能被实例化,只能用作子类的超类,抽象类是
被用来创建继承层级里子类的模板。
➢ 接口:可以说是一种特殊抽象类,接口中的所有方法都必须是抽象的。接口中的方法定义默认被 public abstract 修饰,
接口中的成员变量类型默认public static final。
二者具体区别:
➢ 1)抽象类可以有非抽象方法,接口不存在非抽象方法;
➢ 2)使用 extends 继承抽象类并实现抽象方法,是 implements 实现接口中所有方法;
➢ 3)抽象类支持构造函数(但不能被 abstract 修饰),接口无构造函数;
➢ 4)抽象类的抽象方法可以被 public、protected、default 修饰,接口只能是 public;
➢ 5)抽象类可以有 main 函数,接口不支持;
2、类初始化顺序?
➢ 1)父类–静态变量/静态初始化块(按代码顺序);
➢ 2)子类–静态变量/静态初始化块;
➢ 3)父类–变量/初始化块;
➢ 4)父类–构造器;
➢ 5)子类–变量/初始化块;
➢ 6)子类–构造器。
1、数组和集合的区别?
➢ 存储方式:数组是一种固定大小的数据结构,它将元素存储在连续的内存空间中,可以通过索引访问元素。集合是一组
元素的无序集合,它们可以存储在任意的内存位置上。
➢ 大小限制:数组的大小在创建时确定,并且不能动态改变。集合的大小可以根据需要动态增长或缩小。
➢ 元素类型:数组可以存储相同类型的元素,而集合可以存储不同类型的元素。
➢ 元素顺序:数组中的元素按照其索引顺序存储和访问。集合中的元素没有明确的顺序,可以通过迭代器或者哈希等方式来访问。
➢ 元素重复:数组中可以存储重复的元素,而集合中不允许存在重复的元素。
➢ 功能和操作:数组提供了快速的随机访问元素的能力,可以通过索引进行元素的查找和修改。集合提供了更丰富的功能,如
添加、删除、查找、合并等,可以根据需要选择不同的实现方式(例如:哈希表、树等)。
2、ArrayList 和 LinkedList 的区别?
➢ 1)底层结构不同
二者都是 List 接口的实现,顾名思义,从数据结构上看,它们底层一个是数组,一个是链表的数据结构。前者是数组队列,
相当于动态数组;后者为双向链表结构,也可当作堆栈、队列、双端队列。
➢ 2)访问效率的不同
对于随机访问(get 和 set)操作,ArrayList 效率比 LinkedList 更高,因为 LinkedList 是线性的数据存储方式,
所以需要移动指针从前往后依次查找。
对于数据增加删除(add 和 remove)操作,LinkedList 的效率又会比 ArrayList 更高,因为数组在其中进行增删操作时,
会对操作点之后所有数据的下标索引造成影响,需要进行数据的移动。
➢ 3)利用效率的不同
从利用效率来看,ArrayList 自由性较低,因为它需要有手动或默认一个固定大小的容量,但是它的使用比较方便,只需要创建,
然后添加数据,通过调用下标进行使用;而 LinkedList 自由性较高,能够动态的随数据量的变化而变化,但是它不便于使用。
➢ 4)控件开销的不同
ArrayList 控件开销在于底层数组中预留了一定空间;而LinkList主要控件开销在于需要存储结点信息以及结点指针信息。
3、HashMap底层数据结构是什么?
在 JDK 1.7 中,HashMap 底层使用数组+链表的形式进行存储,在 JDK 1.8 之后,增加了红黑树,即数组+链表+红黑树的形式存
储元素。其中桶数组是用来存储元素,链表是用来解决哈希冲突,而红黑树是用来提高查询效率的。
链表和红黑树之间的转换?
● 链表长度大于 8 且数组长度大于 64,则将链表转换成红黑树;
● 链表长度小于 6 时会将红黑树转换成链表。
4、HashMap 是否线程安全以及如何解决?
HashMap 是非线程安全的,可能会发生这些问题:
➢ 多线程下扩容死循环。由 JDK 1.7 的头插法导致,1.8 中已解决。
➢ 多线程的 put 可能导致元素的丢失。
➢ 如果两个线程计算出来的索引位置是相同的,那会造成前一个 key 被后一个 key 覆盖,从而导致前一个元素的丢失。
➢ put 和 get 并发时,可能导致 get 为 null。线程 1 执行 put 时,因为元素个数超出 threshold 而导致 rehash,
线程 2 此时执行 get,有可能导致这个问题。这个问题在 JDK 1.7 和 JDK 1.8 中都存在。
5、HashMap 节点是否有序?
HashMap 是无序的,根据 hash 值随机插入。如果想使用有序的 Map,可以使用 LinkedHashMap 或者 TreeMap
1、 输入流,输出流是以什么为参照的
➢ 以内存作为参照物,
➢ 往内存中去,叫做输入,或者叫做读。
➢ 从内存中出来,叫做输出,或者叫做写。
2、I/O流所使用的具体抽象类
➢ java.io.InputStream //字节输入流
➢ java.io.OutputStream //字节输出流
➢ java.io.Reader //字符输入流
➢ java.io.Writer //字符输出流
➢ 所有的流都实现了:java.io.Colseable接口,都是可关闭的,都有close()方法. 流毕竟是一个管道,这个是内存和
硬盘之间的通道,养成一个好习惯,用完流将其关闭。
➢ 所有的输出流都实现了:java.io.Flushable接口,都是可刷新的,都有flush()方法。养成一个好习惯,输出流在最终
输出之后,一定要记得flush()刷新一下。这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道!)刷新的
作用就是清空管道。如果没有flush()可能会导致数据丢失.
➢ 注意:在java中只要"类名"以 Stream 结尾的都是字节流。以 "Reader/Writer" 结尾的都是字符流 。
3、Java.io 包下常用的流(16个)
文件专属
java.io.FileinputStream (用得最多)
java.io.FileOutputStream (用得最多)
java.io.FileReader
java.io.FileWriter
转换流:(将字节流转换成字符流)
java.io.InputStreamReader
java.io.outputStreamWriter
缓冲流专属:
java.io.BufferedReader
java.io.BufferedWriter
java.io.BufferedInputStream
java.io.BufferedOutputStream
数据流专属:
java.io.DataInpoutStream
java.io.DataOutputStream
标准输出流:
java.io.PrintWriter
java.io.PrintStream
对象专属流:(掌握)
java.io.ObjectInputStream
java.io.ObjectOutputStream
4、BIO、NIO、AIO 有什么区别?
➢ BIO (Blocking I/O) :同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别
高(小于单机 1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过
多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十
万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
➢ NIO (New I/O) : NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO 框架,对应 java.nio 包,
提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为Non-blocking,不单纯是 New。它支持面向缓冲的,
基于通道的 I/O 操作方法。NIO 提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应 de 的 SocketChannel
和ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持
一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O
来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。
➢ AIO (Asynchronous I/O) : AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版NIO 2,它是异步非阻塞的 IO 模型。
异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的
线程进行后续的操作。AIO 是异步 IO 的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的IO 行为还是同步的。
对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO 操作本身是同步的。
查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。
1、Java 是如何处理异常的?
在一个方法中如果发生异常,这个方法会创建一个异常对象,并转交给 JVM,该异常对象包含异常名称,异常描述以及异常发生时应用
程序的状态。创建异常对象并转交给 JVM 的过程称为抛出异常。可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列方
法调用的有序列表叫做调用栈。
JVM 会顺着调用栈去查找看是否有可以处理异常的代码,如果有,则调用异常处理代码。当 JVM 发现可以处理异常的代码时,
会把发生的异常传递给它。如果 JVM 没有找到可以处理该异常的代码块,JVM 就会将该异常转交给默认的异常处理器
(默认处理器为 JVM 的一部分),默认异常处理器打印出异常信息并终止应用程序。
2、finally 不会被执行的情况?
➢ 在前面的代码中用了System.exit()退出程序。
➢ finally 语句块中发生了异常。
➢ 程序所在的线程死亡。
3、throw 和 throws 的区别?
➢ 作用位置
throw 关键字用在方法内部,而 throws 关键字用在方法声明上。
➢ 异常个数
throw 只能抛出一种异常,用来抛出方法或代码块中的异常,受查异常和非受查异常都可以被抛出。
throws 可以抛出多个异常,用来标识该方法可能抛出的异常列表。
4、try-catch-finally 中哪个部分可以省略?
➢ catch 和 finally可以被省略其中一个,finally代码块是无论是否捕捉到异常,都会执行finally代码块的逻辑。
5、try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
➢ 捕获到异常后,不会直接返回,会先执行完finally代码块的语句继续执行catch代码块中的return。
6、常见的异常类有哪些?
➢ NullPointerException
➢ SQLException
➢ IndexOutOfBoundException
➢ NumberFormatException
➢ FileNotFoundException
➢ IOException
➢ IllegalArgumentException
➢ NoSuchMethodException
1.什么是反射?
➢ java反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它
的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能成为java的反射机制。
2.什么是 java 序列化?什么情况下需要序列化?
➢ 序列化:将java对象转换成字节流的过程。
➢ 反序列化:将字节流转换成java对象的过程。
➢ 当java对象需要在网络上传输或者持久化存储到文件中时,就需要对Java对象进行序列化处理。
➢ 序列化的实现:类实现Serializable接口。
3.动态代理是什么?有哪些应用?
➢ 在运行时,创建一个新的类,即创建动态代理,可以调用和扩展目标类的方法。动态代理的类是自动生成的。
➢ 应用:Spring的AOP,加事务,加权限,加日志
4.怎么实现动态代理?
➢ 基于jdk,需要实现InvocationHandler接口,重写invoke方法。
➢ 基于cglib,需要jar包依赖;
1、并发编程三要素是什么?
➢原子性:指的是一个操作不能再继续拆分,要么一次操作完成,要么就是不执行;
➢可见性:指的是一个变量在被一个线程更改后,其他的线程能立即看到最新的值;
➢有序性:指的是程序的执行按照代码的先后顺序执行。
至于为什么会提出这三个要素,我理解这是因为 Java 编程中如果满足了这 3 个特性,程序的并发操作就是线程安全的。
对于关键字 synchronized,即保证了原子性,也保证了可见性和有序性。而关键字 volatile 无法保证原子性。
2、Java 线程有几种状态?
在 Java 中,线程主要分为六种状态:
➢ 初始(NEW):新创建了一个线程对象,但还没有调用 start() 方法;
➢ 运行(RUNNABLE):Java 线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”;线程对象创建后,
其它线程(如 main 线程)调用了该对象的 start() 方法。该状态的线程位于可运行线程池中,等待被线程调度选中,
获取 CPU的 使用权,此时处于就绪状态(ready)。就绪状态的线程在获得 CPU 时间片后变为运行中状态(running)。
➢ 阻塞(BLOCKED):表示线程阻塞于锁;
➢ 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断);
➢ 超时等待(TIMED_WAITING):该状态不同于 WAITING,它可以在指定的时间后自行返回;
➢ 终止(TERMINATED):表示该线程已经执行完毕。
2、Java 线程状态是如何切换的?
先来看一下 Java 线程的状态图:
3、Java 如何创建一个线程?如何创建一个进程?
➢ 1)继承 Thread 类,重写 run 方法,然后通过start()方法去启动线程;
➢ 2)实现 Runnable 接口;
➢ 3)实现 Callable 接口;
在 Java 中可以通过两种方式来创建进程:
➢ 1)第一种方式是通过 Runtime.exec() 方法来创建一个进程;
➢ 2)第二种方法是通过 ProcessBuilder 的 start 方法来创建进程。
4、Callable 和 Runnable 区别?
➢ 1. 方法不同
○Runnable接口只有一个run()方法,该方法不返回任何值,因此无法抛出任何checked Exception。
○Callable接口则有一个call()方法,它可以返回一个值,并且可以抛出一个checked Exception。
➢ 2. 返回值不同
○Runnable的run()方法没有返回值,只是一个void类型的方法。
○Callable的call()方法却必须有一个返回值,并且返回值的类型可以通过泛型进行指定。
➢ 3. 异常处理不同
○在Runnable中,我们无法对run()方法抛出的异常进行任何处理。
○但在Callable中,自定义的call()方法可以抛出一个checked Exception,并由其执行者Handler进行捕获并处理。
➢ 4. 使用场景不同
○Runnable适用于那些不需要返回值,且不会抛出checked Exception的情况,比如简单的打印输出或者修改一些共享的变量。
○Callable适用于那些需要返回值或者需要抛出checked Exception的情况,比如对某个任务的计算结果进行处理,或者需要
进行网络或IO操作等。在Java中,常常使用Callable来实现异步任务的处理,以提高系统的吞吐量和响应速度。
3、为什么使用线程池?
线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。
因为创建线程和销毁线程的花销是比较大的,这些时间有可能比处理业务的时间还要长。这样频繁的创建线程和销毁线程,再加上业
务工作线程,消耗系统资源的时间,可能导致系统资源不足。
使用线程池可以‘减少创建和销毁线程的次数’,每个工作线程都可以被‘重复利用’,执行多个任务。提高程序效率的同时还方便管理
以及限流。
4、线程池有哪些参数?
➢ corePoolSize:线程池中的核心线程数量,即使在没有用的时候,也不会被回收(设置了 allowCoreThreadTimeOut 除外);
➢ maximumPoolSize:线程池中可以容纳的最大线程的数量;
➢ keepAliveTime:非核心线程空闲的时间;
➢ util:非核心线程存活时间单位,即 keepAliveTime 的时间单位;
➢ workQueue:工作队列,任务可以储存在任务队列中等待被执行,执行的是 FIFO 原则(先进先出);
➢ threadFactory:创建线程的线程工厂类,可以用来设定线程名、是否为 daemon 线程等;
➢ handler:拒绝策略,可以在任务满了之后采用一定策略拒绝执行某些任务。
5、讲讲线程增长的过程?
主要是理解 corePoolSize、maximumPoolSize 和 workQueue 之间的关系。
➢ 1)当线程池中线程数量小于 corePoolSize 核心线程数时,新提交的任务就会创建一个新线程执行,即使线程池中存在空闲线程;
➢ 2)当线程池中线程数量达到 corePoolSize 核心线程数后,新提交的任务就会被放到 workQueue 工作队列中,等待被线程池调度执行;
➢ 3)当 workQueue 也满了后,且满足当前线程数量是否小于 maximumPoolSize 最大线程数量时,新提交任务将继续创建新的线程执行;
➢ 4)若线程数量达到 maximumPoolSize 最大线程数量,则新提交任务交予 handler 拒绝策略处理。
6、线程池有哪些拒绝策略?
在线程池 ThreadPoolExecutor 中,已经包含四种处理策略。
➢ AbortPolicy:直接丢弃任务,并抛出 RejectedExecutionException 异常,也是线程池默认采用的策略;
➢ CallerRunsPolicy:在调用者线程中直接执行被拒绝任务的 run 方法,除非线程池已经 shutdown,则直接抛弃任务;
➢ DiscardOleddestPolicy:抛弃进入队列最早的那个任务,也是即将被执行的任务,然后尝试把这次拒绝的任务放入队列;
➢ DiscardPolicy:直接丢弃任务,不予任何处理。
➢ 自定义策略:除了 JDK 默认提供的四种拒绝策略,我们可以通过实现 RejectedExecutionHandler 接口根据自己的业务
需求去自定义拒绝策略。
7、线程池有哪些工作队列?
ThreadPoolExecutor 线程池的工作队列可以分为两大类:无界队列和有界队列。
➢ ArrayBlockingQueue
是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
➢ LinkedBlockingQueue
一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。
newFixedThreadPool 使用了这个队列。
容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE。
➢ SynchronousQueue
一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,
吞吐量通常要高于LinkedBlockingQueue。newCachedThreadPool 线程池使用了该队列。
➢ PriorityBlockingQueue
一个具有优先级的无限阻塞队列。
➢ DelayQueue
是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。
newScheduledThreadPool 线程池使用了这个队列。
无界队列不存在饱和的问题,但是其问题是当请求持续高负载的话,任务会无脑的加入工作队列,那么很可能导致内存
等资源溢出或者耗尽。而有界队列不会带来高负载导致的内存耗尽的问题,但是有引发工作队列已满情况下,新提交的
任务如何管理的难题,这就是线程池工作队列饱和策略要解决的问题。
8、几种常见线程池和使用场景?
➢ newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
➢ newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
➢ newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
➢ newScheduledThreadPool
创建一个定长定时及周期性的线程池,支持定时及周期性任务执行。
➢ newWorkStealingPool
一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用 cpu 数量的线程来并行执行。
9、shutdown 和 shutdownNow 区别?
shutdown 和 shutdownNow 均可用于关闭线程池
➢ shutdown
当我们调用 shutdown 方法后,线程池将不再接受新的任务,但也不会强制终止已经提交或者正在执行中的任务。
➢ shutdownNow
当调用 shutdownNow 方法后,会向正在执行的全部任务发出 interrupt() 停止执行信号,取消全部还未开始
执行的任务,并且返回还没开始的任务列表。
10、execute 和 submit 区别?
虽然二者在线程池中都可以提交任务,但却有着完全不同的实现:
➢接收参数
execute() 只能接受 Runnable 类型的任务;
而 submit() 可接受 Runnable 和 Callable 两种类型任务,只是 Runnable 返回值均为 void,
所以使用 Future 的 get() 获得的还是 null。
➢返回值
由 Callable 和 Runnable 的区别可知,execute() 没有返回值,submit() 可以有返回值。
➢异常处理
execute() 是 Runnable 接口的实现,所以只能使用 try-catch 来捕获CheckedException,并通过
实现 UncaughtExceptionHande 接口处理UncheckedException,即和普通线程的处理方式完全一致。
而 submit() 仅需通过捕获 Future.get 抛出的异常,即可让线程调用者感知到内部的 Checked
或者 Unchecked Exception。
1、有哪些调优工具?
JDK 自带工具
首先,JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。
➢ 1)jps:与 Linux 上的 ps 类似,用于查看有权访问的虚拟机的进程,并显示他们的进程号。
当未指定 hostid 时,默认查看本机 JVM 进程。
➢ 2)jinfo:可以输出并修改运行时的 Java 进程的一些参数。
➢ 3)jstat:可以用来监视 JVM 内存内的各种堆和非堆的大小及其内存使用量。
➢ 4)jstack:堆栈跟踪工具,一般用于查看某个进程包含线程的情况。
➢ 5)jmap:打印出某个 JVM 进程内存内的所有对象的情况,一般用于查看内存占用情况。
➢ 6)jconsole:一个 GUI 监视工具,可以以图表化的形式显示各种数据,并支持远程连接。
Eclipse Memory Analyzer(MAT) 是一款内存分析工具,利用 dump 分享内存泄漏。
2、有哪些常用的调优参数?
➢ 常用的设置
○ 1)-Xms:初始堆大小,JVM 启动的时候,给定堆空间大小。
○ 2)-Xmx:最大堆大小,JVM 运行过程中,如果初始堆空间不足的时候,最大可以扩展到多少。
○ 3)-Xmn:设置堆中年轻代大小。整个堆大小=年轻代大小+年老代大小+持久代大小。
○ 4)-XX:NewSize=n:设置年轻代初始化大小大小。
○ 5)-XX:MaxNewSize=n:设置年轻代最大值。
○ 6)-XX:NewRatio=n:设置年轻代和年老代的比值。如 n = 3 时表示年轻代与年老代比值为 1:3,
年轻代占整个年轻代+年老代和的 1/4。
○ 7)-XX:SurvivorRatio=n 年轻代中 Eden 区与两个 Survivor 区的比值。注意 Survivor 区有两个。
8表示两个Survivor :eden=2:8 ,即
一个Survivor占年轻代的1/10,默认就为8。
○ 8)-Xss:设置每个线程的堆栈大小。JDK5后每个线程 Java 栈大小为 1M,以前每个线程堆栈大小为 256K。
○ 9)-XX:ThreadStackSize=n:线程堆栈大小。
○ 10)-XX:PermSize=n:设置持久代初始值。
○ 11)-XX:MaxPermSize=n:设置持久代大小。
○ 12)-XX:MaxTenuringThreshold=n 设置年轻带垃圾对象最大年龄。如果设置为 0 的话,
则年轻代对象不经过 Survivor 区,直接进入年老代。
➢ 不常用的设置
○ 1)-XX:LargePageSizeInBytes=n:设置堆内存的内存页大小。
○ 2)-XX:+UseFastAccessorMethods:优化原始类型的 getter 方法性能。
○ 3)-XX:+DisableExplicitGC:禁止在运行期显式地调用S ystem.gc(),默认启用。
○ 4)-XX:+AggressiveOpts:是否启用 JVM 开发团队最新的调优成果。如编译优化,偏向锁,
并行年老代收集等,JDK 6 之后默认启动。
○ 5)-XX:+UseBiasedLocking:是否启用偏向锁,JDK 6 默认启用。
○ 6)-Xnoclassgc:是否禁用垃圾回收。
○ 7)-XX:+UseThreadPriorities:使用本地线程的优先级,默认启用。
➢ 回收器相关设置
○ 1)-XX:+UseSerialGC:设置串行收集器,年轻带收集器。
○ 2)-XX:+UseParNewGC:设置年轻代为并行收集。可与 CMS 收集同时使用。
JDK 5 以上 JVM 会根据系统配置自行设置,所以无需再设置此值。
○ 3)-XX:+UseParallelGC:设置并行收集器,目标是目标是达到可控制的吞吐量。
○ 4)-XX:+UseParallelOldGC:设置并行年老代收集器,JDK 6 支持对年老代并行收集。
○ 5)-XX:+UseConcMarkSweepGC:设置年老代并发收集器。
○ 6)-XX:+UseG1GC:设置 G1 收集器,JDK 9 默认垃圾收集器
3、jvm内存分为那几部分,分别储存什么数据
➢ 程序计数器:– 当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,
来选取下一条需要执行的字节码指令。分支,循环,跳转,异常处理,线程恢复等基础功能,都需要依赖这个计数器完成。
➢ 虚拟机栈:– 栈是线程私有的内存区域,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
➢ 本地方法栈:– 为虚拟机使用到的Native方法服务,即原声c++代码
➢ 堆:– 被所有线程共享,所有的对象实例都在这里分配内存
➢ 方法区:被所有线程共享,用来存储已被虚拟机加载的类信息、常量、静态变量、
JIT(just in time,即时编译技术)编译后的代码等数据。
4、一个对象从创建到销毁都是怎么在这些部分里存活和转移
➢ 为对象分配存储空间,
➢ 开始构造对象,
➢ 从父类到子类对static成员进行初始化,
➢ 按照顺序对父类成员变量初始化,递归调用父类的构造方法,
➢ 按照顺序对子类成员变量初始化,子类构造方法调用。
➢ 一旦对象被创建,并有某个引用指向它,这个对象的状态就切换到了应用阶段(In Use)。
➢ 当该对象不再处于引用状态,将被回收
5、内存哪些部分参与GC的回收
➢ 堆存放的部分参与GC回收
6、Java的内存模型是怎么设计的,为什么要这么设计
➢ 并发控制中的一些规范
7、什么时候一个对象会被GC
➢ java垃圾回收机制:内存不够用或者CPU空闲时由JVM自动执行,清理无用对象(没有被引用到的对象)所占用的内存空间。
➢ 当然这些内存垃圾不是时时刻刻都在回收的,那样太浪费时间了,当我们需要向堆内存申请空间而发现堆内存空间不足的
时候,后台的监听线程就会自动触发GC算法,帮我们回收无用的垃圾,腾出空间。
8、为什么要在这种时候对象才会被GC
➢ 清理无用对象(没有被引用到的对象)所占用的内存空间。避免内存泄漏,是java的一种机制
9、GC策略都有哪些分类
➢ 回收处理分为:引用计数算法和可达性算法,现主流虚拟机采用的可达性算法。
➢ 可达性算法相对计数算法的优势在于可以回收相互引用,但是程序获取不到的对象。
➢ 可达性算法中的根对象:
○ 1,Java虚拟机栈中引用的对象;
○ 2,方法区中的类静态成员引用的对象(static修饰的);
○ 3,方法区中的常量引用的对象(主要是final修饰的);
○ 4,本地方法栈中JNI(Java Native Interface)引用的对象。
➢ 可达性算法有4大分类:
○1, 标记-清除算法:
■ 标记阶段会通过可达性分析将不可达的对象标记出来。清除阶段会将标记阶段标记的垃圾对象清除。
○2, 标记-压缩算法
■ 在标记-清除算法之后增加了一个内存整理,预防内存碎片化
○3, 复制-清除算法
■ 划分主备内存,每次可达性算法时将当前内存连续复制到备用内存,再整体清楚原内存,切换到备用内存
○4, 分代算法
■ 是标记压缩算法和复制清除算法的结合, 针对内存中新创建的对象和多次GC依然存在的对象进行分类,
新生代区会采用复制清除算法,老生代采用标记压缩算法。
➢ 可达性算法策略:
○ 1, 标记-清除算法:优势是快,但劣势是会有内存碎片。
○ 2, 标记-压缩算法:比直接清除慢一点,但是内存是连续的。
○ 3,复制-清除算法: GC速度最快,但最耗费内存,适用于每次回收时,存活对象少的场景,这样就会减少复制量。
○ 4, 分代算法:是2和3的综合。现主流jvm是采用该策略
10、什么是堆外内存
➢ 堆外内存和堆内内存是相对的二个概念,
➢ 堆内内存
○ 是我们平常工作中接触比较多的,我们在jvm参数中只要使用-Xms,-Xmx等参数就可以设置堆的大小和最大值,
理解jvm的堆还需要知道下面这个公式:
堆内内存 = 新生代+老年代+持久代
○ 在使用堆内内存(on-heap memory)的时候,完全遵守JVM虚拟机的内存管理机制,采用垃圾回收器(GC)
统一进行内存管理,GC会在某些特定的时间点进行一次彻底回收,也就是Full GC,GC会对所有分配的堆内内
存进行扫描,在这个过程中会对JAVA应用程序的性能造成一定影响,还可能会产生 Stop The World。
➢ 堆外内存
○ 和堆内内存相对应,堆外内存就是把内存对象分配在Java虚拟机的堆以外的内存,这些内存直接受操作系统
管理(而不是虚拟机),这样做的结果就是能够在一定程度上减少垃圾回收对应用程序造成的影响。
○ 作为JAVA开发者我们经常用java.nio.DirectByteBuffer对象进行堆外内存的管理和使用,它会在对象创建
的时候就分配堆外内存。
1、什么是设计模式?
设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、
让代码更容易被他人理解、保证代码可靠性、程序的重用性。
2、使用设计模式的好处?
➢ 设计模式可在多个项目中重用。
➢ 设计模式提供了一个帮助定义系统架构的解决方案。
➢ 设计模式吸收了软件工程的经验。
➢ 设计模式为应用程序的设计提供了透明性。
➢ 设计模式是被实践证明切实有效的,由于它们是建立在专家软件开发人员的知识和经验之上的。
3、设计模式六大原则?
➢ 1)单一责任原则(Principle of single responsibility):一个类或者一个方法只负责一项职责。
➢ 2)里氏替换原则(Liskov Substitution Principle):使用基类的任何地方可以使用继承的子类,完美的替换基类。
➢ 3)依赖倒转原则(Dependence Inversion Principle):其核心思想是面向接口编程。
➢ 4)接口隔离原则(Interface Segregation Principle):使用多个隔离的接口,比使用单个接口要好。
还是一个降低类之间的耦合度的意思。
➢ 5)迪米特法则(Demeter Principle):最少知道原则,一个对象应当对其他对象有尽可能少地了解,简称类间解耦。
➢ 6)开闭原则(Open Close Principle):尽量通过扩展软件实体来解决需求变化,而不是通过修改已有的代码来完成变化。
4、设计模式如何分类?
通常情况下,我们可以将常用的 23 种设计模式分为创建模式、结构模式以及行为模式三种类型。
➢ 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
➢ 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
➢ 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、
状态模式、访问者模式、中介者模式、解释器模式。
➢ 单例模式
什么是单例模式
保证一个类只有一个实例,并且提供一个访问该全局访问点。
单例模式的应用
➢ 1)应用程序的日志应用,一般都可用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,
因为只能有一个实例去操作,否则内容不好追加。
➢ 2)数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
➢ 3)多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
单例模式的实现
单例模式的创建方式主要分为懒汉式和饿汉式两种,并在次基础衍生出更多实现方式:
➢ 1)饿汉式:类初始化时会立即创建该类单例对象,线程天生安全,调用效率高。
➢ 2)懒汉式:类初始化时,不会立即实例化该对象,而是在真正需要使用的时候才会创建该对象,具备懒加载功能。
➢ 3)静态内部方式:结合了懒汉式和饿汉式各自的优点,真正需要对象的时候才会加载,加载类是线程安全的。
public class Demo3 {
public static class SingletonClassInstance {
private static final Demo3 DEMO_3 = new Demo3();
}
// 方法没有同步
public static Demo3 getInstance() {
return SingletonClassInstance.DEMO_3;
}
}
➢ 4)枚举单例:使用枚举实现单例模式,其优点是实现简单、调用效率高,枚举本身就是单例,由 JVM 从根本上提供保障,
避免通过反射和反序列化的漏洞,而缺点是没有延迟加载。
public class Demo4 {
public static Demo4 getInstance() {
return Demo.INSTANCE.getInstance();
}
//定义枚举
private static enum Demo {
INSTANCE;
// 枚举元素为单例
private Demo4 demo4;
private Demo() {
demo4 = new Demo4();
}
public Demo4 getInstance() {
return demo4;
}
}
}
➢ 5)双重检测锁方式:因为 JVM 重排序的原因,可能会初始化多次,不推荐使用。
public class Demo5 {
private volatile static Demo5 demo5;
public static Demo5 getInstance() {
if (demo5 == null) {
synchronized (Demo5.class) {
if (demo5 == null) {
demo5 = new Demo5();
}
}
}
return demo5;
}
}
➢ 工厂模式
工厂模式提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,
并且是通过使用一个共同的接口来指向新创建的对象。
工厂模式的好处
➢ 1)工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。
➢ 2)利用工厂模式可以降低程序的耦合性,为后期的维护修改提供了很大的便利。
➢ 3)将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。
工厂模式的分类
实现了创建者和调用者分离,工厂模式又可以分为简单工厂、工厂方法以及抽象工厂三种模式。
➢ 1)简单工厂:用来生产同一等级结构中的任意产品。(不支持拓展增加产品)
➢ 2)工厂方法:用来生产同一等级结构中的固定产品。(支持拓展增加产品)
➢ 3)抽象工厂:用来生产不同产品族的全部产品。(不支持拓展增加产品;支持增加产品族)
➢ 建造者模式
建造者模式是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的方式进行创建。
建造者模式和工厂模式区别
工厂类模式是提供的是创建单个类的产品,而建造者模式则是将各种产品集中起来进行管理,更加关注零件装配,
用来具有不同的属性的产品。
建造者模式的应用
1)Java 语言中的 StringBuilder。
➢ 原型模式
原型设计模式简单来说就是克隆。原型表明了有一个样板实例,这个原型是可定制的。
原型模式多用于创建复杂的或者构造耗时的实例,因为这种情况下,复制一个已经存在的实例可使程序运行更高效。
原型模式的使用场景
1)类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。这时我们就可以通过原型拷贝避免这些消耗。
2)通过 new 产生的一个对象需要非常繁琐的数据准备或者权限,这时可以使用原型模式。
3)一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个
对象供调用者使用,即保护性拷贝。
➢ 代理模式
代理模式通过代理控制对象的访问,可以在这个对象调用方法之前、调用方法之后去处理,甚至添加新的功能。
(AOP 的实现原理)
代理模式的应用
Spring AOP、日志打印、异常处理、事务控制、权限控制等。
代理模式的分类
1)静态代理:简单代理模式,是动态代理的理论基础,也是常见使用在代理模式。
2)JDK 动态代理:使用反射完成代理,需要有顶层接口才能使用,常见是Mybatis 的 mapper 文件。
3)cglib 动态代理:也是使用反射完成代理,使用字节码技术,可以直接代理类,有个缺点就是不能对 final 类进行继承。
➢ 外观模式
也被叫做门面模式,用于隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。
➢ 策略模式
策略模式定义了一系列的算法或业务逻辑操作,并将每一个算法、逻辑、操作封装起来,而且使它们还可以相互替换。
➢ 模板方法模式
模板方法模式定义一个操作中的业务处理逻辑骨架(父类),而将一些步骤延迟到子类中。使得子类可以不改变一个操作
的结构来重定义该操作逻辑的实现。
实现一些操作时,整体步骤很固定,但是其中一小部分需要改变,这时候可以使用模板方法模式,将容易变的部分抽象出来,
供子类实现。
举个例子,去餐厅吃饭时,餐厅给我们提供了一个模板就是:看菜单,点菜,吃饭,付款,走人。这里的点菜和付款是不确定的,
需要由子类来完成的,其他的则是一个模板。
➢ 观察者模式
观察者模式是一种行为性模型,又叫发布-订阅模式。其定义对象之间一种一对多的依赖关系,使得当一个对象改变状态,
则所有依赖于它的对象都会得到通知并自动更新。
1、http 响应码 301 和 302 代表的是什么?有什么区别?
➢ 301:Moved Permanently ——被请求的资源已经永久移动到新位置
➢ 302:Found——被请求URL临时转移到新的URL
➢ 其他的响应码:
○ 信息响应(100-199)
○ 成功响应(200-299)
○ 重定向(300-399)
○ 客户端错误(400-499)
○ 服务器错误(500-599)
2、forward 和 redirect 的区别?
➢ redirect是客户端发起的请求,forward是服务端发起的请求
➢ redirect浏览器显示被请求的URL,forward浏览器地址不显示被请求的URL
➢ redirect重新开始一个request,原页面的request生命周期结束;
➢ forward另一个连接的时候,request变量是在其生命周期内的。
➢ redirect实质上是两次HTTP请求,forward是一次请求
3、简述 tcp 和 udp的区别?
➢ TCP是面向连接的,TCP提供可靠的服务,通过TCP连接传输的数据不会丢失,没有重复,并且按顺序到达。
➢ UDP是无连接的,没有可靠性,但是速度快,操作简单,要求系统资源较少,可以实现广播发送。
➢ TCP是面向字节流的,UDP是面向报文的;TCP是全双工的可靠信道,UDP是不可靠信道
4、tcp 为什么要三次握手,两次不行吗?为什么?
两次握手只能保证单向连接是畅通的。
只有经过第三次握手,才能确保双向都可以收到对方发送的数据
5、说一下 tcp 粘包是怎么产生的?
➢ TCP粘包:发送方发送的多个数据包,到接收方缓冲区首尾相连,粘成一包,被接收
➢ 产生原因:TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,
通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。
➢ 接收方原因:接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的
数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据
放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,
这样就一次取到了多包数据。
6、OSI 的七层模型都有哪些?
➢ OSI(Open System Interconnection Reference Model)开放式系统互联网通信
➢ 应用层、表达层、会话层、传输层、网络层、数据链路层、物理层
7、get 和 post 请求有哪些区别?
➢ get是从服务器上获取数据;post是向服务器传送数据
➢ get请求通过URL直接请求数据,数据信息可以在URL中看到;post请求是放在请求头中的,用户看不到;
➢ get传送的数据量小,有限制,不能大于2kb,post传送的数据可以没有限制
➢ get安全性比较低,post相对较安全
8、如何实现跨域?
➢ 使用JSONP,利用了script不受同源策略的限制;
➢ 代理跨域请求;
➢ HTML5 postMessage方法;
➢ 修改document.domain跨子域;
➢ 基于HTML5 websocket协议
9、说一下 JSONP 实现原理?
➢ 同源:同协议、同主机、同端口号
➢ json是一种数据格式,jsonp是一种数据调用的方式,带callback的json就是jsonp。
➢ 首先在客户端注册一个callback,然后把callback的名字传给服务器,此时,服务器先生成json数据,
然后以JavaScript语法的方式,生成function,返回给客户端,客户端解析script,并执行callback函数。
➢ 简单的说,就是利用script标签没有跨域限制的"漏洞"来达到与第三方通讯的目的。
10、单工、半双工、全双工
➢ 单工:只支持数据在一个方向上传输;
➢ 半双工:允许数据在两个方向上传输,但在某一时刻,只允许数据在同一个方向上传输;
➢ 全双工:允许数据可以同时接收和发送信息,实现双向通信