栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 前沿技术 > 大数据 > 大数据系统

Java 面试总结

Java 面试总结

java 基础 说说你对jvm中类加载过程的理解?


源码.java —>(javac编辑器)—>字节码.class —> 类加载器(JVM)—>运行时数据区(JVM)—>执行引擎(JVM) —>机器码 —>机器识别处理

java中的集合类有哪些?

Collection List

对于实现了RandomAccess 接口的list 是可以实现随机访问(RandomAccess 类型作为判断是否可以随机访问的标识)

public static  int binarySearch(List> list, T key) {
 if (list instanceof RandomAccess || list.size() 

总结:实现了RandomAccess 接口的list,优先选择普通for循环,其次选择foreach,未实现RandomAccess 接口的list,优先选择Iterator遍历,foreach 底层也是使用Iterator 实现,大数据量前往不要使用for遍历

ArrayList
  1. 底层:使用Object [] 数组来存储数据
  2. 特点:数组在内存中是一块连续的内存空间
  3. 优点:由于属于数组可以直接使用下标找到对应数组位置的元素,所以对于随机查询很有优势
  4. 缺点:由于内存空间的连续性,在添加和删除元素操作的时候需要移动部分元素的空间位置,所以效率比较低下。
  5. 内存消耗:由于它们使用的扩容机制决定了用数组来存储数据会浪费数组未实际存储的空元素位置的内存空间
linkedList
  1. 底层:底层使用链表的方式( transient Node first ) 来存储数据(jdk1.6之前使用循环链表,jdk1.6之后改为双向链表)
  2. 特点:链表中的节点在内存中不要求连续性,是散列分布在内存中。每个节点利用首尾指针进行连接成线性结构
  3. 优点:由于节点是使用首尾指针域连接前驱和后继结点,所以在对链表进行添加/删除元素操作的时候只需要改变目标节点前驱和后继结点的指针域即可
  4. 缺点:由于链表式结构办法使用下标快速随机查找,所以每次查找都要遍历一遍链表才行
  5. 内存消耗:每个结点都会因为使用首尾指针域来连接该节点的前驱/后继结点,造成内存消耗
Vector
  1. 底层:底层都是使用Object [] 数组来存储数据
  2. 初始值:两者在没有设置初始容量的时候,均是默认初始化一个长度为10 的Object 数组
  3. 扩容:ArrayList (int newCapacity = oldCapacity + (oldCapacity >> 1) 每次扩容都是原来数组长度的1.5倍。Vector (int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity) 每次扩容都是原来数组长度的2倍
  4. 安全性:ArrayList 是线程不安全的,Vector 的操作方法都使用synchronized 同步锁修饰,所以线程安全。
Set HashSet
  1. 底层:使用 HashMap map 来存储数据(准确来说是使用 HashMap 的 key 来存储数据)
  2. 特点:元素不重复,无顺序
  3. 优点:可以实现集合内元素的自动去重
  4. 缺点:
  5. 内存消耗:
linkedHashSet
  1. 底层:底层是TreeMap。
  2. 特点:提供一个使用树结构存储Set接口的实现,对象以升序顺序存储,访问和遍历的时间很快。
  3. 优点:有顺序
  4. 缺点:
  5. 内存消耗:
TreeSet
  1. 底层:继承自 HashSet
  2. 特点:以元素插入的顺序来维护集合的链接表,允许以插入的顺序在集合中迭代; 底层是HashMap。
  3. 优点:有顺序
  4. 缺点:
  5. 内存消耗:
Queue linkedList

双向队列

PriorityQueue
  1. 底层:使用 Object[] queue 来存储数据
  2. 特点:实质上维护了一个有序列表。加入到 Queue 中的元素根据它们的天然排序(通过其 java.util.Comparable 实现)或者根据传递给构造函数的 java.util.Comparator 实现来定位。
  3. 优点:有顺序
  4. 缺点:
  5. 内存消耗:
Map HashMap 底层实现:
  1. jdk1.8之前:使用数组和链表的方式结合在一次使用也就是链表散列( transient Node[] table; ),即数组中每个元素都是一条链表的首节点。
  2. jdk1.8之后:在链表散列的基础上做了增强,即当链表的长度大于8的时候链表就会自动转为红黑树结构存储,数组中的每个元素都为一棵红黑树的根结点
存储过程:一个key/value执行put 操作后,把key计算出hashcode。hashcode % length = 数组上的存储位置(当length为2的幂次方时候,hashcode % length == (length-1) & hashcode)。
// 一般计算hashcode方式:
public int hashCode() {
	int h = hash;
 	if (h == 0 && value.length > 0) {
 		char val[] = value;
		for (int i = 0; i < value.length; i++) {
	 		h = 31 * h + val[i];    // 以31为权,每一位为字符的ASCII值进行运算,用自然溢出来等效取模
	 		// 因为计算机对乘法和除法的计算性能低下,一般使用移位法代替乘/除法计算提升性能。使用31可以得到更好的性能: 31 * i == (i << 5) - i
 		}
 		hash = h;
 	}
 	return h;
 }
 // HashMap 中的扰动函数hash:
 static final int hash(Object key) {
 	int h;
 	return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
 	// 采用无符号右移16位和异或运算是为了降低哈希碰撞
 }
扩容机制:
HashMap 默认初始化容量是16,加载因子0.75,即16 * 0.75 = 12 当前容量已使用12 个的时候会触发扩容机制,因为取模寻位的公式:hashcode % length == (length-1) & hashcode,公式成立的前提是length为2的幂次方。计算机采用二进制按位与(&)操作相对于做除法取模(%)能够提升性能,所以每次扩容都是原来的两倍。
加载因子(loadFactor):
  1. jdk默认是 static final float DEFAULT_LOAD_FACTOR = 0.75f;
  2. 它是控制数组中数据的疏密程度的,数值范围在 0 - 1 之间,数值越大表示数组越密集。
  3. 思考:为什么jdk 默认是0.75?
    3.1. 太大:很难触发扩容机制,则数组的密集度太高不利于查询数据元素
    3.2. 太小:扩容太过频繁,则数组存放的数据太过分散,内存空间利用率太低
HashTable 描述:

HashTable是较为远古的使用Hash算法的容器结构了,现在基本已被淘汰,单线程转为使用HashMap,多线程使用ConcurrentHashMap。

继承体系:

HashTable也是一种key-value结构,它继承自Dictionary,实现了Map和Cloneable以及Serializable接口。

扩容:

HashTable底层数组长度可以为任意值,这就造成了hash算法散射不均匀,容易造成hash冲突,默认为11;

hash映射:

Hash映射:HashMap的hash算法通过非常规设计,将底层table长度设计为2的幂,使用位与运算代替取模运算,减少运算消耗;而HashTable的hash算法首先使得hash值小于整型数最大值,再通过取模进行散射运算;

hashmap和hashtable的区别? 安全性:

HashTable 内部方法基本上都用synchronized 同步锁修饰,所以线程安全。HashMap 没有使用同步锁,所以线程不安全

效率:

HashTable使用了同步锁的数据结构在线程并发的情况下需要线程等待,所以效率低下。HashMap 是为了追求高效率,牺牲安全性。key/value要求:HashMap 允许key 只能有一个null,value 可以有一个或多个。HashTable 中若是添加了一个key = null,就会报NullPointerException

扩容机制:

HashMap 扩容见上。HashTable 在没有指定初始容量的时候,默认容量为11,加载因子0.75。每次扩容都是2 * n + 1(n为原来容量)

底层数据结构:

HashMap 见上。HashTable 使用数组 + 链表的方式,不会自动转化为红黑树的机制

HashMap 和 HashSet 比较:
  1. HashSet 底层是使用HashMap 来存储数据。因为HashSet 是对象型存储,所以每次添加对象都是存放到HashMap的key位置,而value 则是存放一个常量(private static final Object PRESENT = new Object()。
  2. 正是因为HashMap 中每个key都是唯一的,所以HashSet 存储的对象都是去重的。
  3. 性能上HashMap 要比HashSet 高效率。
HashTable 和 ConcurrentHashMap 比较: 安全性:

因为HashMap 线程不安全,而线程安全的HashTable 又效率低下,所以ConcurrentHashMap 就是为了解决HashMap 线程不安全性和HashTable 效率低下问题。

锁机制:

HashTable 使用synchronized 修饰操作方法,这种方式是全表锁(在任意时刻同步锁只能被一个线程获取,其他线程等待其释放锁) ConcurrentHashMap 并不是直接使用synchronized 锁住方法

jdk1.8之前:

使用分段锁的方式达到线程安全,把整个数据集拆分成多个segment(每个segment 中存在一个链表节点元素的HashEntry[]),数据结构segment 继承了ReentrantLock 可重用锁来实现并发控制。

jdk1.8之后:

使用synchronized + CAS(乐观锁的一种,采用版本号的方式实现并发控制) 的方式控制线程安全,直接对数组元素/链表首节点/红黑树根结点上锁。这样只要不发生hash冲突,有并发现象,效率得到提高。

CAS 机制: 定义:

CAS(Compare And Swap)比较和交换机制。java平台对这种操作机制做了封装,在Unsafe 类下:如,unsafe.compareAndSwapInt(this, valueOffset, expect, update);这其中有三个重要的参数:valueOffset——内存位置,expect——旧预期值,update——新交换值

思想:

它的思想源自乐观锁的一种,采用版本号的方式实现并发控制。每个线程操作共享数据的时候都维持一个自己的版本号,关键就是获取版本号操作时必须要求原子性,否则没办法保证并发控制。

public final int incrementAndGet() {
 	return unsafe.getAndAddInt(this, valueOffset, 1) + 1;   // 获取当前版本号+1,并返回
}
public final int getAndAddInt(Object var1, long var2, int var4) {
 	int var5;
 	do {
 		var5 = this.getIntVolatile(var1, var2);     // 采用volatile 关键字保证每次获得的旧预期值都是最新的
 	} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));    // 重试机制
	return var5;
}
流程:

CAS操作时,先用valueOffset 读取内存中的版本号,再和自身的旧预期值进行比较。true:把内存位置的值换成新交换值,false:不作任何处理

public final boolean weakCompareAndSet(int expect, int update) {
 	return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
重试机制(循环CAS):

由上面的代码可知为了保证原子性java的原子类中使用了一个死循环进行CAS操作。成功了就跳出循环体返回,失败了就重新从内存中读取旧预期值和重新设计更新值直到成功为止。

ABA问题:

CAS 保证了比较和交换的原子性。但是从读取到开始比较这段期间,其他核心仍然是可以修改这个值的。如果核心将 A 修改为 B,CAS 可以判断出来。但是如果核心将 A 修改为 B 再修改回 A。那么 CAS 会认为这个值并没有被改变,从而继续操作。这是和实际情况不符的。解决方案是加一个版本号。

synchronized 底层实现:java 对象头 + monitor 机制 对象在内存中有三部分:

对象头、实例数据、对齐填充

对象头:

ClassmetadataAddress——类型指针(jvm就是通过这个查询该对象属于哪个类型的实例)、MarkWord——标记字段(标记哈希码、锁状态、GC年龄代等)

monitor:

它是有三部分组成owner、EntryList、WaitSet

owner:

EntryList中竞争胜利者,记录当前锁拥有者

EntryList:

等待队列,存放多线程并发锁竞争中的失败者,等待下一次获得锁

WaitSet:

挂起队列,存放owner 中执行了wait() 方法后进入等待队列

操作机制:

多线程并发竞争锁时,这些线程都会进入到EntryList 等待队列中。只有一个竞争胜利者可以进入owner(获得锁)执行,执行完毕走出owner(释放锁)。EntryList 中剩下的线程就会再次竞争选出以为胜利者进入owner 。当在owner 中的线程执行了wait() 方法该线程就会进入WaitSet 挂起队列。等待被通知(执行notify())后再次进入EntryList。

JVM 对 synchronized 的处理: synchronized 修饰代码块:

编译器会把synchronized 翻译成monitorenter(获取锁) 和 monitorexit(释放锁) 两个指令,分别放于代码块的开始和结尾的位置。

synchronized 修饰方法:

编译器会为方法生成一个ACC_SYNCHRonIZED 标志,jvm 根据这个标志来判断是否需要同步。

说说Java的反射的优点与缺点 什么是反射

指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能调用它的任意一个方法.这种动态获取信息,以及动态调用对象方法的功能叫java语言的反射机制.

原理

优点:

在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

缺点:
  1. 反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
  2. 反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
说说反射可以应用在哪些场景? 反编译:

.class–>.java

动态访问:

通过反射机制访问java对象的属性,方法,构造方法等

动态加载:

反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,他们可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。

Class.forName("com.mysql.jdbc.Driver"); // 动态加载mysql驱动
说说怎么通过反射获取类中的一个静态变量?
public static int key = 10;

public static void main(String[] args) {
    try {
        // 通过反射加载类信息
        Class aClass = Class.forName("cn.chaoyou.interview.Reflect");
        // 根据指定属性名称初始化一个属性对象
        Field field = aClass.getField("key");
        // 从属性对象中获取属性值
        int fieldValue = field.getInt(null);
        System.out.println(fieldValue);
        // 从属性对象中设置属性值
        field.set(null, 5);
        fieldValue = field.getInt(null);
        System.out.println(fieldValue);
    } catch (Exception e){
        e.printStackTrace();
    }
}
说说对象是什么时候被jdk的gc回收的?

手动执行System.gc()方法会触发Full GC(非常不建议)

新生区
  1. 程序空闲的时候会执行 Minor GC
  2. Eden内存空间不够用的时候会触发Minor GC
老年代
  1. 内存空间不足以接收新时代要晋升到老年代对象的时候会触发Full GC
  2. 当对一个大数组对象分配空间的时候,在老年代中找不到一段那么大的连续空间的时候会触发Full GC
方法区
  1. 内存空间不足的时候会触发CMS GC
Java中有哪些常用的线程安全的容器 同步容器类:
  1. HashTable
  2. Vector
并发容器:
  1. ConcurrentHashMap/ConcurrentHashSet(分段,底层哈希实现的同步Map(Set)。效率高,线程安全。使用系统底层技术实现线程安全。量级较synchronized低。key和value不能为null)
Sorted容器:
  1. ConcurrentSkipListMap/ConcurrentSkipListSet(底层跳表(SkipList)实现的同步Map(Set)。有序,效率比ConcurrentHashMap稍低。)
List:
  1. CopyOnWriteArrayList/CopyOnWriteArraySet(写时复制集合。写入效率低,读取效率高。每次写入数据,都会创建一个新的底层数组。)
Queue:
  1. ConcurrentlinkedQueue(基础链表同步队列。)
  2. linkedBlockingQueue(阻塞队列,队列容量不足自动阻塞,队列容量为0自动阻塞。)
  3. ArrayBlockingQueue(底层数组实现的有界队列。自动阻塞。根据调用API(add/put/offer)不同,有不同特性。)
  4. SynchronusQueue(同步队列,是一个容量为0的队列。是一个特殊的TransferQueue。必须现有消费线程等待,才能使用的队列。)
说说静态变量在项目中有哪些作用? 类变量

一种是被static修饰的变量;

实例变量

一种是没有被static修饰的变量;

两者区别
  1. 对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。
  2. 对于实例变量,每创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。
  3. 所以一般在需要实现以下两个功能时使用静态变量:
    3.1. 对象之间共享值时
    3.2. 方便访问变量时
数据共享

static int = arraySize = 100;

全局常量

static final String CODE = “ABCD”;

Jdk1.8的接口的default和static方法?
  1. 用于提供一套默认的实现
  2. 实现类对于该方法就不需要强制来实现
  3. 实现类可以选择使用默认的实现,也可以重写自己的实现
  4. 当实现类为接口扩展方法时,只需要提供该方法的默认实现即可
  5. 实现类不会报语法错误:Xxx不是抽象的, 并且未覆盖Yxx中的抽象方法。
  6. static方法使用类名即可调用,default方法需要使用实例对象才能调用
项目是怎么处理一些业务类型的报错信息的?
  1. 自定义一些运行时异常
public class BusinessException extends RuntimeException {
  public BusinessException(String message) {
    super(message);
  }

  public BusinessException() {
  }
}
说说你jvm中双亲委派机制的理解? 类加载器

类加载阶段中“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作的实现代码块称之为“类加载器”

启动类加载器

主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。

扩展类加载器

主要负责加载jre/lib/ext目录下的一些扩展的jar。

应用程序类加载器

主要负责加载应用程序的主函数类

双亲委派机制

打开“java.lang”包下的ClassLoader类。然后将代码翻到loadClass方法:

public Class loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}
//              -----??-----
protected Class loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
        // 首先,检查是否已经被类加载器加载过
        Class c = findLoadedClass(name);
        if (c == null) {
            try {
                // 存在父加载器,递归的交由父加载器
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    // 直到最上面的Bootstrap类加载器
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                c = findClass(name);
            }
        }
        return c;
}

其实这段代码已经很好的解释了双亲委派机制,为了大家更容易理解,我做了一张图来描述一下上面这段代码的流程:

工作原理
  1. 当一个类加载器收到了类加载的请求。
  2. 类加载器先判断该类是否已经被当前类加载器完成加载了
    2.1. 如果已经被加载,则停止向双亲委派
    2.2. 如果没有被加载,则把类加载请求委派给双亲(父类加载器)处理
  3. 递归 2 过程,直到到达Bootstrap classLoader之前,都是在检查是否已加载过,并不会选择自己去加载。
  4. 直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了
  5. 如果自己无法加载,会下沉到子加载器去加载,一直到最底层
  6. 如果没有任何加载器能加载,就会抛出ClassNotFoundException。
为什么要设计这种机制

这种设计有个好处是防止一个类被多个类加载器加载而造成在系统中出现多个不同的类,到后面应用程序都不知道到底使用哪个类比较合适。双亲委派机制存在时,不管在哪个类加载器收到类加载请求,都会首先委派父类加载器进行类加载。最终类加载请求会到达BootstrapClassLoader(启动类加载器)执行,由于都是在同一个加载器执行操作,加载之前先判断先前是否已经加载过了,所以基本不会出现一个类被加载出多个不同的类。

说说jdk和jre的区别?

jdk

java development kit(java 开发工具包),主要包含了:jre、java源码的编译器javac、监控工具jconsole、分析工具jvisualvm

jre

java runable environment(java 运行环境),包含了java虚拟机,java基础类库

在什么情况下会出现内存泄露和内存溢出问题? 内存泄漏(Memory Leak)

就是申请了内存,无法释放已申请的内存空间,导致内存空间浪费。通俗说法就是有人占着茅坑不拉屎。

内存溢出(Out Of Memory,OOM)

就是申请内存时,JVM没有足够的内存空间。通俗说法就是去蹲坑发现坑位满了。

两者之间的关系

两者是不同的概念,大量的内存泄漏可能会引起内存溢出(内存泄漏积累到堆无法为新执行的程序分配内存时,就会出现内存溢出)

常见的内存溢出案例 加载图片或音频过大,超出申请的内存
  1. 对图片进行压缩处理(不推荐,图片多起来,你再怎么压缩也是要耗很大的内存)
  2. 使用第三方加载图片框架(推荐,开源,省时又省事)Glide ,Picasso ,Fresco等
  3. 减少Bitmap对象的引用,并及时的回收
对象引用没及时回收,导致堆积,超出所申请的内存
  1. 动态回收内存
  2. 对像引用采用软引用(方便内能够对此进行回收)
  3. 对象复用,存在的对象不要重复多次new它,应该循环利用
  4. 注意对象复用的生命周期(static和程序进程一样长)
  5. 单例模式的合理使用,单例模式避免重复创建对象,但也注意他的生命周期和程序进程一样长容易因为持有的对象没有正常回收导致内存泄漏
  6. 监听器不使用时及时注销
  7. 尽量减少抽象对象的使用
程序造成死循环或者循环过多
  1. 避免在循环中创建对象
一次性查询大量数据到内存中
  1. 尽量执行分批查询操作,避免全量查询SQL
避免内存溢出方法
  1. 使用字符串处理,避免使用String,应大量使用StringBuffer,每一个String对象都得独立占用内存一块区域
  2. 尽量少用静态变量,因为静态变量存放在永久代(方法区),永久代基本不参与垃圾回收
  3. 适量为堆分配足够大的内存
常见的内存泄漏情况

Java内存泄露根本原因是什么呢?长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。

静态集合类引起内存泄露

像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。

解决方案:

集合对象用完之后要及时清空集合中元素,并且把集合对象设置为null。

当集合里面对象的属性被修改,再调用remove()方法时不起作用。

当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段,否则对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中删除当前对象,造成内存泄露。

监听器

在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。

解决方法:

监听器用完要及时关闭

各种连接

比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去的连接,在finally里面释放连接。

单例模式

不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,导致内存泄露,

其他引发内存泄漏
  1. 流对象使用完之后没有关闭
  2. 线程未终止造成内存泄漏
怎么排查内存泄漏问题?

某个业务系统在一段时间突然变慢,我们怀疑是因为出现内存泄露问题导致的,于是踏上排查之路。

确定频繁Full GC现象
  1. 使用ps命令找到PID
ps aux | grep 进程名字
  1. 利用“虚拟机统计信息监视工具:jstat”监视虚拟机各种运行状态信息
# 意思是每1000毫秒查询一次,一直查。gcutil的意思是已使用空间站总空间的百分比
jstat -gcutil PID 1000

结果如下:S0(survivor0)、S1(survivor1)、E(Eden)、O(老年代)、M(方法区)、CCS(类空间)、YGC(年轻代gc次数)、YGCT(年轻代gc耗时)、FGC(老年代gc次数)、FGCT(老年代gc耗时)、GCT(堆总gc耗时)

找出导致频繁Full GC的原因 分析方法通常有两种:
  1. 把堆dump下来再用MAT等工具进行分析,但dump堆要花较长的时间,并且文件巨大,再从服务器上拖回本地导入工具,这个过程有些折腾,不到万不得已最好别这么干。
  2. 更轻量级的在线分析,使用“Java内存影像工具:jmap”生成堆转储快照(一般称为headdump或dump文件)。
    2.1. 利用jmap初步分析内存映射
# 主要是找出对象的引用出现了未被垃圾回收收集,通知开发人员优化相关代码。
jmap -histo:live PID | head -7


3. 如果上面一步还无法定位到关键信息,那么需要拿到heap dump,生成离线文件。

jmap -dump:live,format=b,file=~/heap.hprof PID
  1. 拿到heap dump文件,利用 idea 插件visualVM来分析heap profile。
    4.1.
线程安全 有哪些实现多线程的方式? 继承Thread类

继承Thread类,重写run()方法,创建Thread对象调用start()方法启动线程。

public class ThreadDemo extends Thread {
    @Override
    public void run() {
        int t = 1;
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + (t++));
        }
    }

    public static void main(String[] args) {
        ThreadDemo td1 = new ThreadDemo();
        ThreadDemo td2 = new ThreadDemo();
        td1.setName("Thread1");
        td2.setName("Thread2");
        td1.start();
        td2.start();
    }
}
实现Runnable接口

实现Runnable接口,实现run()方法,接口的实现类的实例作为Thread的target传入带参的Thread构造函数,调用start()方法启动线程。

public class RunnableDemo implements Runnable {
    @Override
    public void run() {
        int t = 1;
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + (t++));
        }
    }

    public static void main(String[] args) {
        RunnableDemo rd = new RunnableDemo();
        Thread tr1 = new Thread(rd);
        Thread tr2 = new Thread(rd);
        tr1.setName("Thread1");
        tr2.setName("Thread2");
        tr1.start();
        tr2.start();
    }
}
}
Callable和FutureTask创建线程实现方式
  1. 创建Callable接口的实现类 ,并实现Call方法;
  2. 创建Callable实现类的实现,使用FutureTask类包装Callable对象,该FutureTask对象封装了Callable对象的Call方法的返回值 ;
  3. 使用FutureTask对象作为Thread对象的target创建并启动线程;
  4. 调用FutureTask对象的get()来获取子线程执行结束的返回值。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableFutureTaskDemo implements Callable {
    @Override
    public Integer call() throws Exception {
        int t = 1;
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + (t++));
        }
        return t;
    }

    public static void main(String[] args) {
        Callable cftd1 = new CallableFutureTaskDemo();
        Callable cftd2 = new CallableFutureTaskDemo();
        FutureTask ft1 = new FutureTask<>(cftd1);
        FutureTask ft2 = new FutureTask<>(cftd2);
        Thread t1 = new Thread(ft1);
        Thread t2 = new Thread(ft2);
        t1.setName("Thread1");
        t2.setName("Thread2");
        t1.start();
        t2.start();
        try {
            System.out.println(ft1.get());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
线程池实现方式
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorDemo implements Runnable {
    private static int task_num = 2;  //任务数量

    @Override
    public void run() {
        int t = 1;
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + (t++));
        }
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i  
怎么保证线程的安全性? 
volatile关键的作用? 
volatile是怎么保证可见性的? 
什么场景下需要考虑线程安全问题? 
MySQL 
mysql怎么进行性能优化的? 
什么情况下会造成索引失效? 
按数据结构区分有多少种索引? 
除了建立索引还有哪些方式可以进行性能优化? 
说说你对mysql事务的理解? 
Mysql中为什么要创建索引? 
Mysql中的索引为什么使用的是B树? 
怎么监测数据库查询效率低下? 
说说你对事务特性的理解 
说说你对事务隔离级别的理解 
什么隔离级别下会产生幻读? 
可重复读的实现原理是什么? 
事务传播属性有哪些? 
  1. PROPAGATION_REQUIRED(需要):如果存在一个事务,则支持当前事务,如果没有事务则开启一个新的事务
  2. PROPAGATION_SUPPORTS(支持):如果存在一个事务,则支持当前事务,如果没有事务则非事务
  3. PROPAGATION_MANDATORY(必要的):如果存在一个事务,则支持当前事务,如果没有一个活动的事务则则抛出异常
  4. PROPAGATION_REQUIRES_NEW(总是开启一个新的):总是开启一个新的事务,如果已经存在一个活动的事务,则把当前事务挂起。
  5. PROPAGATION_NOT_SUPPORTED(不支持):总是非事务的执行,并挂起任何存在的事务。
  6. PROPAGATION_NEVER(局部):总是非事务的执行,如果存在一个活动的事务,则抛出异常
  7. PROPAGATION_NESTED(嵌套):如果存在一个活动的事务,则运行在一个嵌套的事务中,如果没有活动事务,则按
mybatis mybatis的mapper类下的方法可以重载吗? SpringMVC springmvc是怎么接收一个long[]? spring 说说对spring中bean生命周期的理解? 实例化

一个Bean --也就是我们常说的new

设置属性
  1. 如果这个Bean已经实现了BeanNameAware接口,会调用接口的setBeanName(String)方法,此处传递的就是这个spring配置文件中定义的bean的id值。
  2. 如果这个Bean实现了BeanFactoryAware接口的话,会调用接口的setBeanFactory(BeanFactory)方法,该方法传递的是Spring工厂本身(可以用这个方法来获取其他Bean,只需在Spring配置文件中配置一个普通的Bean就可以)。
  3. 如果这个BEan实现了ApplicationContextAware接口的话,会调用setApplicationContext(applicationContext)方法,传入spring的上下文(该方法也可以实现方法4中的步骤,但比4更好,因为ApplicationContext是BeanFactory的子接口,有更多的实现方法)。
  4. 如果这个Bean关联了BeanPostProcessor接口,将会调用该接口的postProcessBeforeInitialization(Obejct obj,String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用这个方法,也可以被应用在内存或者缓存技术。
初始化

initializeBean进行Bean初始化,包括Aware系列接口调用,BeanPostProcessor前置方法,然后是相关的初始化方法,比如@Bean的init-method、

销毁

当Bean不再被需要的时候,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法

怎么解决循环加载问题?

转载至详细说明

生成代理对象产生的循环依赖
  1. 使用@Lazy注解,延迟加载
  2. 使用@DependsOn注解,指定加载先后关系
  3. 修改文件名称,改变循环依赖类的加载顺序
使用@DependsOn产生的循环依赖

这类循环依赖问题要找到@DependsOn注解循环依赖的地方,迫使它不循环依赖就可以解决问题。

多例循环依赖

这类循环依赖问题可以通过把bean改成单例的解决。

说说对spring的AOP和IOC的理解? AOP(面向切面编程) 核心原理:

使用动态代理的设计模式在执行前后或出现异常后做相关逻辑操作。

使用场景:
  1. 事务处理:执行方法开启事务,执行完成后关闭事务,出现异常后回滚事务(jdbc 事务)
  2. 权限判断:在方法执行前做出用户权限的判断
  3. 日志记录:在方法执行前后均做系统运行日志的记录
IOC( Inversion of Controller)控制反转(工厂模式) 原理:我的service 需要调用Dao ,service 就需要自己手动创建Dao Spring:Spring 发现service 依赖于Dao,就给service 自动注入Dao(前提是这个Dao 已经被注册到Spring容器中了) 核心原理:就是配置文件 + 反射 + 容器(map) 依赖注入: 构造器注入
  1. 优点:有效减少代码量
  2. 缺点:大量参数值需要注入的情况下会降低代码的可读性
setter方法注入
  1. 优点:会大大增加代码的灵活性和可读性
  2. 缺点:产生大量的代码
说说spring中的AOP是怎么实现的 springboot springboot有哪些优势? springboot是怎么实现配置信息的自动加载? 说说你在项目中遇到了那些问题?都是怎么解决的? springboot中@Transaction是怎么实现的? springboot怎么实现自动装配机制? 缓存中间件 redis有哪些数据类型? 说说对redis中缓存穿透、缓存击穿和缓存雪崩的理解? 说说redis中的哨兵模式机制是怎么实现的? 消息队列 说说RabbitMQ是用来做什么业务的? Hadoop 说说Hadoop中对HDFS的理解? elasticsearch 说说你的elasticsearch集群的部署方案?

集群由三个节点组成(由于节点数少所以使用公用版ES集群设置,三个节点均可参与选举master、作为数据节点、作为搜索节点)。每个索引拆分为三个分片(shard)、索引副本数是2,即每个索引有6个分片(3主 + 6副),所以每个节点分配到两个分片(一主一副,不能为同一份数据)。这样的集群可以保证在一个节点宕机之后还能正常运行,容灾能力只有65%,这样将大大提高es集群的高可用性。

说说elasticsearch集群中的主从复制是怎么配置的?

把同一个索引的主分片(shard)和副分片(replica)分配到不同的节点上,可以在elasticsearch.yml配置文件中设置参数

  1. cluster.routing.allocation.awareness.attributes: rack_one # 一个副本
  2. cluster.routing.allocation.awareness.attributes: rack_one # 两个副本
说说elasticsearch中节点和分片的理解?
  1. 集群,一个ES集群由一个或多个节点(Node)组成,每个集群都有一个cluster name作为标识。
  2. 集群,一个ES集群由一个或多个节点(Node)组成,集群中的所有节点都有独立的node_name作为标识,这个集群的所有节点的cluster_name都是一样的。
  3. 分片,ES是分布式搜索引擎,每个索引有一个或多个分片,索引的数据被分配到各个分片上,相当于一桶水用了N个杯子装。分片分为主分片和副分片两种,副分片作为主分片的副本。
说说elasticsearch中索引的mapping设置原理?
{
  "order": 0,
  "index_patterns": [
    "classify_two*"
  ],
  "settings": {
    "index": {
      "max_result_window": "100000000",
      "refresh_interval": "1s",
      "number_of_shards": "3",
      "number_of_replicas": "1",
     "analysis": {
        "analyzer": {
          "default": {
            "type": "ik_smart"
          }
        }
      }
    }
  },
  "mappings": {
    "data": {
      "properties": {
        "FIELD": {
          "type": "keyword"
        },
      }
    }
  },
  "aliases": {
    "classify_two": {}
  }
}
说说在项目使用elasticsearch的API有哪些?
  1. BoolQueryBuilder
  2. SearchSourceBuilder
  3. SearchRequest
  4. RestHighLevelClient
说说elasticsearch中match、match_phrase和term查询的区别?
  1. match查询会把条件做分词处理(模糊查询,只要一部分词匹配上就行了,无顺序要求)
  2. match_phrase查询会把条件做分词处理(精确查询,必须所有词匹配上才行,且顺序不能变)
  3. term查询不会进行分词处理,match查询keyword字段和term效果一样的。
Elasticsearch集群怎么应对数据量的增长问题? 操作系统 说说你对 Linux 的管道知识的理解 设计模式 说说你对设计模式中动态代理的理解 其他 传统的web项目中session是怎么实现? 向面试官提问
  1. 请问贵公司的这个岗位需要什么样的人才呢?
  2. 面试过程中你觉得我有哪些需要加强的方面呢?
  3. 如果有幸加入贵公司,贵公司对我有哪些方面的提升呢?
  4. 贵公司对新员工的培训体系是什么样的呢?
  5. 公司的核心价值观或者理念是什么?
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/654341.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号