Java 问题总结

2020-07-28  本文已影响0人  厚积方能薄发

一、基础

1、JDK 和 JRE 的区别

JRE(Java Runtime Environment)

JDK(Java Development Kit)

Java 开发工具包,包含了 JRE 、编译器和其它工具,可以让开发者开发、编译、执行 Java 应用程序。

2、静态方法和实例方法的区别

对比项 静态 动态
绑定方式 编译时静态绑定 运行时动态绑定
对象数 整个进程中只有一份 在进程中可创建多个实例
调用方式 不用创建实例,可以通过类.方法名调用 必须实例化后才可调用
可重写性 不能被重写,因为它是编译时静态绑定的,而重写是基于运行时动态绑定的 非 private 的方法可重写

3、说一下大 O 表示法

语句执行次数是问题规模 n 的函数,记作 T(n) = O(f(n)) 。
一般用来描述时间复杂度和空间复杂度。

4、Exception 和 Error 的区别

Exception

程序本身可以处理的异常。这些是可以预料的一些异常,应该被捕获或处理。
例:NullPointerException、ClassCastException

Error

程序本身不可处理的异常。
例:内存溢出。

5、Lamada 函数

Lamada 函数是一种匿名函数,优点是轻量、简捷。

二、数据类型

三、面向对象

1、Java 是否支持多继承

这个要从不同的角度区分:

2、接口和抽象类的区别

对比项 抽象类 接口
继承性 单继承性 多实现
方法类型 可以有抽象方法和非抽象方法 只能有抽象方法
实现方法 非抽象方法可以不实现 必须实现接口中的所有方法
权限 函数也可以是 private 和 protected 函数都是 public 的
包含对象 可包含非 final 的对象 申明的变量都是 final 的

3、绑定 ? 静态绑定? 动态绑定 ?

四、泛型

五、集合框架

1、集合类框架的基本接口有哪些

2、hashCode()和equals()方法的作用

  1. 确定索引:Java中的HashMap使用这两个方法来确定键值对的索引和取值
  2. 重复:如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash值,因此,可能会被集合认为是相等的。

3、数组(Array)和列表(ArrayList)有什么区别?

区别:

使用场景:
对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。

4、ArrayList 和 LinkedList 的区别

六、多线程

1、进程和线程的区别

对比项 进程 线程
作用 资源分配的最小单位 程序执行的最小单位
资源 进程间相互独立 同一进程不同线程间相互共享
健壮性 一个进程死掉不会影响其它线程 一个线程死掉会影响整个进程

2、创建线程的几种方法

  1. 继承 Thread 类
  2. 实现 Runnable 接口
  3. Executor 创建线程池
  4. 实现 Callable 接口

3、线程的几种状态

4、什么情况会引起线程阻塞

  1. 等待阻塞:运行的线程执行了 wait 、join、sleep 方法。
  2. 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池。
  3. IO操作:发出了 IO 请求时, JVM 会把该线程置为阻塞状态。

5、sleep、wait、join的区别

6、线程锁

什么是线程锁?

多线程可以同时运行多个任务,但是当多个线程同时访问共享数据时,可能导致数据不同步,甚至错误!
线程锁主要用来给方法、代码块加锁。当某个方法或者代码块使用锁时,那么在同一时刻至多仅有有一个线程在执行该段代码。

哪些可以作为线程锁?

可作为线程锁的有对象锁和类锁,对象锁是用来修饰实例方法的,类锁是用来修饰静态方法的。

对象锁:多个线程调用同一个对象的同步方法会阻塞,调用不同对象的同步方法不会阻塞。

public synchronized void obj3() {}

public void obj2() {
       synchronized (this) {}
}

public void obj2() {
       String str=new String("lock");
       //在方法体内,调用一次就实例化一次,多线程访问不会阻塞,因为不是同一个对象,锁是不同的
       synchronized (str) {}
       }
   }

类锁:

public static synchronized void obj3() {}

public void obj1() {
        synchronized (test.class) {}
}

类锁和对象锁同时存在时,多线程访问时不会阻塞,因为他们不是一个锁。

死锁

当两个线程都持有对方所需要的资源,并且同时等待对方的资源时就会造成死锁。举例:夫妻吵架,都待着对方先道歉,就会造成死锁。

7、volatile 关键字

L1

为什么会出现脏读?
Java内存模型规定所有的变量都是存在主存当中,每个线程都有自己的工作内存。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。变量的值何时从线程的工作内存写回主存,无法确定。

L2

计算机在执行程序时,每条指令都是在CPU中执行的,而程序运行过程中的临时数据是存放在主存(物理内存)当中的。

执行指令过程中,势必涉及到数据的读取和写入。这时就存在一个问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。因此在CPU里面就有了高速缓存。

当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。

那这样就会有问题了:

i = i + 1;

假如初始值为 0,两个线程都操作主存中的变量 i,我们希望执行两次加法后 i 的值为 2,实际情况会如我们预期吗?

可能存在下面一种情况:初始时,两个线程分别读取 i 的值存入各自所在的 CPU 的高速缓存当中,然后线程 1 进行加 1 操作,然后把 i 的最新值 1 写入到内存。此时线程 2 的高速缓存当中i的值还是 0,进行加 1 操作之后,i 的值为 1,然后线程 2 把 i 的值写入内存,最终结果 i 的值是1。

解决这样的问题有两种方案:

  1. 总线加锁:synchronized
  2. 一致性协议:volatile

由于在锁住总线期间,其他CPU无法访问内存,导致效率低下。所以就有了缓存一致性协议。

MESI 协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当 CPU 写数据时,如果发现操作的变量是共享变量,会发出信号通知其他 CPU 将该变量的缓存置为无效状态,因此当其他CPU 需要读取这个变量时,发现自己缓存中该变量是无效的,那么它就会从内存重新读取。

七、底层原理

1、Java虚拟机原理

L1:

我们平常写的代码放在.java文件中,通过javac会将其编译成.class字节文件,执行的时候会将这些字节class文件载入内存并转化为机器码执行。

L2:

Java的类加载器将.class文件载入内存,并分配给RuntimeDataArea,执行引擎会解释或编译这些类文件,转化成特定CPU机器码,CPU执行机器码,到此结束整个过程。

L3:

类加载器分为4种:Bootstrap、Extention、System、UserDefined,它们分别加载系统基本API、安全性能相关、应用程序中的类(也就是classpath中配置的)、开发人员自定义一些程序需要加载的类。

运行时区域:
堆内存:存放对象实例。
方法区:被虚拟机加载的类信息、常量、变量、方法。
运行时常量池:是方法区的一部分,存放程序中使用的各种常量。

以上这些是被线程所共享的。
而另外一部分就是线程,而每个线程中又包括了:虚拟机栈、程序计数器、本地方法栈。
虚拟机栈:作用是存放一系列栈帧,执行一个方法时入栈,结束时出栈。
程序计数器:每个线程启动时会创建一个程序计数器,它用来存放当前正在被执行的字节码指令的地址。
本地方法栈:与虚拟机栈类似,但它是用来执行native方法。

执行引擎:
Java的字节码,并不能被机器识别,如果想要被机器运行还要转换为机器码,而类执行引擎就是来完成这一步的,可以由其字节码解释器来转换,也可由即时编译器来转换。

2、JVM垃圾回收算法

  1. 引用计数(Reference Counting) 比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。

  2. 标记-清除(Mark-Sweep) 此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。

  3. 复制(Copying) 此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。

  4. 标记-整理(Mark-Compact) 此算法结合了 “标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象 “压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。

  5. 增量收集(Incremental Collecting) 实施垃圾回收算法,即:在应用进行的同时进行垃圾回收。

  6. 分代(Generational Collecting) 基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器(从J2SE1.2开始)都是使用此算法的。

待总结

参考:

上一篇下一篇

猜你喜欢

热点阅读