【面经】开发相关基础知识

1. JDK和JRE、JVM的区别是什么

JRE(Java运行时环境): Java Runtime Environment

JRE顾名思义是java运行时环境,包含了java虚拟机,java基础类库。是使用java语言编写的程序运行所需要的软件环境,是提供给想运行java程序的用户使用的。

JDK(Java 开发工具包):Java Development Kit

JDK顾名思义是java开发工具包,是程序员使用java语言编写java程序所需的开发工具包,是提供给程序员使用的。JDK包含了JRE,同时还包含了编译java源码的编译器javac,还包含了很多java程序调试和分析的工具:jconsole,jvisualvm等工具软件,还包含了java程序编写所需的文档和demo例子程序。

JVM(Java 虚拟机):Java Virtual Machine

JVM是Java编程语言的核心。当我们运行一个程序时,JVM负责将字节码转换为特定机器代码。JVM也是平台特定的,并提供核心的Java方法,例如内存管理、垃圾回收和安全机制等。JVM 是可定制化的,我们可以通过Java 选项(java options)定制它,比如配置JVM 内存的上下界。JVM之所以被称为虚拟的是因为它提供了一个不依赖于底层操作系统和机器硬件的接口。这种独立于硬件和操作系统的特性正是Java程序可以一次编写多处执行的原因。

JDK, JRE 和JVM的区别

  • JDK是用于开发的而JRE是用于运行Java程序的。
  • JDK和JRE都包含了JVM,从而使得我们可以运行Java程序。
  • JVM是Java编程语言的核心并且具有平台独立性。

2. 进程和线程

2.1 定义

程序

​ 程序是指令和数据的有序集合,其本身没有任何运动的含义,是一个静态的概念,而进程则是在处理机上的一次执行过程,它是一个动态的概念。进程是包含程序的,进程的执行离不开程序,进程中的文本区域就是代码区,也就是程序。

进程(process)

狭义的定义:进程就是一段程序的执行过程。

广义定义:进程是一个具有一定独立功能的程序关于某次数据集合的一次运行活动,它是操作系统分配资源的基本单元。

​ 简单来讲进程的概念主要有两点:第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程中调用的指令和本地变量。第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程。

​ 进程状态:进程有三个状态,就绪,运行和阻塞。就绪状态其实就是获取了除cpu外的所有资源,只要处理器分配资源马上就可以运行。运行态就是获取了处理器分配的资源,程序开始执行,阻塞态,当程序条件不够时,需要等待条件满足时候才能执行,如等待I/O操作的时候,此刻的状态就叫阻塞态。

线程(thread)

​ 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。

2.2进程与线程的区别

​ 进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间一个线程死掉就等于整个进程死掉所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.

2) 线程的划分尺度小于进程,使得多线程程序的并发性高。

3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率

4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

2.3 优缺点

​ 线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP(多核处理机)机器上运行,而进程则可以跨机器迁移。

用一个工厂的形象的解释进程和线程

简书的王布斯

3. 如何预防死锁

3.1 死锁

​ 当线程A持有独占锁a,并尝试去获取独占锁b的同时,线程B持有独占锁b,并尝试获取独占锁a的情况下,就会发生AB两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。

3.2 造成死锁必须达成的4个条件(原因)

  • 互斥:指进程对所分配到的资源进行排他性使用,即一段时间内某资源只能由一个进程占用
  • 请求和保持:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺:指进程已获得的资源在未使用完之前不能被剥夺,只能在使用完时自己释放
  • 循环等待:若干线程之间形成一种头尾相接的循环等待资源关系。

3.3死锁的预防

破坏互斥条件

  • 一般来说在所列的四个条件中,“互斥”条件是无法破坏的

破坏请求和保持条件

  • 一次性分配方案
  • 要求每个进程提出新的资源申请前,释放它所占有的资源

破坏不剥夺条件

  • 如果占有某些资源的一个进程进行进一步资源请求被拒绝,则该进程必须释放它最初占有的资源
  • 进程划分优先级,高优先级进程可以剥夺低优先级进程的资源

破坏循环等待条件

  • 将系统中的所有资源统一编号,进程提出的所有资源申请必须按照资源的编号顺序提出

4. tcp udp 区别

TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

1)提供IP环境下的数据可靠传输(一台计算机发出的字节流会无差错的发往网络上的其他计算机,而且计算机A接收数据包的时候,也会向计算机B回发数据包,这也会产生部分通信量),有效流控,全双工操作(数据在两个方向上能同时传递),多路复用服务,是面向连接,端到端的传输;

2)面向连接:正式通信前必须要与对方建立连接。事先为所发送的数据开辟出连接好的通道,然后再进行数据发送,像打电话。

3)TCP支持的应用协议:Telnet(远程登录)、FTP(文件传输协议)、SMTP(简单邮件传输协议)。TCP用于传输数据量大,可靠性要求高的应用。

UDP(用户数据报协议)是OSI参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。

1)面向非连接的(正式通信前不必与对方建立连接,不管对方状态就直接发送,像短信,QQ),不能提供可靠性、流控、差错恢复功能。UDP用于一次只传送少量数据,可靠性要求低、传输经济等应用。

  1. UDP支持的应用协议:NFS(网络文件系统)、SNMP(简单网络管理系统)、DNS(主域名称系统)、TFTP(通用文件传输协议)等。

总结:

TCP:面向连接、传输可靠(保证数据正确性,保证数据顺序)、用于传输大量数据(流模式)、速度慢,建立连接需要开销较多(时间,系统资源)。

UDP:面向非连接、传输不可靠、用于传输少量数据(数据包模式)、速度快。

5. TCP/IP 协议族常用协议

6. 三次握手四次挥手

6.1 TCP特性

  • TCP 提供一种面向连接的、可靠的字节流服务
  • 在一个 TCP 连接中,仅有两方进行彼此通信。广播和多播不能用于 TCP
  • TCP 使用校验和,确认和重传机制来保证可靠传输
  • TCP 给数据分节进行排序,并使用累积确认保证数据的顺序不变和非重复
  • TCP 使用滑动窗口机制来实现流量控制,通过动态改变窗口的大小进行拥塞控制

那为什么需要三次握手呢?请看如下的过程:

  1. A向B发起建立连接请求:A——>B;
  2. B收到A的发送信号,并且向A发送确认信息:B——>A;
  3. A收到B的确认信号,并向B发送确认信号:A——>B。

三次握手大概就是这么个过程。
通过第一次握手,B知道A能够发送数据。通过第二次握手,A知道B能发送数据。结合第一次握手和第二次握手,A知道B能接收数据。结合第三次握手,B知道A能够接收数据。

至此,完成了握手过程,A知道B能收能发,B知道A能收能发,通信连接至此建立。三次连接是保证可靠的最小握手次数,再多次握手也不能提高通信成功的概率,反而浪费资源。

那为什么需要四次挥手呢?请看如下过程:

  1. A向B发起请求,表示A没有数据要发送了:A——>B;
  2. B向A发送信号,确认A的断开请求请求:B——>A;
  3. B向A发送信号,请求断开连接,表示B没有数据要发送了:B——>A;
  4. A向B发送确认信号,同意断开:A——>B。

B收到确认信号,断开连接,而A在一段时间内没收到B的信号,表明B已经断开了,于是A也断开了连接。至此,完成挥手过程。

可能有捧油会问,为什么2、3次挥手不能合在一次挥手中?那是因为此时A虽然不再发送数据了,但是还可以接收数据,B可能还有数据要发送给A,所以两次挥手不能合并为一次。

挥手次数比握手多一次,是因为握手过程,通信只需要处理连接。而挥手过程,通信需要处理数据+连接

7. 局部变量和函数参数为什么要放在栈中

​ 局部变量,顾名思义其作用域属于局部。全局的变量,意味着谁都随时随地可以访问,所以其放在数据段中。而局部变量只是自己在用,放在数据段中纯属浪费空间,没有必要,故将其放在自己的栈中,随时可以清理,真正体现了局部的意义

​ 函数参数为什么放在栈区呢?第一也是其局限性导致,只有这个函数用这个参数,何必将其放在数据段呢?二是因为函数是在程序执行过程中调用的,属于动态的调用,编译时无法预测何时调用及被调用的次数,函数的参数及返回值都需要内存来存储,如果是递归调用的话,参数及返回值需要的内存空间也就不确定了,这取决于递归的次数。有些体系结构的计算机也把函数参数放到寄存器里面去。

参考:无敌大灰狼me链接

2. 常见设计原则和设计模式

6大设计原则:

  1. 单一职责原则:就是开发人员经常说的”高内聚,低耦合”。也就是说,每个类应该只有一个职责,对外只能提供一种功能,而引起类变化的原因应该只有一个。在设计模式中,所有的设计模式都遵循这一原则。
  2. 开闭原则:一个对象对扩展开放,对修改关闭。也就是说,对类的改动是通过增加代码进行的,而不是修改现有代码。软件开发人员一旦写出了可以运行的代码,就不应该去改动它,而是要保证它能一直运行下去,如何能够做到这一点呢?这就需要借助于抽象和多态,即把可能变化的内容抽象出来,从而使抽象的部分是相对稳定的,而具体的实现则是可以改变和扩展的。
  3. 里氏替换原则:在任何父类出现的地方都可以用它的子类来替代。也就是说,同一个继承体系中的对象应该有共同的行为特征。
  4. 依赖注入原则:要依赖于抽象,不要依赖于具体实现。也就是说,在应用程序中,所有的类如果使用或依赖于其他的类,则应该依赖这些其他类的抽象类,而不是这些其他类的具体类。为了实现这一原则,就要求我们在编程的时候针对抽象类或者接口编程,而不是针对具体实现编程。
  5. 接口分离原则:不应该强迫程序依赖它们不需要使用的方法。也就是说,一个接口不需要提供太多的行为,一个接口应该只提供一种对外的功能,不应该把所有的操作都封装到一个接口中。
  6. 迪米特原则:一个对象应当对其他对象尽可能少的了解。也就是说,降低各个对象之间的耦合,提高系统的可维护性。在模块之间应该只通过接口编程,而不理会模块的内部工作原理,它可以使各个模块耦合度降到最低,促进软件的复用

常见设计模式:

  1. 简单工厂模式

    · 工厂方法模式中抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现。

    ​ · 优点:客户端不需要在负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性。

    ​ · 缺点:需要额外的编写代码,增加了工作量。

  2. 单例设计模式

    单例模式就是要确保类在内存中只有一个对象,该实例必须自动创建,并且对外提供。

    ​ · 优点:在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。

    ​ · 缺点:没有抽象层,因此扩展很难。职责过重,在一定程序上违背了单一职责

  3. 模板设计模式

    · 模版方法模式就是定义一个算法的骨架,而将具体的算法延迟到子类中来实现

    ​ · 优点:使用模版方法模式,在定义算法骨架的同时,可以很灵活的实现具体的算法,满足用户灵活多变的需求

    ​ · 缺点:如果算法骨架有修改的话,则需要修改抽象类

2. volatile关键字作用是什么

英文翻译:不稳定的

弱同步机制

在多线程的化境下,Volatile 修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

数据量过大内存存不下了怎么办