我叫目录
- 2019年面试题
- 1.请谈谈对volatile的理解
- 2.CAS(CompareAndSet)比较并交换
- 3.原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗?
- 4.ArrayList是线程不安全,请编码写一个不安全的案例,并给出解决方案
- 5.公平锁、非公平锁、可重入锁、自旋锁谈谈你的理解?并手写一个自旋锁
- 6.CountDownLatch/CyclicBarrier/Semaphore使用过吗?
- 7.阻塞队列知道吗?
- 8.线程池用过吗?ThreadPoolExcutor谈谈你的理解
- 9.线程池用过吗?生产上你如何设置合理参数
- 10.死锁编码及定位分析
- 1. JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC Roots
- 2.JVM调优和参数配置,如何查看盘点查看JVM系统默认值
- 3.平时工作中常用的JVM基本参数
- 4.强引用、软引用、弱引用、虚引用分别是什么?
- 5.谈谈对OOM的认识
- 6.GC垃圾回收算法和垃圾收集器的关系?分别是什么?
- 7.怎么查看服务器默认的垃圾收集器是哪个?生产上如何配置垃圾收集器?谈谈你对垃圾收集器的理解?
- 8.G1垃圾收集器
- 9.生产环境服务器慢,诊断思路和性能评估谈一谈?
- 10.假如生产环境出现cpu占用过高,请你谈谈分析思路和定位
- 11.GITHUB
2019年面试题
周老师讲课深入浅出,抽象化的概念会讲的比较通俗具象化,学习效果会比自己看书好很多,陆陆续续看了一个多月,终于看完了,估计以后肯定会忘,所以做了笔记以备日后复习。顺便不再做伸手党,自己分享出去一些知识。
1.请谈谈对volatile的理解
- 1.1 volatile是java虚拟机提供的轻量级的同步机制
1.1.1保证可见性
1.1.2不保证原子性
1.1.3禁止指令重排 - 1.2JMM(Java内存模型Java Memory Model,简称JMM)本身是一种
抽象的概念并不真实存在,它描述的是一组规则或规范,通过这
组规范定义了程序中各个变量(包括实例字段、静态字段和构成
数组对象的元素)的访问方式。- JMM关于同步的规定:
- 1.线程解锁前,必须把共享变量的值刷新回主内存
- 2.线程加锁前,必须读取主内存的最新值到自己的工作内存
- 3.加锁解锁是同一把锁
由于JVM运行程序的实体是线程,而每个线程创建是JVm都会为其创建一个工作内存(有些地方成为栈空间),工作内存是每个线程的私有数据区域,而java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,**但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再降变量写回主内存,**不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,其简单访问过程如下图:
可见性、原子性、有序性
- 双重检查单例模式
- JMM关于同步的规定:
class SingleTon{
private static SingleTon instance;
private SingleTon (){
System.out.println(Thread.currentThread().getName()+"----我是DCL,双重检查,听说极小概率是线程不安全的");
}
public static SingleTon getInstance(){
if ( instance == null) {
synchronized (SingleTon.class){
if (instance == null) {
instance = new SingleTon();
}
}
}
return instance;
}
}
DCL(双端检测)机制不一定线程安全,原因是指令重排序的存在,加入volatile可以禁止指令重排
原因在于一个线程执行到第一次检测,读取到instance不为null时,instance的引用对象可能没有完成初始化。
instance = new SingleTon();可以分成以下3步完成(伪代码)
memory = allocate();1.分配对象内存空间
instance(memory);2.初始化对象
instance = memory;3.设置instance指向刚分配的内存地址,此时instance!= null
步骤2和步骤3不存在依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变。
因此这种重排优化是允许的。
memory = allocate();1.分配对象内存空间
instance = memory;3.设置instance指向刚分配的内存地址,此时instance!= null ,但是对象还没有初始化完成!
instance(memory);2.初始化对象
但是指令重排只会保证串行语义的执行的一致性(单线程),但并不关心多线程间的语义一致性。所以当一条线程访问instance不为空时,由于instance实例未必已经初始化完成,也就造成了线程安全问题。
**线程安全版**
class SingleTon{
private static **volatile** SingleTon instance;
private SingleTon (){
System.out.println(Thread.currentThread().getName()+"----我是DCL,双重检查,听说极小概率是线程不安全的");
}
public static SingleTon getInstance(){
if ( instance == null) {
synchronized (SingleTon.class){
if (instance == null) {
instance = new SingleTon();
}
}
}
return instance;
}
}
2.CAS(CompareAndSet)比较并交换
- atomicInteger.getAndIncredment()
- unSafe
- CAS是什么
- CAS缺点
-
循环时间长开销很大
-
只能保证一个共享变量的原子操作
-
引出来ABA问题
-
3.原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗?
- ABA问题
4.ArrayList是线程不安全,请编码写一个不安全的案例,并给出解决方案
new ArrayList();创建一个object类型的空数组,有add操作后会创建一个容量为10的相应类型的数组。扩容为原来的1.5倍。
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
for (int i = 0; i <50 ; i++) {
new Thread( () -> {
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
// java.util.ConcurrentModificationException 并发修改异常
}
1.故障现象
java.util.ConcurrentModificationException 并发修改异常
2.导致原因
并发修改导致的线程不安全问题
3.解决方案
new Vector<>(); 不推荐使用,加了锁,影响性能
Collections.synchronizedList(new ArrayList<>());
new CopyOnWriteArrayList<>();
4.优化建议(同样的错误不犯第二次)
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
5.公平锁、非公平锁、可重入锁、自旋锁谈谈你的理解?并手写一个自旋锁
- 公平锁和非公平锁
区别 - 可重入锁(递归锁)
指的是同一线程外层函数获得锁之后,内存够递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁
也即是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。 - 自旋锁(spinlock)
是指尝试获取的线程不会立即阻塞,而是采取循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU
- 手写自旋锁
public class SpinLock {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"come in ");
while (!atomicReference.compareAndSet(null,thread)){
}
}
public void unLock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(thread.getName()+"go out");
}
public static void main(String[] args) {
SpinLock spinLock = new SpinLock();
new Thread(()-> {
spinLock.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLock.unLock();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()-> {
spinLock.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLock.unLock();
},"B").start();
}
}
- 独占锁(写锁)、共享锁(读锁)、互斥锁
多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行。
但是
如果有一个线程想去写共享资源类,就不应该再有其他线程可以对该资源进行读或写
总结:读-读能共存、读-写不能共存、写-写不能共存。
public static void main(String[] args) {
MyLock myLock = new MyLock();
for (int i = 1; i <= 5 ; i++) {
final int tmpInt = i;
new Thread(()->{
myLock.put(String.valueOf(tmpInt),tmpInt);
},String.valueOf(tmpInt)).start();
}
// try {
// TimeUnit.SECONDS.sleep(1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
for (int i = 1; i <= 5 ; i++) {
final int tmpInt = i;
new Thread(()->{
myLock.get(String.valueOf(tmpInt));
},String.valueOf(tmpInt)).start();
}
}
}
class MyLock{
private volatile Map<String,Object> mapCache = new HashMap();
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void put(String key,Object value){
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "开始写入:"+ key);
try {TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) {e.printStackTrace();}
mapCache.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入完成");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.writeLock().unlock();
}
}
public void get(String key){
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "开始读取");
try {TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) {e.printStackTrace();}
System.out.println(Thread.currentThread().getName()+"读取完成"+ mapCache.get(key));
}catch (Exception e){
e.printStackTrace();
}finally {
lock.readLock().unlock();
}
}
6.CountDownLatch/CyclicBarrier/Semaphore使用过吗?
- countDowLatch
班长最后走,关门
public static void main(String[] args) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i <10 ; i++) {
new Thread(()-> {
System.out.println(Thread.currentThread().getName()+"离开教室");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"班长离开教室,锁门了");
}
秦统一六国枚举类
public static void main(String[] args) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <=6 ; i++) {
new Thread(()-> {
System.out.println(Thread.currentThread().getName()+"国,被灭");
countDownLatch.countDown();
},SixGuo.getGuo(i).reGuo).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"秦国一统天下");
System.out.println(SixGuo.ONE);
System.out.println(SixGuo.ONE.getReCode());
System.out.println(SixGuo.ONE.getReGuo());
}
public enum SixGuo{
ONE(1,"齐"),TWO(2,"楚"),THREE(3,"燕"),FOUR(4,"赵"),FIVE(5,"魏"),SIX(6,"韩");
private Integer reCode;
private String reGuo;
public Integer getReCode() {
return reCode;
}
public String getReGuo() {
return reGuo;
}
SixGuo(Integer reCode, String reGuo) {
this.reCode = reCode;
this.reGuo = reGuo;
}
public static SixGuo getGuo(int index){
SixGuo[] sixGuos = SixGuo.values();
for (SixGuo sixGuo : sixGuos){
if (sixGuo.getReCode()==index) {
return sixGuo;
}
}
return null;
}
}
- CyclicBarrier
和countdownlatch是相反的操作,可以理解为人到齐了再开会
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("召唤神龙");
});
for (int i = 0; i <7 ; i++) {
final int tmpInt = i;
new Thread(()->{
System.out.println("收集到第"+tmpInt+"颗");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
- Semaphore
可以理解为多辆车抢车位,注定会有车没有车位
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i <6 ; i++) {
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢占车位");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"释放车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},String.valueOf(i)).start();
}
}
7.阻塞队列知道吗?
blockingqueue的核心方法
SynchronousQueue
- 用在哪里(多线程:高内聚低耦合场景下,线程操作资源类,多个操作一个要加锁。判断,干活,通知)
- 生产者和消费者
初版生消
- 生产者和消费者
public class ProdConsummer_old {
public static void main(String[] args) {
changeData changeData = new changeData();
new Thread(()->{
for (int i = 0; i <5 ; i++) {
try {
changeData.increment();
} catch (Exception e) {
e.printStackTrace();
}
}
},"AAA").start();
new Thread(()->{
for (int i = 0; i <5 ; i++) {
try {
changeData.decrement();
} catch (Exception e) {
e.printStackTrace();
}
}
},"BBB").start();
}
}
class changeData{
private int number = 0;
private Lock reentrantLock = new ReentrantLock();
private Condition condition = reentrantLock.newCondition();
public void increment() throws Exception{
reentrantLock.lock();
try {
while (number != 0){
condition.await();
}
number ++ ;
System.out.println(Thread.currentThread().getName()+"\t"+ number);
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
reentrantLock.unlock();
}
}
public void decrement() throws Exception{
reentrantLock.lock();
try {
while (number == 0){
condition.await();
}
number -- ;
System.out.println(Thread.currentThread().getName()+"\t"+ number);
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
reentrantLock.unlock();
}
}
}
synchronized和lock有什么区别?用新的lock有什么好处?举例说说
1.原始构成
synchronized是关键字属于jvm层面,
monitorenter(底层是通过monitor对象来完成,其实wait、notify等方法也是依赖于monitor对象只有在同步块或方法中才能调wait、notify等方法)
monitorexit
lock是具体类(java.util.concurrent.Locks.lock)是api层面的锁
2.使用方法
synchronized不需要用户手动释放锁,当synchronized代码执行完后系统会自动让线程释放对锁的占用
reentrantlock则需要手动释放锁若没有主动释放锁,就有可能导致出现死锁现象(需要lock()和unlock()方法配合try、finally语句块来完成)
3.等待是否可中断
synchronized不可中断,除非抛出异常或者正常运行完成
reentrantlock可中断,
1.设置超时方法trylock(Long timeout ,TimeUtil unit)
2.lockInterruptibly()放代码块中,调用interrupt()方法可中断
4.加锁是否公平
synchronized非公平锁(非公平锁第一次未抢到资源,会变成公平锁去排队)
reentrantlock两者都可以,默认公平锁,构造方法可以传入Boolean值,true为公平锁,false为非公平锁
5.锁板顶多个条件condition
synchronized没有
reentrantlock用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized要么随即唤醒一个线程要么唤醒全部线程。
8.线程池用过吗?ThreadPoolExcutor谈谈你的理解
- 为什么用线程池,优势
- 线程池如何使用?
底层都是ThreadPoolExecutors - 线程池的几个重要参数介绍?
4.说说线程池的底层工作原理?
9.线程池用过吗?生产上你如何设置合理参数
- 是什么
等待队列也已经满了,再也塞不下新任务了,同时,
线城池中的max线城也达到了,无法继续为新任务服务。
这个时候我们就需要拒绝策略机制合理的处理这个问题 - jdk内置的拒绝策略
- 如何创建线程池,是否自定义过线程池
- 合理线程池你是如何考虑的?
调用Runtime.getRuntime().availableProcessors();- cpu密集型
- IO密集型
- 由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,cpu数*2
- cpu密集型
10.死锁编码及定位分析
- 是什么
原因:系统资源不足、进程进行推进的顺序不合适、资源分配不当 - 代码
class LockThread implements Runnable{
private String lockA;
private String lockB;
public LockThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+"\t 持有"+lockA + "试图获取"+ lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+"\t 持有"+lockB + "试图获取"+ lockA);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class DeadLock {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new LockThread(lockA,lockB),"AAAA").start();
new Thread(new LockThread(lockB,lockA),"BBBB").start();
}
}
调查死锁命令
1.jps(相当于linux系统下的ps -ef |grep XXXXX)是window下的java PS
定位进程号
2.jstack找到死锁查看
JVM相关
-
JVM体系结构
-
GC作用域
-
常见的垃圾回收算法
引用计数
复制
标记清除
标记整理
1. JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC Roots
- 垃圾:简单说就是内存中不再被使用到的空间
- 要进行垃圾回收,如何判断一个对象是否可以被回收?
- 引用计数法,但是无法避免循环引用问题,所以不用这种方法
- 枚举根节点做可达性分析(根搜索路径)
- 可以作为GC Roots的对象
- 虚拟机栈(栈帧中的局部变量区,也叫局部变量表)中引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(Native方法)引用的对象。
2.JVM调优和参数配置,如何查看盘点查看JVM系统默认值
- JVM参数类型
- 标配参数
-version
-help
java -showversion
- X参数(了解)
-Xint:解释执行
-Xcomp:第一次使用就编译成本地代码
-Xmixed:混合模式
- XX参数
1.Boolean类型
公式:-XX:+ 或者- 某个属性值 (+ 表示开启,- 表示关闭)
举例:
是否打印GC收集细节:-XX:-PrintGCDetails /-XX:+PrintGCDetails
是否使用串行垃圾回收器:-XX:-UserSerialGC/-XX:+UserSerialGC
2.KV设值类型
公式:-XX:属性key=属性值value
例子:
-XX :MetaspaceSize=128m
-XX:MaxTenuringThreshold=15
3.jinfo举例,如何查看当前运行程序的配置
4.题外话(坑)
两个经典参数:-Xms和-Xmx
-Xms 等价于 -XX:InitialHeapSize
-Xmx 等价于 -XX:MaxHeapSize
盘点参数家底
第一种
jps
jinfo -flag 具体参数 java进程编号
jinfo -flags java进程编号
第二种
java -xx:+PrintFlagsInitial (查看初始值)
java -xx:+PrintFlagsFinal (查看最终值)
第三种
java -XX:+PrintCommandLineFlags 打印命令行参数
3.平时工作中常用的JVM基本参数
- 基础知识复习
- 常用参数
-
-Xms
- 初始大小内存,默认为物理内存1/64
- 等价于:-XX:InitialHeapSize
-
-Xmx
- 最大分配内存,默认为物理内存1/4
- 等价于-XX:MaxHeapSize
-
-Xss
- 设置单个线程栈的大小,一般默认为512k~1024k
- 等价于-XX:ThreadStackSize
- 查出来是0,表示用的是默认初始值
-
-Xmn
- 设置年轻代大小(一般用默认就可以,不用调)
-
-XX:MetaspaceSize
- 元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:**元空间并不在虚拟机中,而是使用本地内存。**因此,默认情况下,元空间的大小仅受本地内存限制。
- -Xms10m -Xmx10m -XX:MetaspaceSize=1024m -XX:+PrintFlagsFinal
-
-XX:+PrintGCDetails
-
输出详细GC手机日志信息
-
GC
-
FullGC
-
-
-XX:SurvivorRatio
- 设置新生代中eden和s0和s1空间的比例,默认-XX:SurvivoRatio8,Eden:s0:s1=8:1:1
- 加入 -XX:SurvivorRatio=4,Eden:s0:s1=4:1:1
- SurvivoRatio值就是设置eden区的比例占多少
-
-XX:NewRatio
- 配置年轻代与老年代在堆结构的占比,默认-XX:NewRatio=2新生代占1,老年代2,年轻代占整个堆的1/3
- 假如 -XX:NewRatio=4 新生代占1,老年代4,年轻代占整个堆的1/5 NewRatio值就是设置老年代的占比,剩下的1给新生代
-
-XX:MaxTenuringThreshold
- 设置垃圾的最大年龄,范围是从1-15,默认是15
-
4.强引用、软引用、弱引用、虚引用分别是什么?
- 整体架构
- 强引用(默认支持模式)
- 软引用
- 弱引用
弱引用需要用java.lang.WeakReference类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,㕆JVM的内存空间是否足够,都会回收该对象占用的内存。
应用场景:
假如有一个应用需要读取大量的本地图片:
* 如果每次读取图片都从硬盘读取则会严重影响性能,
* 如果一次性全部加载到内存中又可能造成内存溢出
此时使用软引用或者弱引用可以解决这个问题
设计思路:用一个hashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题
Map<String,SoftReference<Bitmap>> imageCache = new HashMap<String,SoftReference<Bitmap>>();
weakhashmap案例演示和分析
- 虚引用
GCroot和四种引用总结
5.谈谈对OOM的认识
- java.lang.StackOverflowError(栈管运行,堆管对象)
深度方法调用(递归的调用方法,但是没有结束标识) - java.lang.OutOfMemoryError:Java heap space
创建大对象,new byte[8010241024] - java.lang.OutOfMemoryError:GC overhead limit exceeded
- java.lang.OutOfMemoryError:unable to create new native thread
调整生成线程上限
- java.lang.OutOfMemoryError:Metaspace
6.GC垃圾回收算法和垃圾收集器的关系?分别是什么?
- GC算法(引用计数、复制、标记清理、标记整理或标记压缩)是内存回收的方法论,垃圾收集器就是方法论的落地。
- 因为目前为止还没有完美的收集器出现,更加没有万能的收集器,只是针对具体应用最合适的收集器,进行分代收集
- 四种主要的垃圾收集器
串行、并行、并发、G1
- 串行垃圾回收器(Serial):它为单线程环境设计且只是用一个线程进行垃圾回收,会暂停所有的用户线程。所以不适合服务器环境
- 并行垃圾回收器(Parallel):多个垃圾收集线程并行工作,此时用户线程是暂停的,适用于科学计算、大数据处理 等弱交互场景
- 并发垃圾回收器(CMS):用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程互联网公司多用它,适用对响应时间有要求的场景
- G1垃圾回收器:G1垃圾回收器将堆内存分割成不同的区域然后并发的对其进行垃圾回收
7.怎么查看服务器默认的垃圾收集器是哪个?生产上如何配置垃圾收集器?谈谈你对垃圾收集器的理解?
- 查看方式
- 有哪些GC垃圾收集器
- 部分参数预先说明
- DefNew:Default New Generation
- Tenured:Old
- ParNew:Parallel New Generation
- PSYongGen:Parallel Scavenge
- ParOldGen:Parallel Old Generation
- server/client模式
- 新生代
-
串行GC(serial)/(serial Copying)
-
并行GC(ParNew)
-
并行回收GC(Parallel)/(Parallel Scavenge)
-
- 老年代
-
串行GC(Serial Old)/(serial MSC)
-
并行GC(Paraller Old)/(Parallel MSC)
-
并发标记清除GC(CMS)
-
- 如何选择垃圾收集器
8.G1垃圾收集器
- G1是什么
- 特点
- 底层原理
-
region区域垃圾收集器
最大的好处是化整为零,避免全内存扫描,只需要按照区域进行扫描即可
-
回收步骤
-
4步过程
-
- 和cms相比的优势
9.生产环境服务器慢,诊断思路和性能评估谈一谈?
-
整机:top
主要看两点,一时cup和内存,而是看load average(一分钟,5分钟,10分钟的平均值,需要三个数相加求平均值,如果大于60%就是负责过大)
精简版命令 uptimes -
cpu:vmstat
-
内存:free
-
硬盘:df
df -h (h是以人类看得懂的方式打印出来,既以G为单位) -
磁盘IO:iostat
-
网络IO:ifstat
10.假如生产环境出现cpu占用过高,请你谈谈分析思路和定位
- 结合linux和jdk命令一起分析
-
先用top命令找出cpu占比做高的
-
ps -ef 或jps进一步定位,得知是一个怎样的后台程序造成
-
定位到具体线程或者代码
-
将需要的线程ID装换成16进制格式(英文小写格式)
printf"%x\n" 有问题的线程id -
jstack进程ID | grep tid(16进制线程小写英文) -A60
-
- 案例步骤
11.GITHUB
-
in
XX关键词 in:name或description或readme -
stars/fork
springboot stars:>=5000
springboot fork:>=5000
-
awesome加强搜索
公式:awesome 关键字 (awesome系列一般是用来收集学习、工作、书籍类相关的项目) -
高亮显示某一行代码
-
项目内搜索
-
搜索某个地区内的大佬