java开发面试问答----基础篇

1,875 阅读9分钟

重写和重载的区别

  • 重写(Override)是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变
  • 重载(overloading)是在一个类里面,方法名字相同,而参数不同。

抽象类和接口有什么区别

  • 抽象类是类的抽象,目的在于找出类的共同点,抽象类的不同点,接口是行为的抽象,每个行为的具体实现都是不同的。
  • 实现上,抽象类可以有构造函数和普通成员变量
  • 一个类只能继承一个抽象类,但是可以实现多个接口

反射的用途和实现

  • 反射用于在jvm运行过程中动态的获取对象的类信息,通常用于框架开发如spring
    • 判断任意一个对象所属的类
    • 构造任意一个类的对象
    • 判断任意一个类所具有的成员变量和方法,包括private,比如idea的输入提示
    • 调用任意一个对象的方法
  • 实现可以通过对象的getclass方法获取类,或者使用class包的class.forname通过类路径找到类,然后可以使用class的newInstance方法创建新的对象,也可以通过获取类的constructor对象使用指定的构造函数创建对象。

int 和 Integer 有什么区别

  • Integer是int的包装类,int则是java的一种基本数据类型
  • Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
  • Integer的默认值是null,int的默认值是0

equals 和 == 有什么区别

  • == 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。比较的是真正意义上的指针操作
  • equals用来比较的是两个对象的内容是否相等

Integer使用==和int型比较为什么在127以下是相等的而128不等

当Integer和int比较时,java会自动对int装箱(Integer.valueOf),由于大多数int比较都在128以下,因此java将-128到127的数放入了缓存,返回的是缓存中的同一个对象而不是新的对象

类加载,隔离机制

类加载的过程

类加载主要有三个过程,装载,连接,初始化;装载是指查找和导入class文件到内存中,连接则是根据class二进制数据生成对象,这里会进行类的校验保证数据符合jvm规范,静态变量内存分配和符号引用的解析;初始化则是类的初始化操作,这里会进行静态变量和静态代码块的初始化,执行类的构造器。

双亲委派机制

jvm中有三类加载器,启动类加载器,扩展类加载器和应用类加载器,双亲委派机制是说当应用类加载器需要加载一个类时不会直接加载而是到父加载器中查找,找不到就找祖加载器,只有都找不到时才会自己加载。

隔离机制

每个类加载器都有自己的加载范围以保证内存隔离,对于同一个类的评判标准是:类名一致,类加载器一致,类加载器实例一致。

如果黑客手写了一个string.class用自定义加载器加载会生效吗

不会生效,因为父类加载器已经加载了string.class,当其他类加载时会直接加载系统的string.class

多线程环境如何保证只有一个class对象

扩展类加载器在加载类时使用了concurrentHashMap和synchronized,concurrentHashMap保证了只有一个线程能往里面放对象,而synchronized则保证了加载时只有一个类能被加载

如何破坏双亲委派

自定义类加载器通常是继承扩展类加载器,实现自身findclass方法,如果要破坏双亲委派则要重写loadclass方法

序列化方式

有两种方式,一种是使用OutputStream类的wirteobject方法,二种是实现Serializable接口

如何判断一个文件是否存在,如何读取一个目录下的所有文件和子目录

创建file类对象,调用exist方法判断是否存在,并使用isfile方法判断是否是文件还是目录,目录的话可以使用listfile列出目录下所有文件和子目录

nio aio bio

  • bio,同步阻塞io:客户端发起一个请求,服务端起一个线程处理请求
  • nio,同步非阻塞io:客户端发起一个请求时,服务端生成一个channel建立连接,channel注册到多路复用器上,selector扫描channel,当channel有数据写到buffer时扔给线程处理
  • aio,异步非阻塞io:客户端发起一个请求时,服务端标记这个io,当系统处理完时回调服务端,服务端处理这个请求

select poll epoll

三种系统的io处理模式,他们都是同步io,使用的多路复用器,当有流需要处理时必须自己处理。

  • select 和nio很像,当有流需要io处理时,轮训多路复用器上所有的文件描述符,每个文件描述符对应一个socket,找到需要处理的流
  • poll 是对select的优化,select由于在用户空间操作,每个端口对应的连接数有限制,而poll是将数据移到内核空间处理,只需要检查文件描述符对应的设备状态即可,因为处理链表存储fd因此没有连接数的限制
  • epoll 则是对poll的优化,当有流进来时会生成io事件通知,然后进程处理对应的io事件,当fd就绪时内核会通过回调激活fd

堆,栈,gc

jvm的作用

java代码编译成.class存在操作系统兼容问题,而jdk是根据操作系统安装的,所以jdk中的jvm加载class文件可以屏蔽掉操作系统的兼容性问题

jmm是什么

jmm是java内存模型,和cpu硬件结构相似,每个线程有自己的工作内存空间,并与主内存交互,当有新的变量时首先写入主内存然后拷贝到工作内存,好处是可以提高处理速度,并且线程之间内存隔离。

堆,栈

堆存放动态产生的数据,如new的对象,而栈存放局部变量,包括基础数据类型的值和对象的引用

一次gc的过程

堆分为新生代,老年代,永久代(常量池,元数据),java 内存分配和回收的策略是分代分配分代回收,java young gc采用的是停止-复制清理法。新生代分为eden区和两个存活区,当一个对象被创建时首先分配到eden区中,当eden区满时触发young gc将消亡的对象清理掉并把存活的对象复制到存活区0;之后,young gc在清理eden的同时清理存活区0,把存活的对象复制到存活区1,此时存活区0清空;如此反复,两个存活区总有一个是空的,每切换一次存活对象的年龄加1,当多次切换后(默认年龄是8)仍存活的对象将复制到老年代;当老年代满时触发full gc。如果存入eden区的对象非常大,超过eden区的大小则会直接放入老年代。

如何主动触发gc

jvm判断是否是垃圾对象用的是可达性分析法,判断这个对象是否有引用; 使用system.gc主动触发full gc;

jvm相关命令jstak,jmap,jstat,如何dump内存

  • jstat:内存管理工具,可以查看gc和当前内存使用统计情况;
  • jstack:用来跟踪堆栈,分析当前程序的运行情况 ,可以dump出当前的堆栈信息
  • jmap:可以dump出堆信息,分析当前gc情况。

排查频繁gc

  • 思路:引起gc的原因通常有2类,一类是内存泄漏,一类是大对象存储,因此可以从这2个方向去排查问题
    • 首先jmap dump出内存文件,jstack dump出堆栈信息
    • 使用MAT工具分析内存dump文件,找到大量创建的对象和内存占用最大的对象
    • 到堆栈dump信息中找寻问题对象相关的堆栈信息还原现场
    • 优化代码,针对内存泄漏修改代码逻辑及时释放对象,针对大对象要分拆

数据结构

ArrayList, LinkedList, SynchronizedList and Vector, CopyAndWriteArrayList

ArrayList, LinkedList 和Vector都实现了List接口,区别在于:

  • ArrayList内部实际上是一个数组,当更多元素加入时,其大小会动态的增长,每次增长50%,在使用时如果能预估数组的大小进行初始化,能大幅减少调整容量的开销。
  • LinkedList内部是一个双链表,其和ArrayList相比优势在于增删操作比较快,而get和set操作比较慢
  • Vector和ArrayList一样内部是一个数组,区别在于Vector是线程安全的,当Vector数据增长时默认每次扩容一倍但是长度可控。
  • SynchronizedList和Vector一样是线程安全的,他的区别在于可以将任意List转成线程安全的类,但是缺点在于其内部实现机制上遍历操作不是线程安全的需要手动加锁。
  • CopyAndWriteArrayList是对vector做的优化,vector使用Synchronized锁住读写操作,而CopyAndWriteArrayList不锁读操作,写操作时使用lock上锁当写操作完成时替换整个array。

HashMap原理

  • hashmap concurrenthashmap
    • hashmap
      • java7是由数组和链表组成的,每一个数组元素存的是entry
      • get: 根据key的hash值找到数组的位置,然后比较链表中每个元素的key得到value
      • put: 根据key的hash值找到数组的位置,然后比较链表中是否存在该key,没有存在则将该 entry存入链表
      • resize: hashmap初始化时有数组容量和负载因子,负载因子默认0.75,当数组中存放个数大 于容量*负载因子时进行扩容操作容量翻倍
      • java8加入红黑树,当链表长度大于8时会变成红黑树
    • concurrenthashmap
      • 采用分段设计,每个段其实就是一个加了同步锁的hashmap
  • ConcurrentHashMap 和 hashtable
    • hashtable同步会锁住整个数据,而ConcurrentHashMap采用分段设计,每次只会锁住一段数据