目录

JAVA架构师面试题01

答案

基础题目

Java多线程

        private static class Thread1 implements Runnable
        {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                //由于这里的Thread1和下面的Thread2内部run方法要用同一对象作为监视器,我们这里不能用this,因为在Thread2里面的this和这个Thread1的this不是同一个对象。我们用MultiThread.class这个字节码对象,当前虚拟机里引用这个变量时,指向的都是同一个对象。
                synchronized (MultiThread.class) {

                    System.out.println("enter thread1...");

                    System.out.println("thread1 is waiting");
                    try {
                        //释放锁有两种方式,第一种方式是程序自然离开监视器的范围,也就是离开了synchronized关键字管辖的代码范围,另一种方式就是在synchronized关键字管辖的代码内部调用监视器对象的wait方法。这里,使用wait方法释放锁。
                        MultiThread.class.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                    System.out.println("thread1 is going on...");
                    System.out.println("thread1 is being over!");
                }
            }

        }

        private static class Thread2 implements Runnable
        {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                synchronized (MultiThread.class) {

                    System.out.println("enter thread2...");

                    System.out.println("thread2 notify other thread can release wait status..");
                    //由于notify方法并不释放锁, 即使thread2调用下面的sleep方法休息了10毫秒,但thread1仍然不会执行,因为thread2没有释放锁,所以Thread1无法得不到锁。

                    MultiThread.class.notify();

                    System.out.println("thread2 is sleeping ten millisecond...");
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                    System.out.println("thread2 is going on...");
                    System.out.println("thread2 is being over!");

                }
            }

        }

    }
```

面向对象

堆区:

新生代

  Eden,Survivor1,Survivor2

老年代



3,

  1,枚举根节点 

    一致性,STW,OopMap

  2,safepoint

    让程序长时间执行的特征作为条件

  3,safeRegion

    代码片段中,引用关系不会发生变化

3,垃圾回收器

  

  新生代:Serial,ParNew,Parallel Scavenage, G1

  老年代:CMS,Serial Old(MSC),Parallel Old,G1

  新老搭配关系:

  Serial---->CMS,Serial Old

  ParNew---->CMS,Serial Old(MSC)

  ParallelScanvenge----->Serial Old,Parallel Old

  //------------------------------------------------

  G1---->G1

  CMS---->Serial,ParNew,Serial Old(老)

  Serial Old--->CMS(老),Serial,ParNew,Parallel Scanvenge

  Parallel Old ---->Parallel Scanvenge

  G1-----> G1  



  1,Serial收集器

    最基本的,STW,单线程

    Client下新生代默认的收集器,因为新生代比较小,简单高效

    复制算法

    



  2,ParNew收集器

    Serial的多线程版本

    采用复制算法

    Server模式下首选的新生代收集器,新生代只有ParNew和Serial能配合CMS(老年代)工作

    单核CPU效果不明显

  

  3,Parallel Scavenge收集器

    复制算法,多线程

    吞吐量优先,吞吐量=运行用户代码时间/(运行用户代码时间+GC垃圾收集时间),高吞吐量适合后台运算交互不多的任务

    GC自适应调节策略 -XX:UseGCAdaptiveSizePolicy

  4,Serial Old收集器

    标记-整理算法,单线程

    Client模式下使用,CMS收集器的后备预案

    图见Serial收集器

  5,Parallel Old收集器

    标记-整理算法,多线程

    注重吞吐量和CPU资源敏感的场景,Parallel Scanvenge + Parallel Old

  

  6,CMS收集器

    目标:最短回收停顿时间,注重相应性能

    标记-清除算法

    4个步骤:

      初始标记:STW,标记GC Roots直接关联的对象,很快

      并发标记:GC Roots Tracing,耗时长,可以用用户线程并发

      重新标记:修正并发期间用户线程产生的标记变动,时间短于并发标记,长于初始标记

      并发清除:可以用用户线程并发

    优点:并发收集,低停顿

    缺点:

      对CPU资源比较敏感,CPU数量少的时候,占用较多CPU资源,改进算法i-CMS(然并卵)

      CMS无法处理浮动垃圾,并发清理时用户线程也在生成垃圾,在Concurrent Mode Failed时,后备预案,临时启用Serial Old来收集老年代,时间变长

      大量空间碎片,标记-清除算法的通病,内存碎片的合并整理

  



  7,G1收集器

    目标是替换调CMS

    整体来看是标记-整理算法

    特点:

      并行与并发:多CPU缩短STW时间,用户程序仍在并发

      分代收集:单独的算法处理新创建对象和老对象

      空间整合:Region局部采用复制算法,不会有空间碎片

      可预测停顿:可预测停顿时间模型,降低停顿时间

    内存布局的变化:没有新生代和老年代的区别,所有都划分为等大小的Region区,新老不再物理隔离

    避免全区域垃圾回收,根据Region判断回收价值,回收价值最大的Region(G1),有限时间获取更高的回收效率

    可达性判断问题更突出,采用Region的RemeberSet来处理

    回收过程:

      初始标记

      并发标记

      最终标记:并发标记将对象变化记录在线程的Remebered Set Log,在这个阶段将RemeberSetLog中的变化记录到RememberSet中

      筛选回收

    

4,内存分配和回收策略

  在堆(Heap)上分配(JIT标量类型间接在栈上分配),主要是在Eden区上分配,TLAB,少数直接在老年代分配

  对象优先在Eden区分配(分配)

    Eden区空间不足,触发Minor GC

    新生代(Eden区+一个Survivor区)

    新生代GC:Minor GC

    老年代GC:Full GC/Major GC,Full GC经常伴随至少一次Minor GC   

  大对象直接进入老年代(分配)

    -XX:PretenureSizeThreshold,大于这个参数值的对象直接分配在老年代(只对ParNew和Serial收集器有效)

  长期存活的对象将进入老年代

    对象年龄计数器,每熬过一次Minor GC,则年龄计数器+1,达到阈值(-XX:MaxTenuringThreshold,默认15)则对象晋升到老年代

  动态对象年龄判断

    Survivor中相同年龄X的对象大小总和 > 1/2 Survivor空间,则对象年龄>=X的对象直接进入老年代

  空间分配担保
2,主内存和工作内存交互操作

  交互协议:8种操作,每种操作原子化,不可以细分

  lock

  unlock

#####################

  read---load

  store-write

#####################

  use

  assign

  交互操作原则:8个规则



  volatile的特性

    可见性,不能保证原子性(所以并发下并非线程安全)

    禁止指令重排序优化:内存屏障,指令重排序

  volatile变量的特殊规则:read,load,use,assign,store,write的特殊规则

  

  long和double的特殊规则:

  64位的数据类型

  非原子性协定

  

3,

原子性,基本类型数据操作为原子性的,更大范围的使用lock/unlock--->monitorenter/monitorexit--->synchronized关键字

可见性,变量修改后将新值同步会主内存,在变量读取前从主内存刷新变量值,共享主内存这种方式实现可见性

  volatile变量的区别是新值能立即同步会主存,每次使用前立即从主存刷新,但是线程操作数栈中的变量不会立即刷新

有序性

  线程内表现为串行语意

  指令重排序

  工作内存与主存同步延迟

  volatile保证有序性:禁止指令重排序语意

  synchronized保证有序性:一个变量在同一时刻只允许一个线程对其lock操作

4,先行发生原则(happens-before):8个

  程序次序规则:同一线程,控制流顺序

  管程锁定规则:同一个锁,unlock先于lock

  volatile变量规则:同一个变量,写先于读

  线程启动规则

  线程终止规则

  线程中断规则

  对象终结规则

  传递性:

技术深度

那么线程池的原理就是通过一个计数器和work队列来维护线程池的大小

每创建一个Thread(通过Work来包装的从queue队列中取出来的Runnable实现),则以CAS的方式增加计数器,并将新增的work放入work队列

在Work的执行过程中,异常退出,则计数器-1,并且重新生成一个work来继续处理

技术框架

系统架构

分布式系统

综上所述:

集群+负载均衡

增加缓存

系统拆分

分库分表

垂直拆分+水平拆分

异步化+MQ

实战能力

软能力