-
JavaSE高级 (注:知识都来源于Java面试宝典书籍,此处只为学习)
一、Java中的反射
1. 理解
反射是首先能够获取到java中要反射类的字节码,然后将字节码中的方法、变量、构造函数等映射成相应的Method、Filed、Constructor等类。
获取字节码方式:
- Class.forName(className)
- 类名.class
- this.getClass()
二、Java中的动态代理
1. 写一个ArrayList的动态代理类
首先复习动态代理机制:https://blog.csdn.net/hungrysoul/article/details/80835909 作者:hungrysoul
(此图片来此上面中的博客)
public static void main(String[] args) {
//直接调用真实角色
Providable p1 = new ComputerFactory();
//调用卖电脑
p1.sellComputer(3500);
//调用修电脑方法
p1.repairComputer(200);
/*
生成代理对象
*/
Providable p2 = (Providable) Proxy.newProxyInstance(
// 参数1:真实对象的类加载器
p1.getClass().getClassLoader(),
//参数2:真实对象实现的所有的接口,接口是特殊的类,使用Class[]装载多个接口
new Class[]{Providable.class},
//参数3: 接口,传递一个匿名内部类对象
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy:代理对象
//method: 代理的方法对象
//args: 方法调用时参数
if (method.getName().equals("sellComputer")) {
//得到方法的参数
double price = (double) args[0];
//如果是卖电脑,加价1500
System.out.println("代理商卖电脑:" + (price + 1500));
return null;
}
else {
//如果是修电脑,调用原来的方法,不修改原来的方法
return method.invoke(p1,args);
}
}
}
);
//调用代理对象的方法
p2.sellComputer(3500);
p2.repairComputer(200);
}
final List<String> list = new ArrayList<>();
List<String> proxyInstance = (List<String>) Proxy.newProxyInstance(list.getClass().getClassLoader(),
list.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(list,args);
}
});
proxyInstance.add("你好");
proxyInstance.add("代理");
System.out.println(list);
2. 动静态代理的区别,什么场景下使用?
-
静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类
-
静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道
-
动态代理是实现 JDK 里的 InvocationHandler 接口的 invoke 方法,但注意的是代理的是接口,也就是你业务类必须要实现接口,通过 Proxy 里的 newProxyInstance 得到代理对象
AOP 编程就是基于动态代理实现的,比如著名的 Spring 框架、Hibernate 框架等等都是动态代理的使用例子
三、Java的设计模式及回收机制
1. 设计模式 https://www.runoob.com/design-pattern/singleton-pattern.html 菜鸟教程
- 创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
- 结构型模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
- 行为型模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式
2. 单例模式:懒汉式和饿汉式
3. 工厂设计模式:工厂方法模式和抽象工厂模式
工厂方法模式:
- 普通工厂模式:就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建
- 多个工厂模式:是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象
- 静态工厂模式:将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可
抽象工厂模式:创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码
4. 建造者模式
将各种产品集中起来进行管理,用来创建复合对象, 所谓复合对象就是指某个类具有不同的属性
5. 适配器模式--将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题
-
类的适配器模式
-
对象的适配器模式
-
接口的适配器模
6. 装饰模式-Decorator
定义:是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例
7. 策略模式(strategy)
定义:策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,属于辅助类),提供辅助函数。策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可
8. 观察者模式(Observer)
定义:当你订阅了该文章,如果后续有更新,会及时通知你。当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化!对象之间是一种一对多的关系
Vector 可实现自动增长的对象数组
9. JVM垃圾回收机制和常见算法
CG(Garbage Collection):在回收对象前首先必须发现那些无用的对象,如何去发现这些无用的对象--搜索对象
- 引用计数器算法(废弃):给每个对象设置一个计数器,当有地方引用这个对象的时候,计数器+1,当引用失效的时候,
计数器-1,当计数器为 0 的时候,JVM 就认为对象不再被使用,是“垃圾”了。引用计数器实现简单,效率高;但是不能解决循环引用问问题(A 对象引用 B 对象,B 对象又引用 A 对象,但是A,B 对象已不被任何其他对象引用),同时每次计数器的增加和减少都带来了很多额外的开销。
- 根搜索算法:从这些节点开始往下搜索,搜索通过的路径成为引用链(Reference Chain),当一个对象没有被 GC Roots 的引用链连接的时候,说明这个对象是不可用的。
GC Roots对象:
- 虚拟机栈(栈帧中的本地变量表)中的引用的对象
- 方法区域中的类静态属性引用的对象
- 方法区域中常量引用的对象
- 本地方法栈中 JNI(Native 方法)的引用的对象
垃圾搜索出来后,那就再回收
1) 标记-清除算法(Mark Sweep)
2) 复制算法
3) 标记-整理算法
4) 分代收集(Generational Collection)
分代收集是根据对象的存活时间把内存分为新生代和老年代,根据各个代对象的存活特点,每个代采用不同的垃圾回收算法。新生代采用复制算法,老年代采用标记—整理算法。垃圾算法的实现涉及大量的程序细节,而且不同的虚拟机平台实现的方法也各不相同。
10. JVM的内存结构和内存分配
1) Java内存模型
Java 虚拟机将其管辖的内存大致分三个逻辑部分:方法区(Method Area)、Java 栈和 Java 堆。
-
方法区是静态分配的,编译器将变量绑定在某个存储位置上,而且这些绑定不会在运行时改变。 常数池,源代码中的命名常量、String 常量和 static 变量保存在方法区
-
Java Stack 是一个逻辑概念,特点是后进先出。一个栈的空间可能是连续的,也可能是不连续的,最典型的 Stack 应用是方法的调用,Java 虚拟机每调用一次方法就创建一个方法帧(frame),退出该方法则对应的 方法帧被弹出(pop)。栈中存储的数据也是运行时确定的。
-
Java 堆分配(heap allocation)意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。堆中存储的数据常常是大小、数量和生命期在编译时无法确定的。Java 对象的内存总是在 heap 中分配。
2) java内存分配
-
基础数据类型直接在栈空间分配
-
方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收
-
引用数据类型,需要用 new 来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量
-
方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完后从栈空间回收
-
局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待 GC 回收
-
方法调用时传入的实际参数,先在栈空间分配,在方法调用完成后从栈空间释放
-
字符串常量在 DATA 区域分配 ,this 在堆空间分配
-
数组既在栈空间分配数组名称, 又在堆空间分配数组实际的大小
11. Java中引用类型都有哪些
- 强引用:如果一个对象被被人拥有强引用,那么垃圾回收器绝不会回收它。 Java 的对象是位于 heap 中的,heap 中对象有强可及对象、软可及对象、弱可及对象、虚可及对象和不可到达对象 对于对象是属于哪种可及的对象,由他的最强的引用决定
- 软引用:如果一个对象只具有软引用,那么如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会
回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用
- 弱引用:如果一个对象只具有弱引用,那该类就是可有可无的对象
- 虚引用:如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收
12. heap和stack有什么区别
1)申请方式
-
stack:由系统自动分配。例如,声明在函数中一个局部变量 int b; 系统自动在栈中为 b 开辟空间
-
heap:需要程序员自己申请,并指明大小,在 c 中 malloc 函数,对于 Java 需要手动 new Object()的形式开辟
2)申请后系统的响应
- stack:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出
- heap:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中
3)申请大小的限制
-
stack:栈是向低地址扩展的数据结构,是一块连续的内存的区域
-
heap:堆是向高地址扩展的数据结构,是不连续的内存区域
4)申请效率的比较
-
stack:由系统自动分配,速度较快。但程序员是无法控制的
-
heap:由 new 分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便
5)heap和stack中存储的内容
- stack:函数调用时,第一个进栈的时主函数中下一条指令的地址,然后是函数的各个参数。注:静态变量是不入栈的。调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,即主函数中下一条指令
-
一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排
6)数据结构层面的区别
13. 解释内存中的栈、堆和方法区的用法
四、Java的类加载器
1. Java的类加载器
- 根类加载器(Bootstrap):C++写的,看不到源码
- 扩展类加载器(Extension):加载位置,jre\lib\ext
- 系统类加载器(System\App):classpath
- 自定义加载器(必须继承ClassLoader)
2. 类什么时候被初始化
- 创建类的实例,也就是new一个对象
- 访问某个类或接口的静态变量,或者对该变量赋值
- 调用类的静态方法
- 反射(Class.forName("com.lyi.load"))
- 初始化一个类的子类(会首先初始化父类)
- JVM启动时标明的启动类,即文件名和类名相同的那个类
类初始化步骤:
-
如果这个类还没有被加载和链接,那先进行加载和链接
-
假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)
-
加入类中存在初始化语句(如 static 变量和 static 块),那就依次执行这些初始化语句
3. Java类加载体系之ClassLoader双亲委托机制
安全沙箱机制
- 类加载体系
- .class文件检验器
- 内置Java虚拟机的安全特性
- 安全管理器及Java API
加载体系:
.java-->.class文件,.class文件通过类加载器加载的(用到双亲委托机制)
4. JVM加载class
JVM 中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java 中的类加载器是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入类文件中的类
类的加载首先请求
父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载
5. 获得一个类对象有哪些方式
- .class:例:String.class
- 对象 .getClass() 例如:"hello"。getClass()
- Class,forName() 例如:Clas.forName("java.lang.String")
五、JVM基础知识
1. 既然有GC机制,为什么还会有内存泄漏的情况
例如:hibernate的Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中能存在无用的垃圾对象,如果不及时关闭(close)或清空(flush)一级缓存就可能导致内存泄露
六、GC基础知识
1. Java中为什么会有GC机制
-
安全性考虑
-
减少内存泄露
-
减少程序员工作量
2. Java的GC哪些内存需要回收
主要包括 5 大部分:程序计数器(Program Counter Register)、虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap)
-
程序计数器、虚拟机栈、本地方法栈是每个线程私有的内存空间,随线程而生,随线程而亡
-
但方法区和堆就不同了,一个接口的多个实现类需要的内存可能不一样,只有在程序运行期间才会知道会创建哪些对象,这部分内存的分配和回收都是动态的,GC 主要关注的是这部分内存
3. Java的GC什么时候回收垃圾
如何判断一个对象已经死去?
- 引用计数器
- 可达性分析
判断对象是否存活与引用有关,如何定义对象的引用
- 强引用(Strong Reference):Object obj = new Object();:只要强引用还存在,GC 永远不会回收掉被引用的对象
- 软引用:描述一些还有用但非必需的对象。在系统将会发生内存溢出之前,会把这些对象列入回收范围进行二次回收(即系统将会发生内存溢出了,才会对他们进行回收
- 弱引用:程度比软引用还要弱一些。这些对象只能生存到下次 GC 之前。当 GC 工作时,无论内存是否足够都会将其回收(即只要进行 GC,就会对他们进行回收
- 虚引用:一个对象是否存在虚引用,完全不会对其生存时间构成影响
方法区中需要回收的是一些废弃的常量和无用的类
-
废弃的常量的回收。这里看引用计数就可以了。没有对象引用该常量就可以放心的回收了
-
无用的类的回收。什么是无用的类呢:a. 该类所有的实例都已经被回收。也就是 Java 堆中不存在该类的任何实例 b. 加载该类的 ClassLoader 已经被回收 c. 该类对应的 java.lang.Class 对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法
堆中对象,主要是用可达性分析判断一个对象是否存还存在引用
七、Java8的新特性以及使用
1. lambda表达式
(params) -> expression
(params) -> statement
(params) -> { statements }
八、在开发遇到过内存溢出?原因有哪些?解决方法?
内存溢出原因:
-
内存中加载的数据量过于庞大,如一次从数据库取出过多数据
-
集合类中有对对象的引用,使用完后未清空,使得 JVM 不能回收
-
代码中存在死循环或循环产生过多重复的对象实体
-
使用的第三方软件中的 BUG
-
启动参数内存值设定的过小
解决方案:
- 修改 JVM 启动参数,直接增加内存
- 检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误
- 对代码进行走查和分析,找出可能发生内存溢出的位置