摘要:这篇文章是对Java基础的总结和归纳,为自己下半年的面试做准备。
前言:hexo的一些基础内容已经学习的差不多了,blog也已经布置完善了,接下来准备复习java和学习一下新的知识点,为自己下半年的面试做准备,先复习复习java吧,把一些坑点总结一下。
一.基础知识点
1.Java的四个基本特性(抽象、封装、继承,多态)
- 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
- 继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。
- 封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。
- 多态:多态性是指允许不同子类型的对象对同一消息作出不同的响应。
2.多态的理解(多态的实现方式)
- 方法重载:(overload)实现的是编译时的多态性(也称为前绑定)。
- 方法重写:(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西。
- 要实现多态需要做两件事:1). 方法重写(子类继承父类并重写父类中已有的或抽象的方法);2). 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
- 项目中对多态的应用:举一个简单的例子,在物流信息管理系统中,有两种用户:订购客户和卖房客户,两个客户都可以登录系统,他们有相同的方法Login,但登陆之后他们会进入到不同的页面,也就是在登录的时候会有不同的操作,两种客户都继承父类的Login方法,但对于不同的对象,拥有不同的操作。
3.面向对象和面向过程的区别
- 面向过程就像是一个细心的管家,事无具细的都要考虑到。而面向对象就像是个家用电器,你只需要知道他的功能,不需要知道它的工作原理。
- 面向过程”是一种是事件为中心的编程思想。就是分析出解决问题所需的步骤,然后用函数把这些步骤实现,并按顺序调用。面向对象是以“对象”为中心的编程思想。
4.面向过程能不能实现面向对象
- 不能,只能模仿,但是得不到大部分面向对象的特性,如继承,多态等。
5.重载和重写,如何确定调用哪个函数
- 重载:重载发生在同一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载。
- 重写:重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。根据不同的子类对象确定调用的那个方法。
6.面向对象开发的六个基本原则(单一职责、开放封闭、里氏替换、依赖倒置、合成聚合复用、接口隔离),迪米特法则
- 单一职责:一个类只做它该做的事情(高内聚)。在面向对象中,如果只让一个类完成它该做的事,而不涉及与它无关的领域就是践行了高内聚的原则,这个类就只有单一职责。
- 开放封闭:软件实体应当对扩展开放,对修改关闭。要做到开闭有两个要点:①抽象是关键,一个系统中如果没有抽象类或接口系统就没有扩展点;②封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而换乱。
- 里氏替换:任何时候都可以用子类型替换掉父类型。子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题。
- 依赖倒置:面向接口编程。(该原则说得直白和具体一些就是声明方法的参数类型、方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不用具体类型,因为抽象类型可以被它的任何一个子类型所替代)
- 合成聚和复用:优先使用聚合或合成关系复用代码。
- 接口隔离:接口要小而专,绝不能大而全。臃肿的接口是对接口的污染,既然接口表示能力,那么一个接口只应该描述一种能力,接口也应该是高度内聚的。
- 迪米特法则:迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。
- 项目中用到的原则:单一职责、开放封闭、合成聚合复用(最简单的例子就是String类)、接口隔离。
7.static和final的区别和用途
- Static
- 修饰变量:静态变量随着类加载时被完成初始化,内存中只有一个,且JVM也只会为它分配一次内存,所有类共享静态变量。
- 修饰方法:在类加载的时候就存在,不依赖任何实例;static方法必须实现,不能用abstract修饰。
- 修饰代码块:在类加载完之后就会执行代码块中的内容。
父类静态代码块->子类静态代码块->父类非静态代码块->父类构造方法->子类非静态代码块->子类构造方法
- Final
- 修饰变量:编译期常量:类加载的过程完成初始化,编译后带入到任何计算式中。只能是基本类型。
- 运行时常量:基本数据类型或引用数据类型。引用不可变,但引用的对象内容可变。
- 修饰方法:不能被继承,不能被子类修改。
- 修饰类:不能被继承。
- 修饰形参:final形参不可变
8.匿名内部类是什么?如何访问在其外面定义的变量?
匿名内部类是什么?
- 匿名内部类是没有访问修饰符的。
- 所以当所在方法的形参需要被匿名内部类使用,那么这个形参就必须为final
- 匿名内部类是没有构造方法的。因为它连名字都没有何来构造方法。
如何访问在其外面定义的变量?
- 所以当所在方法的形参需要被匿名内部类使用,那么这个形参就必须为final
9.如果你定义一个类,包括学号,姓名,分数,如何把这个对象作为key?要重写equals和hashcode吗
- 需要重写equals方法和hashcode,必须保证对象的属性改变时,其hashcode不能改变。
10.为什么要实现内存模型?
- 内存模型的就是为了在现代计算机平台中保证程序可以正确性的执行,但是不同的平台实现是不同的。
- 编译器中生成的指令顺序, 可以与源代码中的顺序不同;
- 编译器可能把变量保存在寄存器而不是内存中;
- 处理器可以采用乱序或并行等方式来执行指令;
- 缓存可能会改变将写入变量提交到主内存的次序;
- 保存在处理器本地缓存中的值,对其他处理器是不可见的;
二.集合知识点
1.Hash Map和Hash Table的区别
- Hashtable的方法是同步的,HashMap未经同步,所以在多线程场合要手动同步HashMap这个区别就像Vector和ArrayList一样。
- Hashtable不允许 null 值(key 和 value 都不可以),HashMap允许 null 值(key和value都可以)。
- 两者的遍历方式大同小异,Hashtable仅仅比HashMap多一个elements方法。
Hashtable 和 HashMap 都能通过values()方法返回一个 Collection ,然后进行遍历处理。 - 两者也都可以通过 entrySet() 方法返回一个 Set , 然后进行遍历处理。
- HashTable使用Enumeration,HashMap使用Iterator。
- 哈希值的使用不同,Hashtable直接使用对象的hashCode。而HashMap重新计算hash值,而且用于代替求模。
- Hashtable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
- HashTable基于Dictionary类,而HashMap基于AbstractMap类
2.Hash Map中的key可以是任何对象或数据类型吗
- 可以为null,但不能是可变对象,如果是可变对象的话,对象中的属性改变,则对象HashCode也进行相应的改变,导致下次无法查找到已存在Map中的数据。
- 如果可变对象在HashMap中被用作键,那就要小心在改变对象状态的时候,不要改变它的哈希值了。我们只需要保证成员变量的改变能保证该对象的哈希值不变即可。
3.HashTable是线程安全的么
- HashTable是线程安全的,其实现是在对应的方法上添加了synchronized关键字进行修饰,由于在执行此方法的时候需要获得对象锁,则执行起来比较慢。所以现在如果为了保证线程安全的话,使用CurrentHashMap。
4HashMap和Concurrent HashMap区别, Concurrent HashMap 线程安全吗, Concurrent HashMap如何保证 线程安全?
- HashMap和Concurrent HashMap区别?
- HashMap是非线程安全的,CurrentHashMap是线程安全的。
- ConcurrentHashMap将整个Hash桶进行了分段segment,也就是将这个大的数组分成了几个小的片段segment,而且每个小的片段segment上面都有锁存在,那么在插入元素的时候就需要先找到应该插入到哪一个片段segment,然后再在这个片段上面进行插入,而且这里还需要获取segment锁。
- ConcurrentHashMap让锁的粒度更精细一些,并发性能更好。
- Concurrent HashMap 线程安全吗, Concurrent HashMap如何保证 线程安全?
- HashTable容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
- get操作的高效之处在于整个get过程不需要加锁,除非读到的值是空的才会加锁重读。get方法里将要使用的共享变量都定义成volatile,如用于统计当前Segement大小的count字段和用于存储值的HashEntry的value。定义成volatile的变量,能够在线程之间保持可见性,能够被多线程同时读,并且保证不会读到过期的值,但是只能被单线程写(有一种情况可以被多线程写,就是写入的值不依赖于原值),在get操作里只需要读不需要写共享变量count和value,所以可以不用加锁。
- Put方法首先定位到Segment,然后在Segment里进行插入操作。插入操作需要经历两个步骤,第一步判断是否需要对Segment里的HashEntry数组进行扩容,第二步定位添加元素的位置然后放在HashEntry数组里。
5.因为别人知道源码怎么实现的,故意构造相同的hash的字符串进行攻击,怎么处理?那jdk7怎么办?
- 怎么处理构造相同hash的字符串进行攻击?
- 当客户端提交一个请求并附带参数的时候,web应用服务器会把我们的参数转化成一个HashMap存储,这个HashMap的逻辑结构如下:key1–>value1;
- 但是物理存储结构是不同的,key值会被转化成Hashcode,这个hashcode有会被转成数组的下标:0–>value1;
- 不同的string就会产生相同hashcode而导致碰撞,碰撞后的物理存储结构可能如下:0–>value1–>value2;
- 1、限制post和get的参数个数,越少越好
2、限制post数据包的大小
3、WAF
- Jdk7 如何处理hashcode字符串攻击
- HashMap会动态的使用一个专门的treemap实现来替换掉它。
三.Java多线程
1.Java实现多线程的方式及三种方式的区别
- 实现多线程的方式
- 继承Thread类,重写run函数
- 实现Runnable接口
- 实现Callable接口
- 三种方式的区别
- 实现Runnable接口可以避免Java单继承特性而带来的局限;增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的;适合多个相同程序代码的线程区处理同一资源的情况。
- 继承Thread类和实现Runnable方法启动线程都是使用start方法,然后JVM虚拟机将此线程放到就绪队列中,如果有处理机可用,则执行run方法。
- 实现Callable接口要实现call方法,并且线程执行完毕后会有返回值。其他的两种都是重写run方法,没有返回值。
2.线程安全
- 定义
- 某个类的行为与其规范一致。
- 不管多个线程是怎样的执行顺序和优先级,或是wait,sleep,join等控制方式,,如果一个类在多线程访问下运转一切正常,并且访问类不需要进行额外的同步处理或者协调,那么我们就认为它是线程安全的。
- 如何保证线程安全?6
- 对变量使用volitate
- 对程序段进行加锁(synchronized,lock)
- 注意
- 非线程安全的集合在多线程环境下可以使用,但并不能作为多个线程共享的属性,可以作为某个线程独享的属性。
- 例如Vector是线程安全的,ArrayList不是线程安全的。如果每一个线程中new一个ArrayList,而这个ArrayList只是在这一个线程中使用,肯定没问题。
3.多线程怎么进行信息交互
- Object中的方法,wait(), notify(),notifyAll();
4.多线程共用一个数据变量需要注意什么?
- 当我们在线程对象(Runnable)中定义了全局变量,run方法会修改该变量时,如果有多个线程同时使用该线程对象,那么就会造成全局变量的值被同时修改,造成错误.
- ThreadLocal是JDK引入的一种机制,它用于解决线程间共享变量,使用ThreadLocal声明的变量,即使在线程中属于全局变量,针对每个线程来讲,这个变量也是独立的。
- volatile变量每次被线程访问时,都强迫线程从主内存中重读该变量的最新值,而当该变量发生修改变化时,也会强迫线程将最新的值刷新回主内存中。这样一来,不同的线程都能及时的看到该变量的最新值。
5.什么是线程池?如果让你设计一个动态大小的线程池,如何设计,应该有哪些方法?
什么是线程池
- 线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。
- 设计一个动态大小的线程池,如何设计,应该有哪些方法
- 一个线程池包括以下四个基本组成部分:
线程管理器(ThreadPool):用于创建并管理线程池,包括创建线程,销毁线程池,添加新任务;
工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
任务队列(TaskQueue):用于存放没有处理的任务。提供一种缓冲机制;
- 所包含的方法
- private ThreadPool() 创建线程池
- public static ThreadPool getThreadPool() 获得一个默认线程个数的线程池
- public void execute(Runnable task) 执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器决定
- public void execute(Runnable[] task) 批量执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器决定
- public void destroy() 销毁线程池,该方法保证在所有任务都完成的情况下才销毁所有线程,否则等待任务完成才销毁
- public int getWorkThreadNumber() 返回工作线程的个数
- public int getFinishedTasknumber() 返回已完成任务的个数,这里的已完成是只出了任务队列的任务个数,可能该任务并没有实际执行完成
- public void addThread() 在保证线程池中所有线程正在执行,并且要执行线程的个数大于某一值时。增加线程池中线程的个数
- public void reduceThread() 在保证线程池中有很大一部分线程处于空闲状态,并且空闲状态的线程在小于某一值时,减少线程池中线程的个数
- 所包含的方法
6.Java是否有内存泄露和内存溢出
- 静态集合类,使用Set、Vector、HashMap等集合类的时候需要特别注意。当这些类被定义成静态的时候,由于他们的生命周期跟应用程序一样长,这时候就有可能发生内存泄漏。
例子

在上面的代码中,循环申请了Object对象,并添加到Vector中,然后设置为null,可是这些对象呗vector引用着,因此不能被GC回收,因此造成内存泄漏。因此要释放这些对象,还需要将它们从vector删除,最简单的方法就是将vector设置为null
监听器: 在Java编程中,我们都需要和监听器打交道,通常一个应用中会用到很多监听器,我们会调用一个控件,诸如addXXXListener()等方法来增加监听器,但往往在释放的时候却没有去删除这些监听器,从而增加了内存泄漏的机会。
物理连接:一些物理连接,比如数据库连接和网络连接,除非其显式的关闭了连接,否则是不会自动被GC 回收的。Java 数据库连接一般用DataSource.getConnection()来创建,当不再使用时必须用Close()方法来释放,因为这些连接是独立于JVM的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。。一般情况下,在try代码块里创建连接,在finally里释放连接,就能够避免此类内存泄漏。
内部类和外部模块等的引用:内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。在调用外部模块的时候,也应该注意防止内存泄漏,如果模块A调用了外部模块B的一个方法,如:
public void register(Object o)
这个方法有可能就使得A模块持有传入对象的引用,这时候需要查看B模块是否提供了出去引用的方法,这种情况容易忽略,而且发生内存泄漏的话,还比较难察觉。单例模式:因为单利对象初始化后将在JVM的整个生命周期内存在,如果它持有一个外部对象的(生命周期比较短)引用,那么这个外部对象就不能被回收,从而导致内存泄漏。如果这个外部对象还持有其他对象的引用,那么内存泄漏更严重。
7.concurrent包下面,都用过什么
- concurrent下面的包
- Executor 用来创建线程池,在实现Callable接口时,添加线程。
- FeatureTask 此 FutureTask 的 get 方法所返回的结果类型。
- TimeUnit
- Semaphore
- LinkedBlockingQueue
- 所用过的类
- Executor
8.volatile 关键字的如何保证内存可见性
- volatile 关键字的作用
- 保证内存的可见性
- 防止指令重排
- 注意:volatile 并不保证原子性
- 内存可见性
- volatile保证可见性的原理是在每次访问变量时都会进行一次刷新,因此每次访问都是主内存中最新的版本。
- 当且仅当满足以下所有条件时,才应该使用volatile变量
- 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。
- 该变量没有包含在具有其他变量的不变式中。
- volatile使用建议
- 在两个或者更多的线程需要访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,没必要使用volatile。
- 由于使用volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。
- volatile和synchronized区别
- volatile不会进行加锁操作:
volatile变量是一种稍弱的同步机制在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同步机制。 - volatile 变量作用类似于同步变量读写操作:
从内存可见性的角度看,写入volatile变量相当于退出同步代码块,而读取volatile变量相当于进入同步代码块。 - volatile 不如 synchronized安全:
在代码中如果过度依赖volatile变量来控制状态的可见性,通常会比使用锁的代码更脆弱,也更难以理解。仅当volatile变量能简化代码的实现以及对同步策略的验证时,才应该使用它。一般来说,用同步机制会更安全些。 - volatile 无法同时保证内存可见性和原则性:
加锁机制(即同步机制)既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性,原因是声明为volatile的简单变量如果当前值与该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:“count++”、“count = count+1”。
- volatile不会进行加锁操作:
9.sleep和wait分别是那个类的方法,有什么区别
- sleep和wait
- sleep是Thread类的方法
- wait是Object类的方法
- 有什么区别
- sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复(线程回到就绪状态)。
- wait()是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态
10.synchronized与lock的区别,使用场景。看过synchronized的源码没?
- synchronized与lock的区别
- (用法)synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。
- (用法)lock(显示锁):需要显示指定起始位置和终止位置。一般使用ReentrantLock类做为锁,多个线程中必须要使用一个ReentrantLock类做为对 象才能保证锁的生效。且在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。
- (性能)synchronized是托管给JVM执行的,而lock是java写的控制锁的代码。在Java1.5中,synchronize是性能低效的。因为 这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象,性能更高一些。但 是到了Java1.6,发生了变化。synchronize在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致 在Java1.6上synchronize的性能并不比Lock差。
- (机制)synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其 他线程只能依靠阻塞来等待线程释放锁。Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就 是CAS操作(Compare and Swap)。
11.synchronized底层如何实现的?用在代码块和方法上有什么区别?
synchronized底层如何实现的
用在代码块和方法上有什么区别?
- synchronized用在代码块锁的是调用该方法的对象(this),也可以选择锁住任何一个对象。
- synchronized用在方法上锁的是调用该方法的对象,
- synchronized用在代码块可以减小锁的粒度,从而提高并发性能。
- 无论用在代码块上还是用在方法上,都是获取对象的锁;每一个对象只有一个锁与之相关联;实现同步需要很大的系统开销作为代价,甚至可能造成死锁,所以尽量避免无谓的同步控制。
synchronized与static synchronized的区别
- synchronized是对类的当前实例进行加锁,防止其他线程同时访问该类的该实例的所有synchronized块,同一个类的两个不同实例就没有这种约束了。
- 那么static synchronized恰好就是要控制类的所有实例的访问了,static synchronized是限制线程同时访问jvm中该类的所有实例同时访问对应的代码快。
12.常见异常分为那两种(Exception,Error),常见异常的基类以及常见的异常
Throwable是java语言中所有错误和异常的超类(万物即可抛)。它有两个子类:Error、Exception。
异常种类
- Error:Error为错误,是程序无法处理的,如OutOfMemoryError、ThreadDeath等,出现这种情况你唯一能做的就是听之任之,交由JVM来处理,不过JVM在大多数情况下会选择终止线程。
- Exception:Exception是程序可以处理的异常。它又分为两种CheckedException(受捡异常),一种是UncheckedException(不受检异常)。
CheckException发生在编译阶段,必须要使用try…catch(或者throws)否则编译不通过。
UncheckedException发生在运行期,具有不确定性,主要是由于程序的逻辑问题所引起的,难以排查,我们一般都需要纵观全局才能够发现这类的异常错误,所以在程序设计中我们需要认真考虑,好好写代码,尽量处理异常,即使产生了异常,也能尽量保证程序朝着有利方向发展。
常见异常的基类
- IOException
- RuntimeException
常见的异常

13.Java中的NIO,BIO,AIO分别是什么?
- BIO
- 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
- BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
- NIO
- 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
- NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
- AIO
- 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理.
- AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
四.Jvm相关东西
1.JVM垃圾处理方法(标记清除、复制、标记整理)
- 标记-清除算法
- 标记阶段:先通过根节点,标记所有从根节点开始的对象,未被标记的为垃圾对象
- 清除阶段:清除所有未被标记的对象
- 复制算法
- 将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,然后清除正在使用的内存块中的所有对象。
- 标记-整理
- 标记阶段:先通过根节点,标记所有从根节点开始的可达对象,为被标记的为垃圾对象
- 整理阶段:将所有的存活对象压缩到内存的一段,之后清理边界所有的空间
- 三种算法的比较
- 效率:复制算法 > 标记/整理算法 > 标记/清除算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此)。
- 内存整齐度:复制算法=标记/整理算法>标记/清除算法。
- 内存利用率:标记/整理算法=标记/清除算法>复制算法。
2.JVM如何GC,新生代,老年代,持久代,都存储哪些东西,以及各个区的作用?
- 新生代
- 在方法中去new一个对象,那这方法调用完毕后,对象就会被回收,这就是一个典型的新生代对象。
- 老年代
- 在新生代中经历了N次垃圾回收后仍然存活的对象就会被放到老年代中。而且大对象直接进入老年代
- 当Survivor空间不够用时,需要依赖于老年代进行分配担保,所以大对象直接进入老年代
- 永久代
- 即方法区。
3.GC用的引用可达性分析算法中,哪些对象可作为GC Roots对象?
Java虚拟机栈中的对象
方法区中的静态成员
方法区中的常量引用对象
本地方法区中的JNI(Native方法)引用对象。
4.什么时候进行MinGC,FullGC
- MinGC
- 新生代中的垃圾收集动作,采用的是复制算法
- 对于较大的对象,在Minor GC的时候可以直接进入老年代
- FullGC
- Full GC是发生在老年代的垃圾收集动作,采用的是标记-清除/整理算法。
- 由于老年代的对象几乎都是在Survivor区熬过来的,不会那么容易死掉。因此Full GC发生的次数不会有Minor GC那么频繁,并且Time(Full GC)>Time(Minor GC)
5.各个垃圾收集器是怎么工作的
- Serial收集器
- 是一个单线程的收集器,不是只能使用一个CPU。在进行垃圾收集时,必须暂停其他所有的工作线程,直到收集结束。
- 新生代采用复制算法,Stop-The-World
- 老年代采用标记-整理算法,Stop-The-World
- 简单高效,Client模式下默认的新生代收集器

- ParNew收集器
- ParNew收集器是Serial收集器的多线程版本
- 新生代采用复制算法,Stop-The-World
- 老年代采用标记-整理算法,Stop-The-World
- 它是运行在Server模式下首选新生代收集器
- 除了Serial收集器之外,只有它能和CMS收集器配合工作

- ParNew Scanvenge收集器
- 类似ParNew,但更加关注吞吐量。目标是:达到一个可控制吞吐量的收集器。
- 停顿时间和吞吐量不可能同时调优。我们一方面希望停顿时间少,另外一方面希望吞吐量高,其实这是矛盾的。因为:在GC的时候,垃圾回收的工作总量是不变的,如果将停顿时间减少,那频率就会提高;既然频率提高了,说明就会频繁的进行GC,那吞吐量就会减少,性能就会降低。
- G1收集器
- 是当今收集器发展的最前言成果之一,对垃圾回收进行了划分优先级的操作,这种有优先级的区域回收方式保证了它的高效率
- 最大的优点是结合了空间整合,不会产生大量的碎片,也降低了进行gc的频率
- 让使用者明确指定指定停顿时间
- CMS收集器:(Concurrent Mark Sweep:并发标记清除老年代收集器)
- 一种以获得最短回收停顿时间为目标的收集器,适用于互联网站或者B/S系统的服务器上
- 初始标记(Stop-The-World):根可以直接关联到的对象
- 并发标记(和用户线程一起):主要标记过程,标记全部对象
- 重新标记(Stop-The-World):由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正
- 并发清除(和用户线程一起):基于标记结果,直接清理对象
- 并发收集,低停顿

6.Java虚拟机内存的划分,每个区域的功能
- 程序计数器(线程私有)
- 线程创建时创建,执行本地方法时其值为undefined。
- 虚拟机栈(线程私有)
*(栈内存)为虚拟机执行java方法服务:方法被调用时创建栈帧–>局部变量表->局部变量、对象引用- 如果线程请求的栈深度超出了虚拟机所允许的深度,就会出现StackOverFlowError。-Xss规定了栈的最大空间
- 虚拟机栈可以动态扩展,如果扩展到无法申请到足够的内存,会出现OOM
- 本地方法栈(线程私有)
- java虚拟机栈是为虚拟机执行java方法服务的,而本地方法栈则为虚拟机执使用到的Native方法服务。
- Java虚拟机没有对本地方法栈的使用和数据结构做强制规定。Sun HotSpot把Java虚拟机栈和本地方法栈合二为一
- 会抛出StackOverFlowError和OutOfMemoryError
- Java堆
- 被所有线程共享,在Java虚拟机启动时创建,几乎所有的对象实例都存放到堆中
- GC的管理的主要区域
- 物理不连续,逻辑上连续,并可以动态扩展,无法扩展时抛出OutOfMemoryError
- 方法区
- 用于存储已被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码的数据
- Sun HotSpot 虚拟机把方法区叫做永久待(Permanent Generation)
- 运行时常量池
- 受到方法区的限制,抛出OutOfMemoryError
7.用什么工具可以查出内存泄漏
- MemoryAnalyzer:一个功能丰富的 JAVA 堆转储文件分析工具,可以帮助你发现内存漏洞和减少内存消耗
- EclipseMAT:是一款开源的JAVA内存分析软件,查找内存泄漏,能容易找到大块内存并验证谁在一直占用它,它是基于Eclipse RCP(Rich Client Platform),可以下载RCP的独立版本或者Eclipse的插件
- JProbe:分析Java的内存泄漏。
8.JVM如何加载一个类的过程,双亲委派模型中有哪些方法有没有可能父类加载器和子类加载器,加载同一个类?如果加载同一个类,该使用哪一个类?
双亲委派机制图

双亲委派概念
- 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载。
加载器
- 启动(Bootstrap)类加载器:是用本地代码实现的类装入器,它负责将
/lib下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。 - 标准扩展(Extension)类加载器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将< Java_Runtime_Home >/lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
- 系统(System)类加载器:由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。除了以上列举的三种类加载器,还有一种比较特殊的类型 — 线程上下文类加载器。
- 启动(Bootstrap)类加载器:是用本地代码实现的类装入器,它负责将
如果加载同一个类,该使用哪一个类?
- 父类的
9.JVM线程死锁,你该如何判断是因为什么?如果用VisualVM,dump线程信息出来,会有哪些信息
- 常常需要在隔两分钟后再次收集一次thread dump,如果得到的输出相同,仍然是大量thread都在等待给同一个地址上锁,那么肯定是死锁了。
10.java是如何进行对象实例化的
11.Student s = new Student();在内存中做了哪些事情?
- 加载Student.class文件进内存
- 在栈内存为s开辟空间
- 在堆内存为学生对象开辟空间
- 对学生对象的成员变量进行默认初始化
- 对学生对象的成员变量进行显示初始化
- 通过构造方法对学生对象的成员变量赋值
- 学生对象初始化完毕,把对象地址赋值给s变量
12.用什么工具调试程序?JConsole,用过吗?
JConsole 中,您将能够监视 JVM 内存的使用情况、线程堆栈跟踪、已装入的类和 VM 信息以及 CE MBean。
13.了解过JVM调优没,基本思路是什么
五.JSP&Servlet
1.Servlet的生存周期
- Servlet接口定义了5个方法,其中前三个方法与Servlet生命周期相关:
- void init(ServletConfig config) throws ServletException
- void service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOException
- void destory()
- java.lang.String getServletInfo()
- ServletConfig getServletConfig()
- Web容器加载Servlet并将其实例化后,Servlet生命周期开始,容器运行其init()方法进行Servlet的初始化;请求到达时调用Servlet的service()方法,service()方法会根据需要调用与请求对应的doGet或doPost等方法;当服务器关闭或项目被卸载时服务器会将Servlet实例销毁,此时会调用Servlet的destroy()方法。
2.Jsp和Servlet的区别
- Servlet是一个特殊的Java程序,它运行于服务器的JVM中,能够依靠服务器的支持向浏览器提供显示内容。JSP本质上是Servlet的一种简易形式,JSP会被服务器处理成一个类似于Servlet的Java程序,可以简化页面内容的生成。Servlet和JSP最主要的不同点在于,Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML分离开来。而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。有人说,Servlet就是在Java中写HTML,而JSP就是在HTML中写Java代码,当然这个说法是很片面且不够准确的。JSP侧重于视图,Servlet更侧重于控制逻辑,在MVC架构模式中,JSP适合充当视图(view)而Servlet适合充当控制器
3.保存会话状态,有哪些方式、区别如何
- 由于HTTP协议本身是无状态的,服务器为了区分不同的用户,就需要对用户会话进行跟踪,简单的说就是为用户进行登记,为用户分配唯一的ID,下一次用户在请求中包含此ID,服务器据此判断到底是哪一个用户。
- ①URL 重写:在URL中添加用户会话的信息作为请求的参数,或者将唯一的会话ID添加到URL结尾以标识一个会话。
- ②设置表单隐藏域:将和会话跟踪相关的字段添加到隐式表单域中,这些信息不会在浏览器中显示但是提交表单时会提交给服务器。
这两种方式很难处理跨越多个页面的信息传递,因为如果每次都要修改URL或在页面中添加隐式表单域来存储用户会话相关信息,事情将变得非常麻烦。 - ③补充:HTML5中可以使用Web Storage技术通过JavaScript来保存数据,例如可以使用localStorage和sessionStorage来保存用户会话的信息,也能够实现会话跟踪。
4.cookie和session的区别
- session 在服务器端,cookie 在客户端(浏览器)
- session 的运行依赖 session id,而 session id 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie ,同时 session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 session_id)
- session 可以放在 文件、数据库、或内存中都可以。
- 用户验证这种场合一般会用 session
- cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗 考虑到安全应当使用session。
- session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用COOKIE。
- 单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
六.Spring&Hibernate
1.Spring IOC、AOP的理解以及实现的原理
- Spring IOC
- IoC叫控制反转,是Inversion of Control的缩写,DI(Dependency Injection)叫依赖注入,是对IoC更简单的诠释。控制反转是把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的”控制反转”就是对组件对象控制权的转移,从程序代码本身转移到了外部容器,由容器来创建对象并管理对象之间的依赖关系。DI是对IoC更准确的描述,即组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态的将某种依赖关系注入到组件之中。
举个例子:一个类A需要用到接口B中的方法,那么就需要为类A和接口B建立关联或依赖关系,最原始的方法是在类A中创建一个接口B的实现类C的实例,但这种方法需要开发人员自行维护二者的依赖关系,也就是说当依赖关系发生变动的时候需要修改代码并重新构建整个系统。如果通过一个容器来管理这些对象以及对象的依赖关系,则只需要在类A中定义好用于关联接口B的方法(构造器或setter方法),将类A和接口B的实现类C放入容器中,通过对容器的配置来实现二者的关联。
- IoC叫控制反转,是Inversion of Control的缩写,DI(Dependency Injection)叫依赖注入,是对IoC更简单的诠释。控制反转是把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的”控制反转”就是对组件对象控制权的转移,从程序代码本身转移到了外部容器,由容器来创建对象并管理对象之间的依赖关系。DI是对IoC更准确的描述,即组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态的将某种依赖关系注入到组件之中。
- Spring IOC实现原理
- 通过反射创建实例;
- 获取需要注入的接口实现类并将其赋值给该接口。
- Spring AOP
- AOP(Aspect-Oriented Programming)指一种程序设计范型,该范型以一种称为切面(aspect)的语言构造为基础,切面是一种新的模块化机制,用来描述分散在对象、类或方法中的横切关注点(crosscutting concern)。
- “横切关注”是会影响到整个应用程序的关注功能,它跟正常的业务逻辑是正交的,没有必然的联系,但是几乎所有的业务逻辑都会涉及到这些关注功能。通常,事务、日志、安全性等关注就是应用中的横切关注功能。
- Spring AOP实现原理
- 动态代理(利用反射和动态编译将代理模式变成动态的)
- JDK的动态代理
- JDKProxy返回动态代理类,是目标类所实现接口的另一个实现版本,它实现了对目标类的代理(如同UserDAOProxy与UserDAOImp的关系)
- cglib动态代理
- CGLibProxy返回的动态代理类,则是目标代理类的一个子类(代理类扩展了UserDaoImpl类)
2.Ioc容器的加载过程
- 创建IOC配置文件的抽象资源
- 创建一个BeanFactory
- 把读取配置信息的BeanDefinitionReader,这里是XmlBeanDefinitionReader配置给BeanFactory
- 从定义好的资源位置读入配置信息,具体的解析过程由XmlBeanDefinitionReader来完成,这样完成整个载入bean定义的过程。
3.动态代理与cglib实现的区别
- JDK动态代理只能对实现了接口的类生成代理,而不能针对类.
- CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法因为是继承,所以该类或方法最好不要声明成final。
- JDK代理是不需要以来第三方的库,只要JDK环境就可以进行代理
- CGLib 必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承
4.代理的实现原理呗
5.HIbernate一级缓存与二级缓存之间的区别
- Hibernate的Session提供了一级缓存的功能,默认总是有效的,当应用程序保存持久化实体、修改持久化实体时,Session并不会立即把这种改变提交到数据库,而是缓存在当前的Session中,除非显示调用了Session的flush()方法或通过close()方法关闭Session。通过一级缓存,可以减少程序与数据库的交互,从而提高数据库访问性能。
- SessionFactory级别的二级缓存是全局性的,所有的Session可以共享这个二级缓存。不过二级缓存默认是关闭的,需要显示开启并指定需要使用哪种二级缓存实现类(可以使用第三方提供的实现)。一旦开启了二级缓存并设置了需要使用二级缓存的实体类,SessionFactory就会缓存访问过的该实体类的每个对象,除非缓存的数据超出了指定的缓存空间。
- 一级缓存和二级缓存都是对整个实体进行缓存,不会缓存普通属性,如果希望对普通属性进行缓存,可以使用查询缓存。查询缓存是将HQL或SQL语句以及它们的查询结果作为键值对进行缓存,对于同样的查询可以直接从缓存中获取数据。查询缓存默认也是关闭的,需要显示开启
6.Spring MVC的原理
Spring MVC的工作原理如下图所示:

- ① 客户端的所有请求都交给前端控制器DispatcherServlet来处理,它会负责调用系统的其他模块来真正处理用户的请求。
- ② DispatcherServlet收到请求后,将根据请求的信息(包括URL、HTTP协议方法、请求头、请求参数、Cookie等)以及HandlerMapping的配置找到处理该请求的Handler(任何一个对象都可以作为请求的Handler)。
- ③在这个地方Spring会通过HandlerAdapter对该处理进行封装。
- ④ HandlerAdapter是一个适配器,它用统一的接口对各种Handler中的方法进行调用。
- ⑤ Handler完成对用户请求的处理后,会返回一个ModelAndView对象给DispatcherServlet,ModelAndView顾名思义,包含了数据模型以及相应的视图的信息。
- ⑥ ModelAndView的视图是逻辑视图,DispatcherServlet还要借助ViewResolver完成从逻辑视图到真实视图对象的解析工作。
- ⑦ 当得到真正的视图对象后,DispatcherServlet会利用视图对象对模型数据进行渲染。
- ⑧ 客户端得到响应,可能是一个普通的HTML页面,也可以是XML或JSON字符串,还可以是一张图片或者一个PDF文件。
7.简述Hibernate常见优化策略
- 制定合理的缓存策略(二级缓存、查询缓存)。
- 采用合理的Session管理机制。
- 尽量使用延迟加载特性。
- 设定合理的批处理参数。
- 如果可以,选用UUID作为主键生成器。
- 如果可以,选用基于版本号的乐观锁替代悲观锁。
- 在开发过程中, 开启hibernate.show_sql选项查看生成的SQL,从而了解底层的状况;开发完成后关闭此选项。
- 考虑数据库本身的优化,合理的索引、恰当的数据分区策略等都会对持久层的性能带来可观的提升,但这些需要专业的DBA(数据库管理员)提供支持。
七.设计模式
1.所了解的设计模式,单例模式的注意事项,jdk源码哪些用到了你说的设计模式
所了解的设计模式
- 工厂模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类, Factory Method 使一个类的实例化延迟到了子类。
- 单例模式:保证一个类只有一个实例,并提供一个访问它的全局访问点;
- 适配器模式:将一类的接口转换成客户希望的另外一个接口,Adapter 模式使得原本由于接口不兼容而不能一起工作那些类可以一起工作。
- 装饰者模式:动态地给一个对象增加一些额外的职责,就增加的功能来说, Decorator 模式相比生成子类更加灵活。
- 代理:为其他对象提供一种代理以控制对这个对象的访问
- 迭代器模式:提供一个方法顺序访问一个聚合对象的各个元素,而又不需要暴露该对象的内部表示。
单例模式的注意事项
- 尽量使用懒加载
- 双重检索实现线程安全
- 构造方法为private
- 定义静态的Singleton instance对象和getInstance()方法
jdk源码中用到的设计模式
- 装饰器模式:IO流中
- 迭代器模式:Iterator
- 单利模式: java.lang.Runtime
- 代理模式:RMI
八.其他
1.常用的hash算法有哪些?
- 加法Hash;把输入元素一个一个的加起来构成最后的结果
- 位运算Hash;这类型Hash函数通过利用各种位运算(常见的是移位和异或)来充分的混合输入元素
- 乘法Hash;这种类型的Hash函数利用了乘法的不相关性(乘法的这种性质,最有名的莫过于平方取头尾的随机数生成算法,虽然这种算法效果并不好);jdk5.0里面的String类的hashCode()方法也使用乘法Hash;32位FNV算法
- 除法Hash;除法和乘法一样,同样具有表面上看起来的不相关性。不过,因为除法太慢,这种方式几乎找不到真正的应用
- 查表Hash;查表Hash最有名的例子莫过于CRC系列算法。虽然CRC系列算法本身并不是查表,但是,查表是它的一种最快的实现方式。查表Hash中有名的例子有:Universal Hashing和Zobrist Hashing。他们的表格都是随机生成的。
- 混合Hash;混合Hash算法利用了以上各种方式。各种常见的Hash算法,比如MD5、Tiger都属于这个范围。它们一般很少在面向查找的Hash函数里面使用
2.如何设计存储海量数据的存储系统
3.缓存的实现原理,设计缓存要注意什么
4.什么是一致性哈希?用来解决什么问题?
在设计分布式cache系统的时候,我们需要让key的分布均衡,并且在增加cache server后,cache的迁移做到最少。
5.现在有一个进程挂起了,如何用工具查出原因?
- 通过 Javacore 了解线程运行状况
javacore,也可以称为“threaddump”或是“javadump”,它是 Java 提供的一种诊断特性,能够提供一份可读的当前运行的 JVM 中线程使用情况的快照。即在某个特定时刻,JVM 中有哪些线程在运行,每个线程执行到哪一个类,哪一个方法。
应用程序如果出现不可恢复的错误或是内存泄露,就会自动触发 Javacore 的生成。而为了性能问题诊断的需要,我们也会主动触发生成 Javacore。在 AIX、Linux、Solaris 环境中,我们通常使用 kill -3产生该进程的 Javacore。IBM Java6 中产生 Javacore 的详细方法可以参考文章 [1]。
6.你知道的开源协议有哪些?
- Mozilla Public License: MPL License,允许免费重发布、免费修改,但要求修改后的代码版权归软件的发起者。这种授权维护了商业软件的利益,它要求基于这种软件得修改无偿贡献版权给该软件。这样,围绕该软件得所有代码得版权都集中在发起开发人得手中。但MPL是允许修改,无偿使用得。MPL软件对链接没有要求。
- BSD开源协议:给于使用者很大自由的协议。可以自由的使用,修改源代码,也可以将修改后的代码作为开源或者专有软件再发布。
- Apache Licence 2.0 :Apache Licence是著名的非盈利开源组织Apache采用的协议。该协议和BSD类似,同样鼓励代码共享和尊重原作者的著作权,同样允许代码修改,再发布(作为开源或商业软件)。
- GPL:GPL许可证是自由软件的应用最广泛的软件许可证,人们可以修改程式的一个或几个副本或程式的任何部分,以此形成基於这些程式的衍生作品。必须在修改过的档案中附有明显的说明:您修改了此一档案及任何修改的日期。 您必须让您发布或出版的作品,包括本程式的全部或一部分,或内含本程式的全部或部分所衍生的作品,允许第三方在此许可证条款下使用,并且不得因为此项授权行为而收费。
- LGPL:LGPL是GPL的一个为主要为类库使用设计的开源协议。和GPL要求任何使用/修改/衍生之GPL类库的的软件必须采用GPL协议不同。LGPL允许商业软件通过类库引用(link)方式使用LGPL类库而不需要开源商业软件的代码。这使得采用LGPL协议的开源代码可以被商业软件作为类库引用并发布和销售。
- Public Domain:公共域授权。将软件授权为公共域,这些软件包没有授权协议,任何人都可以随意使用它
7.你知道的开源软件有哪些?
- JDK
- Eclipse
- Tomcat
- Spring
- Hibernate
- MySQL
- 本文作者: th3ee9ine
- 本文链接: https://www.blog.ajie39.top/2021/05/05/Java基础复习/
- 版权声明: 本博客所有文章除特别声明外,均采用 LICENSE 下的许可协议。转载请注明出处!