一、JAVA原理
1.1 String、StringBuffer、StringBuilder区别以及使用场景
- String是一个不可变对象
- StringBuffer是用synchronize修饰,线程安全。
- StringBuilder效率比较高。
1.2 Class初始化的过程
- 类的初始化过程
执行类构造器的方法,由编译器自动收集所有的类变量的赋值动作和静态代码块合并产生。- 静态变量、静态代码块>普通成员变量、初始化代码块>构造器
1.3 ConcurrentHashMap的底层原理是什么?
1.8
- 1.8 使用的是数组加链表加红黑树。相比于1.7之前的锁整段,1.8锁住头结点
1.4 GC是如何判断对象被回收了?
- 引用计数法
每一个对象都有一个引用属性,每增加一个引用就加一,引用释放减一,为0时可以被GC回收,但是无法解决循环引用的问题。- 可达性分析算法
从GCROOT向下搜索,搜索走过的路为引用连,当一个对象到GCROOT没有引用链,则对象是不可用的,可以进行GC回收。
GCROOT:虚拟机栈的引用对象、方法区中静态属性引用的对象、方法区常量引用的对象、本地方法栈中引用的对象。- 引用类型
强引用:我们所new出来的都是强引用,强引用不会被gc。
软引用:软引用指向的对象在jvm空间不足会gc。
弱引用:什么时候都会被gc。
无引用:相当于没有,直接被回收。
1.5 JAVA的类加载器有哪些?
- Bootstrap类加载器
启动类加载器主要是加载JVM自身所需要的的类,这个类加载使用C++实现的- Extension类加载器
扩展类加载器,由java语言实现。它用来加载 Java 的扩展库。- Application类加载器
应用类加载器。一般来说,Java 应用的类都是由它来完成加载的。- Custom类加载器
自定义加载器。
双亲委派:
1.6 JVM内存模型如何分配?
JVM内存分为堆(唯一共享)、虚拟机栈、本地方法栈、程序计数器、方法区(唯一共享)。
1.8与1.7的区别是,1.8將永久代变为元空间放在本地内存,而1.7永久代的对象是放在堆内。
- 虚拟机栈
- 本地方法栈
为JVM运行Native方法,大多Native是用C语言实现的。- PC寄存器计数器(程序计数器)
JVM支持多线程运行,每个线程都有自己的程序计数器,如果执行的是JVM方法,寄存器保存当期那执行指令的地址。- 堆
除了堆是共享的,其他都是一个线程对应一个。
堆分为新生代(Eden、From,to)老年代。- 方法区(方法区是个概念,具体实现是永久代)
方法区是线程共享的、主要存储虚拟机加载的类信息、常量池、方法数据、方法代码等。
1.7 synchronize和lock的区别 ?
- synchronize是一个关键字,lock是一个接口。
- synchronize在异常的时候回自动释放锁,lock不会,需要通过try-catch-finally,在finally代码块unlock()。
- lock可以使用interrupt来中断等待,synchronize只能等待锁的释放。
1.8 ThreadLocal的原理?使用场景?
- 作用
提供线程内的局部变量,不同的线程不会相互干扰。能在同一个线程内传递数据,不需要使用参数传递从而达到解耦。- 常用方法
- synchronize和ThreadLocal的区别
- ThreadLocal的原理
(1) 每一个Thread里面都有一个ThreadLocalMap
(2)map对象存储了ThreadLocal对象(key)和线程的变量副本(value)
(3)Thread内部的Map是由ThreadLocal进行维护。
- ThreadLocal内部结构
- 成员变量
(1)INITIAL_CAPACITY为Map的初始容量
(2)table为一个Entry类型的数组
(3)size为表中存储大小
(4)threshold为需要扩容的阈值- 内存泄漏
当我们使用完ThreadLocal后,ThreadLocalRef引用删除后,如果没有删除Entry,那么当前线程仍然执行、CurrentThreadRef还是存在,那么强引用链依然存在,虽然key是null但是value存在值,并且再也无法访问到,导致内存泄漏。
1.8 创建线程的方式?
- 继承Thread类
- 使用Runnble接口继承创建线程
- 使用Callable和Future创建线程
Callable相对于Runnble ,call()可以有返回值,并且可以声明抛出异常。Future为异步的结果,可以通过get()获取结果,isDone()获取执行是否完成。- 我们可以通过Executors来创建线程池,通过submitl或execute执行线程,前者有返回值。
1.9 线程的生命周期?
1.10 为什么使用线程池?
- 创建线程需要大量内存块,所以需要用线程池缓存。
- 提高性能。线程池在执行大量的异步任务时候,可以尽可能使用空闲的线程进行异步任务的执行,最大对线程进行复用。
- 方便管理。线程池会保存线程的相关信息以及空闲的线程以及完成任务的数量。
1.11 垃圾回收算法?
- 标记-清除算法
标记所有需要回收的对象,标记完后统一清除。- 复制算法
把存活下来的复制到to区,from区全部清除。- 标记-整理算法
标记整理和标记清除一样,只不过是标记完后把存活对象移到一端,直接清除端外的内存。- 分代收集算法
分代收集算法是根据对象的存活时间分为新生代和老年代。当对象存活较少就采用复制算法比较高效,存活较多就需要使用标记清除或者标记整理算法。