Java八股文(高阶)背诵版

目录

1、谈谈 JVM 的内存结构和内存分配

2、 解释内存中的栈 (stack) 、堆 (heap) 和方法区 (method area) 的用法

3、java 中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和 suspend()方法为何不推荐使用?

4、sleep() 和 wait() 有什么区别?

6、简述 synchronized java.util.concurrent.locks.Lock 的异同?

7、谈谈 synchronized 和 ReentrantLock 的区别

8、线程的基本概念

10、程序、进程、线程之间的关系

11、创建线程有几种方式,分别是什么?

12、线程的生命周期

13、线程 currentThread()与 interrupt()方法的使用

14、线程状态

15、启动线程的方式?start or run?

16、在 java 中守护线程和本地线程区别?

17、死锁与活锁的区别,死锁与饥饿的区别?

18、线程的调度策略

19、Java 中用到的线程调度算法是什么?

20、线程之间是如何通信的?

21、为什么 wait(), notify()和 notifyAll ()必须在同步方法或者同步块中被调用?

22、什么是线程池?有哪几种创建方式?

23、四种线程池的创建:

24、线程池的优点?

25、volatile 关键字的作用

26、死锁的原因

27、什么是 java 序列化,如何实现 java 序列化?

28、Java 序列化中如果有些字段不想进⾏序列化,怎么办?

29、Collections ⼯具类和 Arrays ⼯具类常⻅⽅法总结

30、 深拷⻉ vs 浅拷⻉

31、 ArrayList 与 Vector 区别呢?为什么要⽤Arraylist 取代 Vector 呢?

32、 HashMap 的底层实现

33、 ConcurrentHashMap 和 Hashtable 的区别

34、 ThreadLocal

35、简单的介绍⼀下强引⽤,软引⽤,弱引⽤,虚引⽤

37、 字节流与字符流的区别

38、怎么判断指定路径是否为目录

39、怎么获取指定路径下的全部文件

40、 BIO,NIO,AIO 有什么区别?

41、 JDBC 中的 PreparedStatement 相比 Statement 的好处

43、关系数据库中连接池的机制是什么?

44、List、Set 和 Map 的区别?

45、Collection 和 Collections 的区别。

46、Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是 equals()? 它们有何区别?

47、HashMap 与 HashTable 的区别

48、Java 中有多少种数据结构,分别是什么?

49、Arraylist 和 linkdlist 的区别

50、List 遍历方式有多少种

51、Map 怎么遍历

52、怎么获取 Map 所有的 key,所有的 value

53、获取 Class 的实例有几种方式

54、怎么获取类中所有的方法,所有属性

55、JDBC 常用接口有哪些?

56、Statement 中 execute、executeUpdate、executeQuery 这三者的区别

57、jdbc 中怎么做批量处理的?

58、什么是 json

59、json 与 xml 的区别

60、XML 和 HTML 的区别?

61、XML 文档定义有几种形式?它们之间有何本质区别?

62、什么是 java 反射机制?

63、知道类的加载过程吗

64、知道哪些类加载器

65、什么是双亲委派模型

66、hashmap 的底层实现

67、什么是 java 内存泄漏,怎么预防?


1、谈谈 JVM 的内存结构和内存分配

a) JVM 内存模型

JDK1.8之前

JDK1.8

  1. 方法区是静态分配的,编译器将变量绑定在某个存储位置上,而且这些绑定不会在运行时改变。常数池,源代码中的命名常量、String 常量和 static 变量保存在方法区。
  2. JavaStack 是一个逻辑概念,特点是后进先出。一个栈的空间可能是连续的,也可能是不连续的。最典型的 Stack 应用是方法的调用,Java 虚拟机每调用一次方法就创建一个方法帧(frame),退出该方法则对应的 方法帧被弹出(pop)。栈中存储的数据也是运行时确定的。所谓的虚拟机栈就是常说的栈。本地方法栈和虚拟机栈所发挥的作⽤⾮常相似,区别是: 虚拟机栈为虚拟机执⾏ Java ⽅法 (也就是字节码)服务,⽽本地⽅法栈则为虚拟机使⽤到的 Native ⽅法服务。 在HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
  3. Java 堆分配(heap allocation)意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。堆中存储的数据常常是大小、数量和生命期在编译时无法确定的。Java 对象的内存总是在 heap 中分配。我们每天都在写代码,每天都在使用 JVM 的内存。
  4. 程序计数器:字节码解释器通过改变程序计数器来依次读取指令,从⽽实现代码的流程控制,如:顺序执⾏、选择、循环、异常处理。在多线程的情况下,程序计数器⽤于记录当前线程执⾏的位置,从⽽当线程被切换回来的时候能够知道该线程上次运⾏到哪个位置。
  5. JDK 1.8 的时候,⽅法区(HotSpot的永久代)被彻底移除了(JDK1.7就已经开始了),取⽽代之是元空间,元空间使⽤的是直接内存。

b) java 内存分配

  1. 基础数据类型直接在栈空间分配;
  2. 方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收;
  3. 引用数据类型,需要用 new 来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量;
  4. 方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完后从栈空间回收;
  5. 局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待 GC 回收;
  6. 方法调用时传入的实际参数,先在栈空间分配,在方法调用完成后从栈空间释放;
  7. 字符串常量在 DATA 区域分配,this 在堆空间分配;
  8. 数组既在栈空间分配数组名称,又在堆空间分配数组实际的大小!

2、 解释内存中的栈 (stack) 、堆 (heap) 和方法区 (method area) 的用法

通常一个基本数据类型的变量,一个对象的引用,以及函数调用的现场保存都使用 JVM 中的栈空间;而通过 new 关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,现在的垃圾收集器都采用分代收集算法,故堆空间还可以细分为新生代和老生代,再具体一点可以分为 Eden、Survivor(又可分为From Survivor 和 To Survivor)、Tenured;

方法区和堆都是各个线程共享的内存区域,用于存储已经被 JVM 加载的类信息、常量、静态变量、JIT 编译器编译后的代码等数据;

程序中的字面量(literal)如直接书写的 100、"hello" 和常量都是放在常量池中,常量池是方法区的一部分。

栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过 JVM 的启动参数来进行调整(栈空间用光了会引发 StackOverflowError,而堆和常量池空间不足则会引发 OutOfMemoryError)

3、java 中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和 suspend()方法为何不推荐使用?

实现线程有两种方式:1.继承 Thread 类,重写 run 方法,在调用 start 方法。

实现 Runnable 接口,重写 run 方法。在传给 Thread 构造器,调用时调用 Thread 的 start 方法。

用 synchronized 关键字修饰同步方法 。

不使用 stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。suspend()方法容易发生死锁。调用 suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用 suspend(),而应在自己的 Thread 类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用 wait()命其进入等待状态。若标志指出线程应当恢复,则用一个 notify()重新启动线程

4、sleep() 和 wait() 有什么区别?

sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用 sleep 不会释放对象锁。

wait 是 Object 类的方法,对此对象调用 wait 方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出 notify 方法(或 notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。

5、当一个线程进入一个对象的一个 synchronized 方法后,其它线程是否可进入此对象的其它方法?

分几种情况:

    1. 其他方法前是否加了 synchronized 关键字,如果没加,则能。
    2. 如果这个方法内部调用了 wait,则可以进入其他 synchronized 方法。
    3. 如果其他个方法都加了 synchronized 关键字,并且内部没有调用 wait,则不能。
    4. 如果其他方法是 static,它用同步锁是当前类的字节码,与非静态的方法不能同步,因为非静态的方法用的是 this。

6、简述 synchronized java.util.concurrent.locks.Lock 的异同?

答: Lock 是 Java 5 以后引入的新的 API,和关键字 synchronized 相比主要相同点:Lock 能完成 synchronized 所实现的所有功能;主要不同点:Lock 有比synchronized 更精确的线程语义和更好的性能,而且不强制性的要求一定要获得锁。synchronized 会自动释放锁,而 Lock 一定要求程序员手工释放,并且最好在 finally 块中释放(这是释放外部资源的最好的地方)。

7、谈谈 synchronized 和 ReentrantLock 的区别

  1. synchronized 依赖于 JVM ⽽ ReentrantLock 依赖于 API

synchronized 是依赖于 JVM 实现的,虚拟机团队在 JDK1.6 为 synchronized 关键字进⾏了很多优化,这些优化都是在虚拟机层⾯实现的,并没有直接暴露给开发人员。

ReentrantLock 是 JDK 层⾯实现的(也就是 API 层⾯,需要 lock() 和 unlock() ⽅法配合try/finally 语句块来完成)

        2.ReentrantLock ⽐ synchronized 增加了⼀些⾼级功能

①等待可中断,可以通过lock.lockInterruptibly()来实现中断等待锁的线程的机制,使其放弃等待改为处理其他事情。

②可实现公平锁;ReentrantLock可以指定是公平锁还是⾮公平锁。⽽synchronized只能是⾮公平锁。

③可实现选择性通知(锁可以绑定多个条件)ReentrantLock类结合Condition实例可以实现“选择性通知”,而使用synchronized关键的话执行notifyAll()会通知所有等待线程。

8、线程的基本概念

一个程序中可以有多条执行线索同时执行,一个线程就是程序中的一条执行线索,每个线程上都关联有要执行的代码,即可以有多段程序代码同时运行,每个程序至少都有一个线程,即 main 方法执行的那个线程。如果只是一个 cpu,它怎么能够同时执行多段程序呢?这是从宏观上来看的,cpu 一会执行 a 线索,一会执行 b 线索,切换时间很快,给人的感觉是 a,b 在同时执行,好比大家在同一个办公室上网,只有一条链接到外部网线,其实,这条网线一会为 a 传数据,一会为 b 传数据,由于切换时间很短暂,所以,大家感觉都在同时上网。

9、什么是多线程

线程是程序执行流的最小单元,相对独立、可调度的执行单元,是系统独立调度和分派

CPU 的基本单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。

10、程序、进程、线程之间的关系

程序是一段静态的代码,是应用软件执行的蓝本。

进程是程序一次动态执行的过程,它对应了从代码加载、执行完毕的一个完整过程,这也是进程开始到消亡的过程。

线程是进程中独立、可调度的执行单元,是执行中最小单位。

一个程序一般是一个进程,但可以一个程序中有多个进程。

一个进程中可以有多个线程,但只有一个主线程。

Java 应用程序中默认的主线程是 main 方法,如果 main 方法中创建了其他线程,JVM

就会执行其他的线程。

11、创建线程有几种方式,分别是什么?

创建线程有三种方式:

  1. 是继承 Thread 类,创建格式如下:Thread thread = new Thread();
  2. 是实现 Runnable 接口,创建格式如下: Thread thread = new Thread(new Runnable()); 其实 Thread 类实现了 Runnable 接口
  3. 是实现 Callable 接口
    Callable<Integer> oneCallable = new SomeCallable<Integer>();
    FutureTask<Integer> oneTask = new FutureTask<Integer>(oneCallable);
    Thread oneThread = new Thread(oneTask); 
    oneThread.start();

  4. 通过线程池方式,获取线程
    package com.myjava.thread; 
    import java.util.concurrent.ExecutorService; 
    import java.util.concurrent.Executors; 
    public class ThreadPool { 
        private      static int POOL_NUM = 10;
        public static void main(String[] agrs){
    
            ExecutorService executorService = Executors.newFixedThreadPool(5); 
            for (int i = 0; i < POOL_NUM; i++) {
    
                RunnableThread thread = new RunnableThread(); executorService.execute(thread);
    
            }
    
        } 
    } 
    class RunnableThread implements Runnable{ 
        private  int THREAD_NUM = 10;
    
        public void run() { 
            for (int i = 0; i <THREAD_NUM; i++) {
    
                System.out.println("线程"+Thread.currentThread()+i);
    
            }
    
        }
    
    }

12、线程的生命周期

新建—就绪 –运行—阻塞—销毁新建:就是使用 new 方法,new 出来的线程就绪:当调用线程的 start()方法后,这时候线程属于等待 CPU 分配资源阶段,谁先抢到 cpu 资源谁开始执行运行:当就绪的线程被调度并且获得 cpu 资源时,便进入运行状态,run 方法定义了线程的操作和功能阻塞:在运行状态的时候,可能因为某些原因导致运行状态线程进入阻塞状态,比如 sleep() 和 wait()之后线程就属于阻塞状态,这时需要其它机制将处于阻塞状态的线程唤醒,比如 notify()或者 notifyAll()方法,唤醒的线程不会立即执行 run()方法,它们要再次等待 cpu 分配资源进入运行状态。

销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致终止,那么现场就被销毁,释放资源。

13、线程 currentThread()与 interrupt()方法的使用

currentThread()方法是获取当前线程

interrupt()唤醒休眠线程,休眠线程发生 InterruptedException 异常

14、线程状态

  1. 新建状态(New):新创建了一个线程对象。
  2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的 start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取 CPU 的使用权。
  3. 运行状态(Running):就绪状态的线程获取了 CPU,执行程序代码。
  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止运行。
  5. 死亡状态(Dead):线程执行完了或者因异常退出了 run()方法,该线程结束生命周期。

15、启动线程的方式?start or run?

启动线程的方式为调用线程的 start()方法。

调用线程的 run()方法将不会开启线程,而是在原来的主线程上运行。

16、在 java 中守护线程和本地线程区别?

java 中的线程分为两种:守护线程(Daemon)和用户线程(User)。

任何线程都可以设置为守护线程和用户线程,通过方法 Thread.setDaemon(boolean);true 则把该线程设置为守护线程,反之则为用户线程。Thread.setDaemon()必须在

Thread.start()之前调用,否则运行时会抛出异常。

两者的区别:

唯一的区别是判断虚拟机(JVM)何时离开,Daemon 是为其他线程提供服务,如果全部的 User

Thread 已经撤离,Daemon 没有可服务的线程,JVM 撤离。也可以理解为守护线程是 JVM 自动创建的线程(但不一定),用户线程是程序创建的线程;举例:JVM 的垃圾回收线程是一个守护线程,当所有线程已经撤离,不再产生垃圾,守护线程自然就没事可干了,当垃圾回收线程是 Java 虚拟机上仅剩的线程时,Java 虚拟机会自动离开。

扩展:ThreadDump 打印出来的线程信息,含有 daemon 字样的线程即为守护进程,可能会有:服务守护进程、编译守护进程、windows 下的监听 Ctrl+break的守护进程、Finalizer 守护进程、引用处理守护进程、GC 守护进程。

17、死锁与活锁的区别,死锁与饥饿的区别?

死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

产生死锁的必要条件:

  1. 互斥条件:所谓互斥就是进程在某一时间内独占资源。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。 4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

活锁:任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。

活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。

饥饿:一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。

死锁与饥饿的区别在于,进程会处于饥饿状态是因为持续的有其他优先级更高的进程请求相同的资源,不像死锁,饥饿能够被解开,比如当其他高优先级的进程都终止时并且没有更高优先级的进程到达。

Java 中导致饥饿的原因:

  1. 高优先级线程吞噬所有的低优先级线程的
  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值