java面试问题总结

JVM
1、JVM运行时包含哪些区域?
    线程公共的:堆(存放一些对象)、方法区(存放一些类信息、静态变量、常量)
    线程私有的:程序计数器(单线程下计数比如循环、异常处理等跳转、多线程下线程之间的跳转)、java虚拟机栈(java方法)、本地方法栈(非java方法)
2、栈:每个线程创建,分配的一块内存区域。每执行一个方法,生成一个栈帧(存放方法的局部变量等信息),进栈,出栈
3、stackoverflow:指定单个线程虚拟机栈大小,当压栈的方法过多,单个线程请求的栈深度大于允许深度,抛出异常,比如无条件的递归函数
4、outofmenory:整个虚拟机栈内存耗尽
5、java堆内存分代策略:新生代、老年代、永久代(1.8取消老年代)
    新生成的对象进入新生代,多次回收失败的进入老年代(大对象直接进入)、静态变量和类信息进入永久代。
    1.8取消老年代,使用元空间代替,老年代和元空间都是方法区的实现,老年代使用的是堆内存,元空间属于本地空间,只存储类的元信息(运行时常量),静态和常量(字符串常量)都还存储在堆内存。
    相当于永久代的数据被分到了堆和元空间中。
6、jvm创建对象步骤:
        1、检查对象加载,没有加载先加载
        2、分配内存,内存规整的话使用指针碰撞,不规整使用空闲列表,是否规则取决于GC算法,标记清除是不规则的,标记整理和复制算法是规则的
        3、初始化零值,比如long、int之类
        4、初始化对象头,比如类信息、哈希码、gc的年龄代信息
        5、执行构造函数初始化对象
7、jvm存在强引用(不会被回收)、软引用(只有在内存不足的时候被回收)、弱引用(gc扫描到就回收)、虚引用(随时被回收)
8、GC三个算法:
    1、标记、清理算法
    2、复制算法,新生代gc使用,分为eden和survivor1 survivor2,大小比例8:1:1  eden将活着的复制到1区,清理eden,,然后new新对象进入eden,这时eden和1区都有存活对象,下次清理将eden和1区复制到2区
    3、标记、整理算法,标记,然后将标记的一移动到一边,清理边界之外的内存
    为什么要分1、2区,为了解决碎片化问题,内存占用是不连续的,会产生碎片化
9、什么时候需要立即初始化类:
        1、new 类的静态信息或者方法被引用
        2、反射
        3、main方法执行的的类
        4、初始化类的时候发现父类没被初始化
10、双亲委派
    类的初始化机制,当类加载器收到加载指令,先委派到父加载器,当父加载不了再自己加载
11、类加载的步骤
    加载 验证 准备 解析 初始化 使用 卸载
12、class文件常量和运行时常量
    class文件常量在类加载完成后进入运行时常量,运行时常量相对于class具备动态性,动态新增常量进来
13、GC优化三个指标
    1、GC的吞吐量
    2、GC延迟,引起的提顿时间
    3、GC占用的内存
    
    
并发编程
1、守护线程 thread.setdaemon true   gc垃圾回收器也是一个守护线程  一般用于监控或者资源回收
2、比较synchronized和volatile(还要再整理一下)
    1、volatile不具备原子性,synchronized具备原子性
    2、都具备可见性,synchronized的锁和volatile修饰的变量都是其他线程可见的
    3、synchronized和volatile都具有有序性
3、synchronized是可重入锁,就是一把锁可以进入多个synchronized
4、synchronized分为同步方法和同步代码块
    1、同步方法,反编译后发现class文件多了acc_synchronized,代表是同步方法,获取锁计数器+1,释放-1,如果获取失败就阻塞住,知道被释放
    2、同步代码块,同步块是由monitorenter指令进入,然后monitorexit释放锁,也会计数,2个monitorexit,第二个是用来处理异常的,处理结束释放锁
5、synchronized底层实现,Synchronized用的锁(也就是objectmonitor)就是存在Java对象头里的、对象头主要结构是由Mark Word(标记字段)、Class Pointer(类型指针)组成,其中Mark Word存储对象的hashCode、锁信息或分代年龄或GC标志等信息,Class Metadata Address是类型指针指向对象的类元数据,JVM通过该指针确定该对象是哪个类的实例。
6、sync的优化
    1、jdk1.6之后优化,新增偏向锁、轻量级锁,当只有一个线程的时候由无锁进入偏向锁,当不在同一时间点出现其他线程申请锁,变成轻量级锁,当同一时间多个线程申请锁,变成重量级锁
    2、自旋锁和适应性自旋锁,线程的阻塞和唤醒需要cpu状态切换,负担大,自旋锁就是原地循环等待,一定次数之后,线程挂起,适应性的根据前几次是否成功决定这次的次数,成功增加次数,失败减少次数甚至取消自旋锁
    3、锁消除,检测到不会出现数据共享的问题
    4、锁粗化,就是将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁,比如for循环,里面的锁挪到外面
7、CAS无锁算法
    compare and swap
    预期值A ,内存值B,将来要变成的值C,set之前比较A和B,一样,将变量设置成C,如果不一样,会循环比较直到一样
    缺点:1、出现ABA问题(在set之前,其他线程将A变成了B之后又变成了A,错认为没有变化),解决办法时内存的值添加版本号
          2、循环开销大
          3、无法保证多个变量的原子性,1.5之后可以通过对象来保证,变量存放在对象中
8、sync和lock区别
    1、 Lock是一个接口,属于JDK层面的实现;而synchronized属于Java语言的特性,其实现有JVM来控制(代码执行完毕,出现异常,wait时JVM会主动释放锁)。
    2、 synchronized在发生异常时,会自动释放掉锁,故不会发生死锁现(此时的死锁一般是代码逻辑引起的);而Lock必须在finally中主动unlock锁,否则就会出现死锁。
    3、 Lock能够响应中断,让等待状态的线程停止等待;而synchronized不行。
    4、 通过Lock可以知道线程是否成功获得了锁,而synchronized不行。
    5、 Lock提高了多线程下对读操作的效率
9、executor接口定义execute方法,接受runnable对象
   executorservice定义submit方法,接受Runnable和Callable对象,通过future来获取返回值(future没有获取结果期间会阻塞,可以设置阻塞时间)
   Executor并不是一个线程池,而只是一个执行线程的工具,而真正的线程池是ExecutorService。
10、四种线程池
    newCacbedThreadPool:没有核心线程,最大线程数很大,定时清理空闲线程(超时机制),适用于任务时间短数量大的场景
    newFixedThreadPool:有指定的线程数的线程池,只有核心的线程,里面有固定的线程数量,不会被回收,有等待队列LinkedBlockingQueue,很大很大的队列
    newScheduledThreadPool:与核心线程也有非核心线程,可以延迟执行,也可以当作定时器执行
    newSingleThreadExecutor:单一线程,没有并发功能,用于线程重用的场景,不重用的时候可以直接new thread
11、线程池参数
    corepollsize:核心池大小,线程小于该值会一直新建线程,大于的时候会放入阻塞队列
    Maximumpoolsize:最大线程数
    keeplivetime:线程空闲时间,超过回收,该参数只在线程数大于 corePoolSize 时才有用
    workQuque:阻塞队列(BlockingQueue等)
    拒绝策略:阻塞队列也满了,会创建新线程,当线程数大于最大线程数,会拒绝任务
    threadfactory:线程工厂,给线程设置名字
    当接到任务,线程少于核心线程,创建线程,超过核心线程数时,进入阻塞队列,阻塞队列也满了就拒绝任务进入池子
12、线程池拒绝策略
    阻塞队列已满(说明不是LinkedBlockingQueue!因为LinkedBlockingQueue是近似无界的)
    ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。注:默认策略!!!!!!
    ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
    ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
    ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
13、线程await后需要notify或者时notifyAll来唤醒
14、写时复制容器,copyonwriteArraylist 写入数据时将容器复制一份,在新的容器中进行数据的修改,结束后将原容器的引用指向新容器,覆盖旧数据,整个操作时上锁的,读写分离思想
    适合读多写少的场景,比如白名单黑名单的实现,操作尽量时批量操作
15、volatile只能保证变量的可见性,不能保证变量的原子性(对任意单个 volatile 变量的读/写具有原子性,但类似于++这种复合操作不具有原子性)。
    变量发生变化,会把当前线程缓存刷新到主内存,cpu锁住总线,同步到其他线程的内存,一般用于一写多读的场景
16、wait和sleep区别
    wait期间会释放锁,将线程放到wait list,sleep不会,wait用户线程切换,sleep用于任务暂停
17、为什么 wait, notify 和 notifyAll 这些方法不在 thread 类里面?
    java的锁时对象级别的,wait是释放锁,是锁级别的操作
    ObjectMonitor
18、每个Java对象都有一个对应的ObjectMonitor对象,它实现了同步机制,这就是为什么Object对象都能当成锁的原因。
19、ObjectMonitor是如何实现同步机制?
    ObjectMonitor有几个关键属性:
      (1)owner,指向持有ObjectMonitor的线程;
      (2)WaitSet,wait状态的线程队列,等待被唤醒,也就是调用了wait;
      (3)EntrySet,等待锁的线程队列,;
    同步流程:
        (1)有两个线程,线程A、线程B将竞争锁访问同步代码块,先进入ObjectMonitor的EntrySet中等待锁;
        (2)当CPU调度线程A获取到锁则进入同步代码,ObjectMonitor owner属性指向线程A,线程B继续在EntryList中等待;
        (3)线程A在同步代码中执行wait,则线程进入WaitSet并释放锁,ObjectMonitor owner属性清空;
        (4)CPU调度使线程B获取到锁进入同步代码块,ObjectMonitor owner属性指向线程B,任务执行完退出同步代码之前调用notifyAll,线程A被唤醒,从WaitSet转到EntryList中等待锁,线程B退出同步代码块,ObjectMonitor owner属性清空;
        (5)CPU调度使线程A获取同步锁,继续后续代码;
20、ThreadLocal
    ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。
    底层是包装了一个map,存取就是map的存取
21、join()
    main线程调用A线程的join(),main线程必须要等到A线程执行完成才能继续执行
    join底层是调用wait方法,join(long)会挂起long时间,再并行执行
    当一个线程执行完毕之后,jvm会做清理收尾工作,这个时候才会调用notifyAll,所以join结束不需要再notify
    join会释放锁,释放调用join的线程
22、为什么Thread类的sleep()和yield()方法是静态的?
    这2个方法是作用于整在执行的线程的,不能让等待线程执行,所以定义为静态方法
23、如何终止一个线程
    1、没有阻塞的情况下可以通过共享变量,通知线程终止,比如变量为true的时候
    2、阻塞状态下,可以使用thread的interrupt方法,整个方法不能终止整在运行的线程,但是可以终止阻塞的线程
24、合理配置线程池
    任务分类:1、cpu密集型,大量计算,和内存打交道,CPU一直全速运行,cpu核数决定效率
              2、io密集型,和硬盘、网络、文件、数据库打交道,即该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费大量的CPU运

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值