java面试的知识点和个人回答

下面的内容都是传我本身的理解, 其中确定有不少的错误之处, 欢迎指正。java

java基础知识

Java 的基本语法都要了解,并发编程、NIO、JVM 等,你多少要有点儿了解,Spring、Netty 这些框架也要了解。
String类为什么要设计成final不可变的?
答:String类是java中最经常使用的类, 而且String能够表示任何的数据。 因此java对于String类作了还多的优化,其中之一就是不可变, String 的 intern() 的方法会把字符串添加到常量池中。 new的方式会新生成常量池, 一艘字符串不建议使用new的方式, 建议直接使用引号。 new String 的方式会会生成一个常量, 同时堆中会生成字符串对象而且持有这个常量的引用linux

不可变的优势:

设计成不可变能够简单的防止并发问题, 由于不可变就能够简单的避免线程的问题。 每一个线程只能可读,天然就能够避免并发问题
设计成不可变的, 就能够进行字符串的缓存共享(字符串池),共享能够大量节约缓存。
不可变的设计能够放心的做为Map等key值web

缺点:

因为现实中老是有字符串改变的需求, 而且咱们日常使用中也老是会用到字符串拼接等操做,
因为不可变性, 须要实现字符串的拼接就须要生成新的字符串常量, 致使字符串常量池很容易膨胀
每次字符串的拼接都须要生成新的字符串常量, 生成对象很耗性能问题, 因此java引入了StringBuffer和StringBuilder
字符串的内部维护了char[] 数组,一样的字符在不一样的编码格式中是不同的,若是进行格式转换,须要把字符转成byte数组在转换成目标字符 。 ps: java11 中String的内部已经改为了byte[]数组, 若是进行编码转换就能够少一步操做算法

java中==和equals和hashCode的区别

== 比较的就是对象在内存中存储位置,若是是统一对象,天然内存地址信息是同样的。
equals 在Object中是native实现, 本质仍是比较内存地址。 由于java的每一个类都是继承自Object类,因此默认也是比较内存地址
hashCode也是native方法, 是根据内存中的内容来计算的hash值,因此不一样的对象的内存中值确定是不一样的,因此hash值大几率不会相同。sql

总结:若是须要肯定两个引用指向的是同一个内存中的区域, 使用== 就能够, 使用默认的equals也行。 若是不用须要判断两个对象中的业务上的相等就须要重写equals和hashCode方法。 而且Map和Set肯定重复的依据也是根据equal和hashCode两个方法, 不少的经常使用类都重写了这两个方法, 好比String 和基本类型的包装方法数据库

int、char、short long各占多少字节数

int 4字节, short2字节, long8字节, char的字节数和编码有关, gbk都是两个字节, utf8字节数在1到3直接, 通常英文1个字节, 中文2到3个字节编程

int与integer的区别

int 是基本变量, 直接分配在栈上, integer是其包装类, 内部成员变量是int类型,内存分配在堆上。json

对java多态的理解

java的多态体如今 接口, 继承, 虚拟类方面。
java 中子类和能够覆盖父类的方法
java中支持一样类名的重载,jdk会根据调用传入的参数动态选用正常的方法
可使用父类或者接口来引用子类的实现, 能够传入不一样的实现子类
可是因为java仍是不支持多继承, 而且严格的类型限制, 致使java的多态性仍是和脚本语言仍是有很大差距, 因此jdk推出了基于接口的动态代理, 而且诞生了不少的字节码框架ASM和Javssent等bootstrap

String、StringBuffer、StringBuilder区别

String不可变, StringBuffer、StringBuilder是可变的,
String 和StringBuffer 是线程安全的, StringBuilder不是设计模式

什么是内部类?内部类的做用

java中将一个类定义在另一个类的内部, 咱们称内部类。 其实内部类在编译成的class仍是两个文件。 经常使用内部类分四种: 成员内部类、局部内部类、匿名内部类和静态内部类

成员内部类:

能够很方便的访问外部类的成员包括静态成员, 不受private的限制。
若是存在和外部类同名变量, 默认访问的是内部类的变量, 访问外部类须要使用 外部类名.this.变量名的方式
外部类访问内部类,必须经过对象的方式。 外部类能够访问内部类的private成员
内部类不能单独存在, 必须依附于外部类的对象

局部内部类: 在外部类的方法中定义的类

这个类只在这个方法做用域内有用, 超出方法范围无妨访问,
在方法做用域内的访问特性和成员内部类同样

匿名内部类:

使用不少, 特别是在一些监听的处理类的设计上。
匿名内部类没有构造方法
匿名内部类不能扩展方法, 只能覆盖接口中的方法

静态内部类:

是最接近正常类的内部类, 静态内部类不用依赖外部类的对象, 可是依赖外部类的类。 因为他是属于类的内部类, 因此不能访问外部类对象的成员
内部类为什么可以访问外部类的成员。
其实内部类编译后都有独立的class文件, 能够反编译看到实际上内部类都在构造参数中传入了外部类的引用, 内部类就是经过这个应用访问外部类的。

问题: 为什么局部和匿名内部类只能访问外部的final变量?

成员内部类传入的是外部对象的引用, 成员内部类又不能脱离外部类存在,因此对于内部类来讲就是外部类就是不可变, 因此不管外部类怎么变,被传入的引用都是有效的指向外部对象的,
静态内部类传入的是class的引用, class类是不可变的, 也没有问题
可是局部内部类,方法内部持有的变量也是引用, 若是传入进去后,变量有改变了(引用变量),就会致使内外部是不一样的变量,没法实现同步, 传入的引用就无心义, 不如规定只能传入不可变的引用,这样外部和内部就能保证持有的是同一个对象
匿名内部类同理

优势

能够很方便的把关联的逻辑的类封装在一块儿,从而实现对外部不可见
对于时间驱动的逻辑编写很方便
能够很方便的编写多线程代码, 能够必定的程度的避免并发问题
内部类能够继承接口, 而且能够和外部类互相访问, 因此能够和外部类实现一样的接口, 至关于变相的实现java的多继承机制

抽象类和接口区别,抽象类的意义,抽象类与接口的应用场景,抽象类是否能够没有方法和属性,接口的意义?

  • 抽象类:

抽象类不能被实例化, 只能被继承
抽象类中能够没有抽象方法, 可是包含抽象方法必须的必须是抽象类

  • 接口:

在1.8以前接口中只能包含未实现的方法, 1.8以后能够有默认方法
接口能够实现接口

二者异同:

  • 都不能实例化,只能继承

  • 抽象类能够有正常的方法和成员变量, 可是接口中只能包含未实现方法和默认方法,不能包含成员变量和正常方法。 接口中成员变量都是默认的static的。

  • 接口的方法都是默认的public的, 不能定义private和protected

抽象类和接口意义:

接口是更像一种约束,约束实现类的实现方法的签名和参数。 因此子类能够实现多个接口,
子类只能实现一个虚拟类, 虚拟类更像一个一个半成品的类。
对于不一样的子类的相同的实现逻辑, 不用每一个子类都实现一遍, 能够提取到虚拟父类实现, 对于子类的不一样点能够电仪抽象方法来知足。
抽象类和接口均可以没有成员变量和方法:
抽象类和接口的引用场景:
接口就是一种约定, 好比dubbo的消费者和服务者是使用的一样的接口, 其实就是约定好两方使用相同的方法签名,这样就能够正确的通讯
对于设计模式中的模板方法模式,使用抽象方法就方便实现
对于不一样子类的共同逻辑能够放到抽象父类

泛型中extends和super的区别?

List<? super Fruit> 规定只能放入Fruit类和其父类
List<? extends Fruit> 规定只能放入Fruit类和其子类类

父类的静态方法可否被子类重写? 静态属性和静态方法是否能够被继承?是否能够被重写?以及缘由?
不能, 静态方法是属于类的, 父类和子类始终是不一样的两个类, 不能覆盖

进程和线程的区别?

进程是操做系统级别的, 每一个进程有独立的内存区间
线程是进程下级的, 线程是共享进程的内存空间的, 因此多线程须要考虑并发问题

final,finally,finalize的区别

finalize是在对象回收的时候会触发调用的

序列化的方式,Serializable 和Parcelable 的区别?

java自带了序列化的方式, 要序列化的类必须实现Serializable接口, java的序列化由于须要序列化类的信息和关联类的信息,效率并不高, 因此经常使用的都是第三方的序列化方式, 好比通用的json 和高效率的Protocol

静态内部类的设计意图

每一个内部类的都有的意义, 就是能够把关联的逻辑封装在一块儿, 不用在单独的写类

谈谈对kotlin的理解

kotlin 是jvm上的另外一种语言, 他的实现本质还编译成class 文件, 因此能够和java互通。 因为java的严谨的特色和必须兼容之前的版本的问题,java在更新方面老是显得很谨慎。
kotlin没有历史包裹, 在设计上实现前卫大胆, 有不少的实用特性

String 转换成 integer的方式及原理

String可以转成数字的前提, 是String中必须都是数字类型的字符, 转换成int就是逐个字符解析

哪些状况下的对象会被垃圾回收机制处理掉?

没有引用的对象会被回收
没有实例的类和加载器也被回收的了, 这个类会被回收
若是常量池中常量不存在引用了,在内存不够的时候会被回收

静态代理和动态代理的区别,什么场景使用?

静态代理:
就是定义一个代理类,实现和被代理类同样的方法(接口), 代理类中持有被代理类引用,可是代理类中的逻辑都是调用被代理类来实现的。 门面模式中就是使用静态代理来很方便的实现
缺点: 每一个代理类只能为一个代理类服务, 没法代理接口的全部实现类

动态代理:
jdk经过反射的方式能够代理接口的全部实现了,
优势: 能够代理一系列的接口实现类, 不会再限制在一个类上。

场景:
若是我须要修改某个接口的全部实现类的子类方法的逻辑,若是每一个子类都改, 会很麻烦, 能够经过动态代理的方式生成对象, 而不用改源码
对于第三方的jar类, 咱们无权限修改, 就可使用代理方式来改变其逻辑
因为jdk的动态代理仍是有借口的限制, 有一些场景仍是不灵活。 因此诞生了修改字节码来实现代理的框架,有Cglib 和ASM等

Java的异常体系

Java把异常做为一种类,当作对象来处理。全部异常类的基类是Throwable类,两大子类分别是Error和Exception
Error 是程序运行抛出的不可能经过程序本身解决的异常, 好比内存溢出等
Exception 表示的是程序运行中的异常, 能够程序本身来捕捉处理。 正确的捕捉处理异常,能够有效的提升程序的健壮性
Exception 能够分为运行时异常和编译器异常
运行时异常RuntimeException: 在程序运行中才会出现的, 程序运行以前并不清楚。
编译器异常: 好比ClassNotFundException , 在编译器就会报出来的异常, 若是不处理会编译不经过, 因此能跑起来的程序都没有编译器异常。

关于异常能够参考 :

Java经常使用集合类

ConcurrentHashMap的实现原理?

HashMap在多线程的状况下会有线程安全的问题。 因此java就提供了HashTable和ConcurrentHashMap两个Map实现类来保证线程安全。 HashTable只是简单的给每一个方法都加锁来保证线程安全, 可是这种简单的加锁方式有性能问题,因此仍是不推荐使用。 ConcurrentHashMap对于HashTable的性能问题进行了优化。 使用分段加锁的机制, 它将内部的数据分红数段,每段都是用独立的锁, 这样若是多线程分别读取map中不一样段的数据,他们获取的是不一样段的锁,这样就不存在锁的竞争问题,只有不一样线程同时获取同一段的数据才会有竞争问题。

分段锁 是一种分而治之的思想, Map中虽然保存有不少的数据,可是每一个线程大几率都只是访问其中一部分的数据,若是加上全局锁,锁上全部的数据,明显会有性能问题。

HashMap ?

内部维护了一个数组。 这个数组存放的是Entity对象
每次存入key-value的时候, 实际是构建一个Entity对象, 而后根据key的Hash值放入到数组的对应位置
若是存入k-v的构建的Entity的时候,若是发现数组的对应位置已经有值了, 就会构建一个链表,这个链表的头部就是数组中这个位置的Entity对象, 后加入的对象会自动加在链表的尾部
链表长度到达了6 后, 这个链表就会变成红黑树, 主要由于链表的遍历性能低下,链表长度长了后用性能更好的红黑树
根据K获取值的时候,根据K的hashcode肯定数组位置, 再遍历链表找到key同样的Entity。
HashMap能够容许key值为null, 可是只容许1个。hastTable不运行null的key
当容量到达0.75的时候, 会进行扩容, 扩容就是内部数组替换成一个更大的。
扩容比例是翻倍扩容

LinkHashMap

继承自HashMap,使用方式几乎和HashMap同样。 放入的数据是有序的, 能够根据放入顺序遍历
实现原理,是内部比HashMap多维护了一套链表。 链表就记录了放入的顺序,因此能够有序遍历

HashSet

内部实现几乎和HashMap同样, 只是它屏蔽了Map中的value值, 每次存入的Entity对象的value值都是固定的。
这个集合类常常做为去重工具处理,若是大量的数据去重,仍是建议使用布隆过滤器
由于是根据HashCode来肯定数组的位置的,因此内部的数据是没有特定顺序的

TreeSet

内部维护了二叉树的结构, 保证存入的数据是有序的。
根据顺序就能够很方便的判断重复性了
存入的对象必须实现Comparable接口, 内部排序就是根据这个接口来判断的
总结: 因为二叉树的特性, 在修改的时候效率会低,在查询的时候会高效, 而且数据仍是有序的。

ArrayList , Vector

内部维护了一个数组, 每次存入对象都是按顺序存入数组中
当容量不够用的时候, 就会生成一个更大的数组替换旧数组,同时会复制内部的值到新数组中。
扩容的比例是每次都新增已有容量的一半, 好比当前容量10, 扩容后变成15.
由于是数组的方式, 若是删除中间部分的数据, 数据中的删除位置后面的数据都须要前移, 性能问题。
一样由于数组的方式, 遍历十分高效
Vector 内部实现几乎和ArrayList同样, 只是在每一个方法中都加上了synchronized来保证同步

LinkList

内部维护的是链表的结构, 每次加入对象都是加入这个链表中
链表的形式,不存在扩容的问题, 容量不够直接在链表中增长就能够
删除和中间插入都十分方便, 只要改变下插入位置的链的指向就能够。
有与链表的特性, 插入删除 十分高效。 可是遍历就有性能问题了。

LinkList和ArrayList都是十分经常使用的List集合。他们的特色恰好互补, 因此平常使用中须要根据场景来选择合适的集合类型

BlockingQueue ?

线程安全的队列, 能够很方便的用户生产者—消费者模型, LinkedBlockingQueue是最经常使用的一种实现类。 内部是使用了两个锁和两个Condition来保证线程安全的。
/** Lock held by take, poll, etc / //获取对象必须获取这个锁 private final ReentrantLock takeLock = new ReentrantLock(); /* Wait queue for waiting takes / //若是容量中有值了, 会通知获取线程 private final Condition notEmpty = takeLock.newCondition(); /* Lock held by put, offer, etc / private final ReentrantLock putLock = new ReentrantLock(); /* Wait queue for waiting puts */ //容量中能够放值了,通知插入线程 private final Condition notFull = putLock.newCondition();
获取的时候,若是没有值能够阻塞住,等待其余线程放入值后, 阻塞结束自动返回值
放入的时候,若是容量已满也会阻塞住,等待其余线程消费了容量后, 阻塞结束对象放入

JVM的类加载机制

Class的载入7大阶段

  • 加载: 从文件或者其余地方将一个类的字节信息加载到内存中,
  • 验证:验证字节信息是否符合虚拟机的规范,防止格式不对的字节码信息载入到虚拟机中
  • 准备:开始在内存中准备一些静态变量区域,而且给这些变量赋初始值。 好比Int的初始值为0
  • 解析: 替换符号引用为直接引用。好比: 类中是经过类名来关联其余类的, 可是类名没法表示其余类的在内存中的位置, 这一步就是把类名替换成真正的类在内存中的应用。
  • 初始化: 在前面已经为类在内存中开辟空间了, 这一步就是把类中定义的静态值赋值进去。 这一步结束后,程序就能够正常使用类了。

类构造器: 是虚拟机根据类的内部定义自动生成的, 在初始化阶段会执行这个构造器。 在构造器中会给一些静态变量赋初值。 虚拟机会保证父构造器先于子构造器执行, 若是类中没有静态变量或者代码块。 构造器能够不用执行。

类的初始化时懒初始化的, 只有在必须初始化的时候才会初始化:
经过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。

定义对象数组,不会触发该类的初始化。

常量在编译期间会存入调用类的常量池中,本质上并无直接引用定义常量的类,不会触发定义常量所在的类。
经过类名获取 Class 对象,不会触发类的初始化。
经过 Class.forName 加载指定类时,若是指定参数 initialize 为 false 时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。

经过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动做。

类加载器

jdk自带三个类加载器
启动类加载器(Bootstrap ClassLoader):负责加载JAVA_HOME\lib目录中的,或经过-Xbootclasspath 参数指定路径中的,且被虚拟机承认(按文件名识别,如 rt.jar)的类。
扩展类加载器(Extension ClassLoader): 负责加载JAVA_HOME\lib\ext目录中的,或经过 java.ext.dirs 系统变量指定路径中的类库。
应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类库。
应用中咱们自定义的类对经过Application ClassLoader 来加载的。 java的类的加载有双亲委派的机制

双亲委派机制

当一个加载器开始加载一个类以前,会调用上级的加载器委托上级加载器来加载, 上级加载器也是一样的操做,一直到启动类加载器它没有上级了,此时启动类加载器就会判断要加载的类是不是本身能够加载的类,若是不是会将加载任务在返回下级下载器, 下级加载器也会判断下本身能够加载不, 若是能够就会加载类, 加载成功后在通知下级下载器不要忙了,类已经加载了。

双亲模型的优势:

不管什么加载器,都没法加载虚拟机的类。 这样对虚拟机是一种保护,防止应用程序破坏虚拟机的正常运行。
防止一个类被不一样的加载器重复加载

双亲模式的缺点:

Jdk中的类没法加载用户的类, 好比JDBC的DriverManager类是接口在rt.jar中, 是属于启动类加载器加载的, 可是他的java.sql.DriverManager#getConnection(String, Properties,Class<?>)方法须要加载的了是各个数据库厂商实现的jdbc的驱动类。 数据库厂商实现的类确定是须要应用程序类加载器或子加载器来加载的。

疑问: 调用A类的方法,这个方法内部又有加载B类, 那么B类的加载会使用A类同样的加载器。

DriverManager的getConnection方法会加载用户传入的驱动类, 若是按照双亲模型,应该使用启动类加载器来加载,可是启动类加载器只能加载特定目录(jdk)中的类。

解决: 线程上下文类加载器。
一个程序的启动类确定是用户类, 他的加载确定是应用类加载器, jdk会在启动的时候把这个加载器放入主线程上下文中。 主线程在建立其余线程的时候,会把这个加载器传递下去。 因此DriverManager中的源码能够看到是取得了取得了这个加载器来加载驱动类的。 这样就实现了jdk中的类加载了用户的类。

Tomcat 的类加载器

tomcat基于本身的特性, 自定义了本身的类加载器。
问: 一个tomcat能够容许多个webApp应用,他们都是在一个jvm上运行的, 那么这个多个webApp是否能够互相调用呢?
答: 不行, 每一个webApp都有本身的惟一的类加载器而且只加载本应用的目录中的类, 多个应用的类加载器是平行的, 若是A应用加载B应用的类,会委托到common类加载器,最后到bootstrap加载器,而后在原路返回。 整个过程都不会找到B类的class文件, 此时就会报ClassNotFundException .

为何Catania.lib目录中的类优先级最高, 而且里面的类会覆盖全部应用的同名类?
由于common加载器是全部webApp加载器的上级加载器, 若是webapp要加载其中的同名类,就会委派到common加载器来加载,它会加载Catania.lib目录下的同名类,天然就屏蔽了各个应用的类了, 因此优先级很高。

JVM的内存区域

jvm的内存分为线程共享的和线程私有的, 线程私有的区域: 方法栈,本地栈, 程序计数器。
线程共享的: 堆, 方法区。
Java8中取消了方法区,将常量池放入堆中,方法区的其余信息(主要一些类的信息)放入堆外空间中了

程序计数器

记录字节码中指令运行的行号,程序根据这个行号肯定下一步的指令。

虚拟机栈

栈是先进先出的单向队列的形式,方法执行的时候会建立一个栈帧, 并将栈帧放入栈中。 方法中的局部变量都会在栈中开辟空间(方法中的变量其实都是应用)。随着方法的结束,栈帧会移除栈并销毁。 若是栈中持有堆中对象的引用,这个对象不会被回收。

本地方法栈

是jdk中一些native方法运行的栈。

程序中建立的对象和数组都会存放在这里, 垃圾回收机制的主要做用区域, 为了方便垃圾回收对堆进行了分代处理和回收

方法区/永久代

存放每一个类的信息, 存放静态常量,存放字符串常量等。 这里也会被垃圾回收机制回收,可是回收的频率较小。

堆的分代

Hotspot将堆分为老年代和新生代, 新生代和老年代的大小比例默认在1:2。
Xms:堆初始初始大小 Xmx:堆的最大值 Xmn: 新生代大小

新生代:

使用复制清除的方式回收对象, 新生代的回收称为Minor GC, GC时候会暂停程序
因为新生代的存活对象少,回收效率高, 实际暂停事件不多。
若是新生代中对象持有老年代中应用, 对象不会被回收
默认分红1:8:1的三个区域, 其中两个Eden区, 一个Servivor
始终有一个Eden是空闲的。 当Servivor内存快满的时候,会将Servivor和Eden1中的存活对象复制到空闲Eden2中, 新的对象继续在Servivor中建立
单空闲Eden放不下存活的对象的时候, 会将对象转入到老年代中。
java中的大部分对象都是存活的周期很短的, 因此分红1:8:1的比例彻底没有问题。 一样因为大部分对象的存活时间不长, 使用复制清除的方法回收十分高效。
一个对象默认存活过15次回收,会转入老年代。 大对象会直接存入老年代

老年代:

存放生命周期长的对象和大对象。
当快要满的时候, 会触发MajorGC。 老年代使用标记整理的算法(不是标记清除)
老年代的回收比较耗时, 应该尽可能避免。
标记整理的算法会清理内存碎片,这样保证了老年代的内存的连续
老年代是新生代的兜底内存区域,因此通常出现OOM的都是老年代

永久代:

随着类的加载而扩大
不会进行垃圾回收, 因此易出现OOM
java8已经把方法区移除, 方法区中的字符串常量和静态常量放入堆中,其余部分放入堆外内存中

垃圾回收

Java之因此可以长盛下去, 主要有三方面缘由:
彻底的面向对象的语言
跨平台的特性
垃圾回收机制
java为了垃圾回收放弃了灵活强大而又难用的指针。 不断的优化垃圾回收算法,如今java已经摆脱运行慢的帽子了, 同时java的内存使用效率程序的健壮性也随着垃圾回收算法的完善二提升。

要进行垃圾回收就要解决两个问题,

  • 什么样的对象算垃圾,
  • 垃圾如何高效回收?

什么样的对象算垃圾?

引用计数的算法
垃圾回收中经典经典的算法, 可是因为没法处理循环引用的问题,jdk没有采用。
可达性算法:
程序中有一些对象咱们已经肯定一定不会是垃圾, 好比栈中的数据和持有的引用, 元空间中类持有的引用, 常量池中持有的引用。这些引用指向的对象确定不能是垃圾。因此栈中对象,常量池中对象能够做为GC roots对象, 沿着GC roots对象持有的引用往下遍历, 可以遍历到的对象就是可达对象, 不可达对象就是垃圾对象,GC就会回收这个垃圾。
可达性分析解决了循环引用问题。
不用维护计数器,减小了开销。
Jdk正是采用的这个算法

垃圾如何高效回收?

复制整理算法:
将内存分红两部分,每次只使用其中一部分。
回收只须要把使用的区域A中的存活对象copy到空闲区域B中, 在所有清空A区域
复制算法回收的空间是连续的, 没有碎片,而且回收简单高效
缺点是浪费空间
新生代正是使用这个算法,结合大部分对象活不久的特色,新生代把空闲区的大小比例设计成1/10 有效的减小了空间的浪费。

标记清除算法:
就是简单的把垃圾对象占用的空间回收掉,
可是垃圾对象可能在内存中各个部分零星分布的,若是直接回收会产生不少的内存碎片空间
标记整理算法: 这个是标记清除的优化版本
前期也是和标记清除算法同样回收垃圾,产生碎片
产生碎片后, 这个算法再把对象进行整理一下, 好比移动下存活的对象的位置, 使的内存连续,消灭碎片
标记整理的算法因为须要一个个碎片的整理,效率不高,可是老年代中回收的频率也不大, 使用这个算法没有问题
老年代使用的是这个算法, 因为老年代的对象存活周期长,每次也回收不了几个对象, 若是采用复制算法, 从A复制到B, 可能须要复制90%的存活对象, 效率低下。 复制对象又会浪费空间,老年代占用的空间原本就大,这样浪费的空间也会大。

常见的垃圾回收器

新生代垃圾收集器

Serial垃圾收集器: 单线程的使用复制算法的新生代垃圾, 收集的时候会暂停程序
ParNew: Serial的多线程版本
Parallel Scavenge收集器: 多线程收集器, 关注吞吐量。会根据设置的吞吐量来自动调整收集效率

老年代收集器

Serial Old收集器(单线程标记整理算法 )老年代, 可做为CMS的后备垃圾收集器, 能配合全部的新生代垃圾收集器
Parallel Old收集器(多线程标记整理算法): 只能配合新生代Parallel Scavenge使用。

CMS也是老年代的垃圾收集器,使用并发标记整理算法, 他分四步执行。

只有在初始标记的时候须要暂停程序

G1垃圾收集器

已经在java11中应用了。 G1收集器弱化了老年代和新生代概念。
将内存分红小的区块, 会标记每一个区块的垃圾对象占比,并维护一个每一个区块的垃圾率等信息的表
根据用户配置的回收比率参数, 来肯定收集多少的区块的垃圾才符合配置
若是一个区块垃圾率很高,会直接采用复制清除算法, 复制这个区块存活对象到新的区块, 并回收它
若是区块垃圾少, 就会使用标记整理算法。
会合并使用率少的区块

java的四种引用类型

强引用

在 Java 中最多见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引
用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即
使该对象之后永远都不会被用到 JVM 也不会回收。所以强引用是形成 Java 内存泄漏的主要缘由之
一。

软引用

软引用须要用 SoftReference 类来实现,对于只有软引用的对象来讲,当系统内存足够时它
不会被回收,当系统内存空间不足时它会被回收。软引用一般用在对内存敏感的程序中

弱引用

弱引用须要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象
来讲,只要垃圾回收机制一运行,无论 JVM 的内存空间是否足够,总会回收该对象占用的内存。

虚引用

虚引用须要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚
引用的主要做用是跟踪对象被垃圾回收的状态。

Java的BIO, NIO 和AIO

阻塞IO

好比从一个输入流中读取数据, 用户进程调用read方法会发送命令到系统内核进程中,而且用户进程会阻塞等待, 系统内核进程会去查看流中是否有可读取数据,有的话返回数据到用户进程,用户进程阻塞结束,若是流中没有可读取数据,系统进程和用户进程都会阻塞直到能够读取数据为止。
从io的阻塞流程能够看到,程序是十分低效的,须要用户线程须要长期堵塞的。

非阻塞IO /NIO

用户进程调用系统内核进程读取数据的时候, 若是流中没有数据,内核进程会直接返回信息告知用户进程没有数据,用户进程也会当即返回不阻塞。

从非阻塞的问题能够看到,他会当即返回流的状态, 若是咱们要持续读取数据,就须要使用循环读取了。 咱们可使用一个线程循环读取每个流的状态, 若是某个流能够读取了, 就把这个流交给新的线程去读取。 这个方式就是epoll的工做原理。 主流的linux和win操做系统都实现了这个机制,咱们不用本身去实现这个循环读取流的操做,只要调用java的api就能够, 这个API正是java中的Selecter类。

多路复用技术:

java的NIO实际上就是对操做系统的epoll机制的调用调用实现的。

NIO: 用户进程开一个线程去监听各个流的状态, 当某个流可读取了,系统会通知用户进程, 用户进程再调用系统的方法从系统内核中复制流数据到用户进程中,这样就实现了数据的读取
AIO: 用户进程通知系统要读取某个流的数据, 以后用户进程就无论了去作其它的事情, 系统内核发觉某个流能够读取了,会本身把流的数据复制到用户进程中,并触发用户进程的回调通知用户进程。

能够看到从BIO到AIO 其实是把更多的操做放到系统进程中, 因为系统是直接与硬件打交道的程序, 由它来监控流状态,读取流数据是十分高效的。 所以IO的效率也是随着提高的。

为什么IO有这种发展趋势呢?

因为刚开始硬件性能低, 操做系统不能完成太复杂的操做, 操做系统就把更多的硬件操做权限交给上级应用程序来控制, 这样上级应用程序能够最大化的开发硬件的性能。 随着硬件的发展和业务复杂性的增长, 用户更但愿能使用更简单的api来应对复杂的业务, 同时也但愿操做系统作更多的事情,因而就发展成这样了。

为什么NIO比BIO高效?

高效是相对的, 若是在一个性能低下的硬件中, BIO比NIO高效的多, 可是在性能更好的硬件中NIO万宝BIO. 其实是硬件的发展带动了不一样的IO实现方式。 相信随着科技的发展,在将来AIO也会成为一种低效的IO

java的流的设计大量使用了装饰器模式, 能够很方便的把本身流转换成字符流等。
NIO中的Channel 和各类Buffer的设计也是将来高效的使用NIO.
AIO使用不高, 著名的netty框架放弃了AIO的实现, 由于听说是性能没有比NIO更好, AIO确实是比NIO更先进的一种设计理念,可是因为Linux并无实现好,致使netty没有采用。

java的序列化机制?

能够参考个人博客

java在序列化的前会调用对象中的writeReplace方法, 并把这个方法的返回值做为真正的序列化类对象。 反序列化的时候, 在以前会调用readResolve方法, 并把这个方法的返回值做为真正的反序列化对象。 因此重写这两个方法就能够控制类的序列化和反序列化的过程 单例模式你们都知道加锁或者私有的构造方法等能够保证惟一,可是若是使用序列化的方式就能够破坏这个惟一性, 若是要真正的保证惟一能够在单例中增长这两个方法来确保惟一。 其实序列化和反序列化 还有不少的方法能够控制这个过程 , 这里就不详细说了 lambda表达式能够经过序列化和反序列化的方式获取到传入的表达式的方法名。 具体能够参考我上面的博客。