Java基础:
1、JAVA中的几种基本数据类型是什么,各自占用多少字节。
byte(1)、short(2)、int(4)、long(8)、float(4)、double(8)、boolean(1)、char(2)
2、String类能被继承吗,为什么?
不能。因为String 是被final修饰的类
3、String,Stringbuffer,StringBuilder的区别。
String为不可变,Stringbuffer和StringBuilder为可变类型
String和StringBuffer 线程安全,StringBuilder线程不安全
4、ArrayList和LinkedList有什么区别。
ArrayList底层数组实现,一般用来查询,可以对元素进行随机访问
LinkedList底层双向链表实现,一般用来增加和删除效率高
5、讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当new的时候,他们的执行顺序。
父类静态变量、父类静态代码块、子类静态变量、子类静态代码块、父类非静态变量(父类实例成员变量)、父类构造函数、子类非静态变量(子类实例成员变量)、子类构造函数。
6、用过哪些Map类,都有什么区别,HashMap是线程安全的吗,并发下使用的Map是什么,他们内部原理分别是什么,比如存储方式,hashcode,扩容,默认容量等。
HashMap、HashTable、LinkedHasMap、TreeMap
HashMap: 随机访问、无序的、非线性安全、只允许一条记录key为null,运行多条记录值 value为null、在Map中插入、删除、定位元素时,HashMap是最好的选择。
HashTable: 不允许记录的key和value为空、线程安全
LinkedHashMap:保存了记录的插入顺序,在iterator遍历LinkedHashMap时,先得到的 肯定是先插入的,比HashMap慢
TreeMap:保存记录根据键排序,默认升序,itorator遍历TreeMap时,记录是排过序的
7、JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何设计。
8、有没有有顺序的Map实现类,如果有,他们是怎么保证有序的?
HashMap和HashTable都不是有序的
TreeMap和LinkedHashMap都是有序的(TreeMap默认Key升序、LinkedHashMap默认数据插入顺序)
- 抽象类和接口的区别,类可以继承多个类么,接口可以继承多个接口么,类可以实现多个接口么。
- 抽象类和接口都不能直接实例化,抽象类不能用final修饰符
- 接口只能做方法申明,抽象类可以做方法申明,也可以方法实现
- 接口可继承接口,可以实现多接口,但类只能单继承
- 继承和聚合的区别在哪?
继承:is的关系,指一个类继承了另一个类的功能
聚合:has关系,指A中可以有b,如public class A{List<B> b}
组合:contans关系,值A中一定有b,public class A{B b}
- IO模型有哪些,讲讲你理解的nio ,他和bio,aio的区别是啥,谈谈reactor模型。
Bio同步并阻塞,适用于连接数目较小且固定的架构,这种方式对服务器资源要求较高,并且局限于应用中,jdk1.4以前的唯一选择。
Nio同步非阻塞,适用于连接数目多且连接较短(轻操作)的架构,并发局限于应用,编程复杂,jdk1.4开始支撑。
Aio异步非阻塞,适用于连接数目多且连接较长(重操作)的架构,充分调用os参与并发操作,变成复杂,jdk7开始支撑。
Reactor模型,Reactor将I/O事件分派给对应的Handler;Acceptor处理客户端新连接并分派请求到处理器中;Handlers执行非阻塞读/写任务
- 反射的原理,反射创建类实例的三种方式是什么。
反射:指程序在运行时能够动态的获取到一个类的类型信息的一种操作。
反射机制实现需要借助4个类:
class(类对象)\Constructor(构造器对象)\Field(属性对象)\Method(方法对象)
第一种Class cl=A.class;
第二种Class cl=A.getClass();
第三种Class cl=Class.forName(“com.chen.entity.A”);
13. 反射中,Class.forName和ClassLoader区别 。
Class.forName()将类的.class文件加载到JVM中,对类进行解释,执行类中static块
ClassLoader将类的.class文件加载到JVM中,不会执行static块,需要newInstance才可
- 描述动态代理的几种实现方式,分别说出相应的优缺点。
JDK动态代理:
优点:解决了静态代理中冗余的代理实现类问题
缺点:JDK动态代理是基于接口设计实现的,没有接口,会抛异常
CGLIB动态代理:
优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。
缺点:技术实现相对难理解些。
- 动态代理与cglib实现的区别。
JDK动态代理类实现了InvocationHandler接口,重写invoke方法。
JDK动态代理的基础是反射机制
CGLIB可以为没有实现接口的类去做代理,也可以为实现接口的类去做代理。
- final的用途。
修饰类,不可被继承;修饰方法,不能被重写;修饰变量,不能被修改
- 写出三种单例模式实现 。
饿汉式:
/**
* 单例模式:饿汉式
* 在类加载时,就创建单例对象
* 执行效率高,但是占空间,以空间换时间
* 线程安全
*/
public class Hungry {
private static final Hungry hungry = new Hungry();
private Hungry(){}
public static Hungry getInstance(){
return hungry;
}
}
懒汉式:
/**
* 单例模式:懒汉式 双重判断
* 对象使用的时候,才去创建
* 有线程安全的风险,需要加锁
*/
public class Lazy1 {
private Lazy1(){}
private static Lazy1 instance = null;
public static Lazy1 getInstance(){
if(instance == null){
synchronized (Lazy1.class){
if(instance == null){
instance = new Lazy1();
}
}
}
return instance;
}
}
/**
*单例模式:懒汉式 静态内部类
* 静态内部类在使用时,才加载
* 此种模式,既是懒加载,又没有加锁影响性能
*/
public class Lazy2 {
private Lazy2(){}
public static final Lazy2 getInstance(){
return LazyLoad.instance;
}
private static class LazyLoad{
private static final Lazy2 instance = new Lazy2();
}
}
注册式:
/**
* 懒汉式的变种
* spring IOC容器就是使用的这种注册式的单例模式
*/
public class BeanFactory {
public static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();
public static Object getBean(String className){
if(!ioc.containsKey(className)){
try {
Object instance = Class.forName(className).newInstance();
ioc.put(className, instance);
return instance;
} catch (Exception e) {
e.printStackTrace();
}
}
return ioc.get(className);
}
}
- 如何在父类中为子类自动完成所有的hashcode和equals实现?这么做有何优劣。
优点:不用重新去写
缺点:有时父类的equals和hashcode不满足要求时,需要重新去重写。
- 请结合OO设计理念,谈谈访问修饰符public、private、protected、default在应用设计中的作用。
Public:访问限制最宽的修饰符,修饰的类、属性、方法可以跨类访问,也可跨包访问
Private:访问限制最窄的修饰符,修饰的类、属性、方法只能该类的对象访问(子类、跨包不能访问)
Protected:介于public和private之间,修饰的类、属性、方法只能类本身方法和子类访问,即使子类不同包中也可以访问。
Default:该模式下,只允许同一个包中进行访问。
- 深拷贝和浅拷贝区别。
浅拷贝:
- 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。
b)对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
深拷贝:
对于深拷贝来说,不仅要复制对象的所有基本数据类型的成员变量值,还要为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。
简单地说,深拷贝对引用数据类型的成员变量的对象图中所有的对象都开辟了内存空间;而浅拷贝只是传递地址指向,新的对象并没有对引用数据类型创建内存空间。
- 数组和链表数据结构描述,各自的时间复杂度。
- 逻辑结构上:数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项) - 数组元素在栈区,链表结构在堆区
c)内存存储角度: (静态)数组从栈中分配空间, 对于程序员方便快速,但自由度小。
链表从堆中分配空间, 自由度大但申请管理比较麻烦。
数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n);
数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1)
23. error和exception的区别,CheckedException,RuntimeException的区别。
Error:Error类对象由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。
Exception:
- RuntimeException:
nullPointerException(空指针)、ClassCastException(类型转换)、
ArithmeticException(算术)、ClassNotFoundException(类找不)、IndexOutOfBoundsExcepton(下标越界)、
- 非运行时异常:
IOException(文件)、SQLException(SQL)、用户自定义异常等
运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。
受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。
- 在自己的代码中,如果创建一个java.lang.String类,这个类是否可以被类加载器加载?为什么。
加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是说当发现这个类没有的时候会先去让自己的父类去加载,父类没有再让儿子去加载。在java中java.lang.String肯定在上层的ClassLoader被加载过了,所以你自己写的完全没有机会加载。
26. 说一说你对java.lang.Object对象中hashCode和equals方法的理解。在什么场景下需要重新实现这两个方法。
对于equals()和hashcode(),通用规则:
两个object,如果equals()相等,hashCode()一定相等
两个object,如果hashCode()相等,equals()不一定相等
在设计一个类时往往重写equals()方法,在重写equals()方法时必须重写hashCode();
- 在jdk1.5中,引入了泛型,泛型的存在是用来解决什么问题。
泛型的核心意义在于:类在进行定义的时候可以使用一个标记,此标记就表示类中属性或者方法以及参数的类型,标记在使用的时候,才会去动态的设置类型。
- 有没有可能2个不相等的对象有相同的hashcode。
- 两个对象equals()相等,hashcode()一定相等
- 两个对象equals()不相等,hashcode()有可能相等
- 两个对象hascode()相等,equals()不一定相等
- 两个对象hashcode()不相等,equals()一定不相等
- Java中的HashSet内部是如何工作的。
HashSet 的内部采用 HashMap来实现。由于 Map 需要 key 和 value,所以HashSet中所有 key 的都有一个默认 value。类似于 HashMap,HashSet 不允许重复的key,只允许有一个null key,意思就是 HashSet 中只允许存储一个 null 对象。
- 什么是序列化,怎么序列化,为什么序列化,反序列化会遇到什么问题,如何解决。
序列化:把对象转换为字节序列的过程称为对象的序列化
反序列化:把字节序列恢复为对象的工程称为对象的反序列化
为什么序列化:
不同进程/线程进行远程通信时可以相互发送各种数据,包括文本图片视频等,对象不能直接传输,所以需要转换为二进制序列传输,所以需要序列化
需要序列化的情况:
把内存中的对象状态保存到一个文件中或数据库时
用套接字在网上传送对象时
通过RMI传输对象时
如何实现序列化:
实现Serializable接口会添加一个serialVersionUID版本号,作用是用来验证反序列化时是否是同一个类。序列化通过ObjectOutputStream类的writeObject()方法将对象直接写出。反序列化通过ObjectInputStream类的readObject()方法从流中读取。
反序列化会遇到的问题:
可能导致InvalidClassException异常
如果未声明serialVersionUID版本号,对对象的需求进行改动,兼容性会破坏,运行是导致InvalidClassException
注意事项:
transient修饰属性时不会被序列化
静态static属性不会序列化
实现Serializable接口,一定给serialVersionUID赋值
- java8的新特性。
速度更快、代码更少(Lambda表达式)、强大的Stream API、便于并行、最大化减少空指针异常Optional
JVM:
- 什么情况下会发生栈内存溢出。
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。
- JVM的内存结构,Eden和Survivor比例。
内存结构:
堆:存放对象
栈:运行时存放栈帧
程序计数器
方法区:存放类和常亮
比例:Eden区是一块,Survivor区是两块。比例是8:1:1
- JVM内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为Eden和Survivor。
- JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代,说说你知道的几种主要的JVM参数。
对象诞生即新生代->eden,在进行minor gc过程中,如果依旧存活,移动到from变成survivor进行标记。当一个对象存活默认超过15次都没有被回收掉就进入老年代。
JVM参数:
- JVM类型及编译器模式
-server 和-client
-version 和showversion
-Xint、-Xcomp 和 -Xmixed
b) 参数分类和即时(JIT)编译器诊断
-XX:+PrintCompilation 和 -XX:CITime
-XX:+UnlockExperimentalVMOptions
-XX:+LogCompilation和 -XX:+PrintOptoAssembly
c) 打印所以XX参数及值
-XX:+PrintFlagsFinal 和 -XX:+PrintFlagsInitial
-XX:+PrintCommandLineFlags
d) 内存调优
-Xms 和 -Xmx实际上是-XX:InitialHeapSize 和 -XX:MaxHeapSize的缩写代表初始化堆内存和最大堆内存
-XX:+HeapDumpOnOutOfMemoryError 和 -XX:HeapDumpPath
-XX: MaxPermSize设置永久代大小最大值 -XX:PermZize设置永久带初始大小
-XX:InitialCodeCacheSize 和 -XX:ReservedCodeCacheSize
-XX:+UseCodeCacheFlushing
e)新生代垃圾回收
-XX:NewSize 和 -XX:MaxNewSize指定新生代大小
-XX:NewRatio 设置新生代和老年代的相对大小
-XX:ServivorRatio指定Eden与Servivor区的大小比例
-XX:+PrintTenuringDistribution 指定JVM 在每次新生代GC时,输出幸存区中对象的年龄分布。
-XX:+NeverTenure , 对象永远不会晋升到老年代.
-XX:+AlwaysTenure, 表示没有幸存区,所有对象在第一次GC时,会晋升到老年代。
f)吞吐量收集器
-XX:+UseSerialGC 标志来激活串行垃圾收集器
-XX:+UseParallelGC多线程并行执行年轻代垃圾收集
-XX:+UseParallelOldGC要优于-XX:+UseParallelGC:除了激活年轻代并行垃圾收集,也激活了年老代并行垃圾收集。
-XX:ParallelGCThreads指定并行垃圾收集的线程数量
-XX:GCTimeRatio告诉JVM吞吐量要达到的目标值
-XX:MaxGCPauseMillis告诉JVM最大暂停时间的目标值(以毫秒为单位)
g) CMS收集器
-XX:+UseConMarkSweepGC 该标志首先是激活CMS收集器
-XX:UseParNewGC该标志激活年轻代使用多线程并行执行垃圾回收
-XX:+CMSConcurrentMTEnabled标志被启用时,并发的CMS阶段将以多线程执行
-XX:ConcGCThreads定义并发CMS过程运行时的线程数
-XX:CMSInitiatingOccupancyFraction当堆满之后,并行收集器便开始进行垃圾收集
-XX:+DisableExplicitGC标志将告诉JVM完全忽略系统的GC调用
h) GC日志
-XX:+PrintGC 开启了简单GC日志模式,为每一次新生代(young generation)的GC和每一次的Full GC打印一行信息。
-XX:PrintGCDetails开启了详细GC日志模式
-XX:+PrintGCTimeStamps和-XX:+PrintGCDateStamps 可以将时间和日期也加到GC日志中
-Xloggc
- 你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms和G1,包括原理,流程,优缺点。
新生代垃圾回收器:Serial、ParNew、Parallel Scavenge
老年代垃圾回收器:Serial Old、 Parallel Old、CMS
整堆回收器:G1(Garbage-First)同时收集老年代和年轻代
CMS: 基于标记-清除算法实现的
步骤:
初始标记:此时标记需要用户线程停下来
并发标记:此时标记可以和用户线程一起运行
重新标记:此时标记需要用户停下,目的市为了对并发标记的垃圾进行审核
并发清除:与用户线程一起运行进行垃圾清除
优点:并发收集,低停顿
缺点:
CMS收集器对CPU资源非常敏感
CMS收集器无法处理浮动垃圾
CMS基于标记清除算法实现的,内存碎片会产生过多
G1: 整体来看是基于“标记-整理”算法实现;局部上看是基于“复制”算法实现的。
步骤:
初始标记:标记GC Root能直接关联的对象,并修改TAMS的值,让下一阶段的用户并发运行,能够正确运用Region创建新对象,这阶段需要短暂停顿。
并发标记:与用户线程并发执行
最终标记:CPU停顿处理垃圾,可并行执行
筛选回收:根据用户期望的GC停顿时间回收
优点:
并行于并发、分代收集、空间整合、可预测的停顿
缺点:
Region大小和大对象很难保持一致,会导致空间浪费,很难找到连续的存储空间
- 垃圾回收算法的实现原理。
标记-清除算法(Mark-Sweep):
分为标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。
优缺点:算法实现容易,但效率低,容易产生内存碎片,碎片太多可能会导致后续过程为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。
复制算法(Copying):
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。
优缺点:实现简单,效率高不易产生内存碎片,但对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。
标记-整理算法(Mark-compact):
标记阶段和标记-清除算法一样,但在完成标记后,不是直接清理可回收对象,而是将存活对象向一端移动,然后清理掉端边界以外的内存。
优缺点:成本高,解决了内存碎片的问题
分代算法(Generational Collection):
核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。一般来说新生代都采取Copying算法,由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。
39. 当出现了内存溢出,你怎么排错。
类中和引用变量是否过多的使用了static修饰
是否使用了大量递归或无限递归
是否使用了大量循环或死循环
是否向数据库查询所有记录的方法,如果数量超过10万条,可能造成内存溢出,查询时应分页查询
是否有数组,List,Map中存放的是对象的引用而不是对象,这些引用会让对应的对象不能释放,大量储存在内存
是否使用了“非字面量字符串进行+”的操作,因为String类不可变,每次运行“+”会产生新的对象,过多会造成新String对象过多,从而导致JVM没有及时回收而内存溢出
- JVM内存模型的相关知识了解多少,比如重排序,内存屏障,happen-before,主内存,工作内存等。
内存屏障:为了保障执行顺序和可见性的一条cpu指令
重排序:为了提高性能,编译器和处理器会对执行进行重拍
happen-before:操作间执行的顺序关系。有些操作先发生。
主内存:共享变量存储的区域即是主内存
工作内存:每个线程copy的本地内存,存储了该线程以读/写共享变量的副本
- 简单说说你了解的类加载器,可以打破双亲委派么,怎么打破。
类加载器就是根据指定限定名称将class文件加载到JVM内存,转为Class对象
启动类加载器(Bootstrap ClassLoader)由C++语言实现(针对HotSpot),负责将存放在\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中。
其他类加载器由Java语言实现,继承自抽象类ClassLoader。
扩展类加载器(Extension ClassLoader)负责加载\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库。
应用程序类加载器(Application ClassLoader)负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。
打破方法:线程上下文加载器(Thread Context ClassLoader) 打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法。
- 讲讲JAVA的反射机制。
43.你们线上应用的JVM参数有哪些。
-server
-Xms6000M
-Xmx6000M
-Xmn500M
-XX:PermSize=500M
-XX:MaxPermSize=500M
-XX:SurvivorRatio=65536
-XX:MaxTenuringThreshold=0
-Xnoclassgc
-XX:+DisableExplicitGC
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=0
-XX:+CMSClassUnloadingEnabled
-XX:-CMSParallelRemarkEnabled
-XX:CMSInitiatingOccupancyFraction=90
-XX:SoftRefLRUPolicyMSPerMB=0
-XX:+PrintClassHistogram
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintHeapAtGC
-Xloggc:log/gc.log
- 打印线程堆栈的方法
方法一:
public class CallStack {
public static void printCallStatck() {
Throwable ex = new Throwable();
StackTraceElement[] stackElements = ex.getStackTrace();
if (stackElements != null) {
for (int i = 0; i < stackElements.length; i++) {
System.out.print(stackElements[i].getClassName()+"/t");
System.out.print(stackElements[i].getFileName()+"/t");
System.out.print(stackElements[i].getLineNumber()+"/t");
System.out.println(stackElements[i].getMethodName());
System.out.println("-----------------------------------");
}
}
}
方法二:
Exception e = new Exception("this is a log");
e.printStackTrace();
方法三:
String fullStackTrace = org.apache.commons.lang.exception.ExceptionUtils.getFullStackTrace(e)
方法四:
Thread.currentThread().getStackTrace()
HashMap为什么线程不安全
悲观锁和乐观锁
CurrentHashMap
线程池
Spring AOP
Spring IOC
类加载过程
volatile
ThreadLocal
未更新完