Java面试宝典书籍-JavaSE高级

本文详细介绍了JavaSE高级知识,包括Java反射的原理和应用,动态代理的实现与区别,设计模式的种类与实践,如单例、工厂模式等,以及JVM的垃圾回收机制、内存结构。此外,还提到了Java8的新特性,如Lambda表达式,并讨论了内存溢出的原因和解决方法。
摘要由CSDN通过智能技术生成
  • 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”错误前是否有其它异常或错误
  • 对代码进行走查和分析,找出可能发生内存溢出的位置​​​​​​​​​​​​​​
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值