京东

1、调用反射的有几种方法?

1
2
3
4
TargetObject.class;
Class.forName("cn.javaguide.TargetObject");
instance.getClass()
ClassLoader.loadClass("cn.javaguide.TargetObject");

2、线程池的参数、线程池的执行流程、拒绝策略等

1
2
3
4
5
6
7
8
//通过ThreadPoolExecutor构造函数自定义参数创建
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    CORE_POOL_SIZE,
    MAX_POOL_SIZE,
    KEEP_ALIVE_TIME,
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(QUEUE_CAPACITY),
    new ThreadPoolExecutor.CallerRunsPolicy());

图解线程池实现原理

拒绝策略

  • ThreadPoolExecutor.AbortPolicy 抛出 RejectedExecutionException来拒绝新任务的处理。
  • ThreadPoolExecutor.CallerRunsPolicy 调用执行自己的线程运行任务,也就是直接在调用execute方法的线程中运行(run)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果您的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略。
  • ThreadPoolExecutor.DiscardPolicy 不处理新任务,直接丢弃掉。
  • ThreadPoolExecutor.DiscardOldestPolicy 此策略将丢弃最早的未处理的任务请求。

3、抽象类与接口的区别?

共同点

  • 都不能被实例化。
  • 都可以包含抽象方法。
  • 都可以有默认实现的方法(Java 8 可以用 default 关键在接口中定义默认方法)。
参数抽象类接口
成员变量默认 default。可以变量,也可以常量只可以常量
成员方法可以抽象,也可以非抽象。抽象类中可以没有抽象方法,但有抽象方法的一定是抽象类只可以抽象
方法实现可以有默认的方法实现接口完全是抽象的(Java 8 可以用 default
构造器抽象类可以有构造器接口不能有构造器
与正常Java类的区别除了你不能实例化抽象类之外,它和普通Java类没有任何区别接口是完全不同的类型
访问修饰符抽象方法可以有public、protected和default这些修饰符接口方法默认修饰符是public。default。
main方法抽象方法可以有main方法并且我们可以运行它接口没有main方法,因此我们不能运行它。
多继承抽象方法可以继承一个类和实现多个接口接口只可以继承一个或多个其它接口
添加新方法如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。如果你往接口中添加方法,那么你必须改变实现该接口的类。
设计理念被继承体现的是:”is a”的关系。抽象类中定义的是该继承体系的共性功能被实现体现的是:”like a”的关系。接口中定义的是该继承体系的扩展功能。

4、重载与重写的区别

重载Overload

  • 重载的方法必须具有不同的参数列表。

重写Override

  1. 参数返回值相同
  2. 构造方法不能被重写
  3. 父类方法抛出异常,子类也要抛出异常,且不能多于父类抛出的异常,例如父类抛出了IOException那么重写这个方法时就不能抛出Exception。

5、内存分区、新建对象在哪个区?

img

6、String类型的字符串存在哪?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";//常量池中的对象
String str4 = str1 + str2; //在堆上创建的新的对象
String str5 = "string";//常量池中的对象
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false

// 编译器会给你优化成 `String str3 = "string";
//String str4 = new StringBuilder().append(str1).append(str2).toString();

img

通过字面量赋值创建字符串(如:String str=”twm”)时,会先在常量池中查找是否存在相同的字符串,若存在,则将栈中的引用直接指向该字符串;若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。

7、垃圾回收机制介绍流程,Full GC,新生代、中生代。

大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为大于 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置默认值,这个值会在虚拟机运行过程中进行调整,可以通过-XX:+PrintTenuringDistribution来打印出当次 GC 后的 Threshold。

针对HotSpot VM的实现,它里面的GC其实准确分类只有两大种:

  • Partial GC:并不收集整个GC堆的模式

    • Young GC:只收集young gen的GC
    • Old GC:只收集old gen的GC。只有CMS的concurrent collection是这个模式
    • Mixed GC:收集整个young gen以及部分old gen的GC。只有G1有这个模式
  • Full GC:收集整个堆,包括young gen、old gen、perm gen(如果存在的话)等所有部分的模式。

Major GC通常是跟full GC是等价的,收集整个GC堆。但因为HotSpot VM发展了这么多年,外界对各种名词的解读已经完全混乱了,当有人说“major GC”的时候一定要问清楚他想要指的是上面的full GC还是old GC。

最简单的分代式GC策略,按HotSpot VM的serial GC的实现来看,触发条件是:

  • young GC:当young gen中的eden区分配满的时候触发。注意young GC中有部分存活对象会晋升到old gen,所以young GC后old gen的占用量通常会有所升高。
  • full GC:当准备要触发一次young GC时,如果发现统计数据说之前young GC的平均晋升大小比目前old gen剩余的空间大,则不会触发young GC而是转为触发full GC(因为HotSpot VM的GC里,除了CMS的concurrent collection之外,其它能收集old gen的GC都会同时收集整个GC堆,包括young gen,所以不需要事先触发一次单独的young GC);或者,如果有perm gen的话,要在perm gen分配空间但已经没有足够空间时,也要触发一次full GC;或者System.gc()、heap dump带GC,默认也是触发full GC。

8、异常的分类、如何捕获?OOM聊一聊?

9、类加载机制、双亲委派模型?

img

每一个类都有一个对应它的类加载器。系统中的 ClassLoader 在协同工作的时候会默认使用 双亲委派模型 。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派给父类加载器的 loadClass() 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader 中。当父类加载器无法处理时,才由自己来处理。当父类加载器为 null 时,会使用启动类加载器 BootstrapClassLoader 作为父类加载器。

10、1.8 hashmap底层put过程

11、volatile能保证线程安全吗

要解决这个问题,就需要把变量声明为 volatile ,这就指示 JVM,这个变量是共享且不稳定的,每次使用它都到主存中进行读取。

所以,volatile 关键字 除了防止 JVM 的指令重排 ,还有一个重要的作用就是保证变量的可见性。

  • volatile 关键字是线程同步的轻量级实现,所以 volatile 性能肯定比synchronized关键字要好 。但是 volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块
  • volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。
  • volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性。

12、JVM垃圾回收

新生代垃圾回收器

名称简介回收算法
Serial单线程进行垃圾回收,停止程序的运行复制算法
ParNewSerial的多线程版本复制算法
ParallelGC吞吐量=代码运行时间/(代码运行时间+垃圾收集时间)标记-复制

老年代垃圾回收器

名称简介回收算法
SerialOld单线程(CMS 收集器的后备方案)标记 - 整理算法
ParallelOldGC吞吐量标记-整理
CMS系统停顿时间,垃圾收集线程与用户线程同时工作标记-清除
G1并行与并发分代收集空间整合可预测的停顿。优先选择回收价值最大的整体:标记-整理;局部:标记-复制

13、单例模式和动态代理模式

单例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 双重校验锁
public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
        if (singleton == null) {  
            synchronized (Singleton.class) {  
                if (singleton == null) {  
                    singleton = new Singleton();  
                }  
            }  
        }  
        return singleton;  
    }  
}
// 枚举
public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
} 

动态代理

  1. 定义一个接口及其实现类;
  2. 自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
  3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;

14、封装、继承和多态

多态

编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数

运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。

Java实现多态有三个必要条件:继承、重写、向上转型。

  • 继承:在多态中必须存在有继承关系的子类和父类。
  • 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
  • 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。

15、ThreadLocal了解吗?软、弱、虚和强引用

ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。

1
2
3
4
5
6
7
ThreadLocal.ThreadLocalMap threadLocals = null
    
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

ThreadLocalMap map = getMap(t);

每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对。

ThreadLocal数据结构

ThreadLocalMapThreadLocal的静态内部类。

软、弱、虚和强引用

1
static class Entry extends WeakReference<ThreadLocal<?>>
  1. 强引用(StrongReference): 最普遍的引用,垃圾回收器绝不会回收它 -> OutOfMemoryError
  2. 软引用(SoftReference): 内存不够回收
  3. 弱引用(WeakReference): 扫到就回收
  4. 虚引用(PhantomReference):虚引用并不会决定对象的生命周期。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。

16、Synchronized和ReentrantLock的区别?

  1. 两者都是可重入锁
  2. synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API
  3. ReentrantLock 比 synchronized 增加了一些高级功能 相比synchronizedReentrantLock增加了一些高级功能。主要来说主要有三点:
  • 等待可中断 : ReentrantLock提供了一种能够中断等待锁的线程的机制,通过 lock.lockInterruptibly() 来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
  • 可实现公平锁 : ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。
  • 可实现选择性通知(锁可以绑定多个条件): synchronized关键字与wait()notify()/notifyAll()方法相结合可以实现等待/通知机制。ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition()方法。

17、Hashmap线程安全? 如何让它线程安全

主要原因在于并发下的 Rehash 会造成元素之间会形成一个循环链表。

不过,jdk 1.8 后解决了这个问题,但是还是不建议在多线程下使用 HashMap,因为多线程下使用 HashMap 还是会存在其他问题比如数据丢失。并发环境下推荐使用 ConcurrentHashMap

链接:https://leetcode-cn.com/circle/discuss/KLnm9d/