面试准备-Java篇

    整理一下Java篇会问到的一些知识点,学习过程中如有错误,欢迎私聊我纠正。

面试准备–Java篇

threadlocal原理

它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是操作的自己本地内存里面的变量,从而避免了线程安全问题,创建一个ThreadLocal变量后每个线程会拷贝一个变量到自己本地内存。
使用场景:
(1)每个线程需要有自己单独的实例;
(2)实例需要在多个方法中共享,但不希望被多线程共享;
简单说ThreadLocal就是一种以空间换时间的做法,在每个Thread里面维护了一个以开地址法实现的ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了

Object类里面的方法

equals()方法:Object 类中的 equals 方法,equals方法和==号比较引用数据类型无区别,重写后的equals方法比较的是对象中的属性。
notify()和wait()方法区别:
前提:由同一个lock对象调用wait、notify方法。
1、当线程A执行wait方法时,该线程会被挂起;
2、当线程B执行notify方法时,会唤醒一个被挂起的线程A;
lock对象、线程A和线程B三者是一种什么关系?根据上面的结论,可以想象一个场景:
1、lock对象维护了一个等待队列list;
2、线程A中执行lock的wait方法,把线程A保存到list中;
3、线程B中执行lock的notify方法,从等待队列中取出线程A继续执行;

wait() yield()和sleep()方法区别:

yield():让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行的机会。因此,使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。
sleep():不释放任何锁,但是它过了睡眠时间后,不一定能获得执行的时间。
wait():object对象中的方法,需在加锁的线程中执行此方法,它释放对象上的锁,以便另一个线程可以跳入并获取锁。待 notify() 或 notifyAll()方法对其唤醒!如果直接调用 wait()方法 会抛出 java.lang.IllegalMonitorStateException 异常

equals 和 == 的区别

(1)equals 是方法,而 == 是操作符;
(2)对于基本类型的变量来说(如 short、 int、 long、 float、 double),只能使用 == ,因为这些基本类型的变量没有 equals 方法。对于基本类型变量的比较,使用 == 比较, 一般比较的是它们的值。
(3)对于引用类型的变量来说(例如 String 类)才有 equals 方法,因为 String 继承了 Object 类, equals 是 Object 类的通用方法。对于该类型对象的比较,默认情况下,也就是没有复写 Object 类的 equals 方法,使用 == 和 equals 比较是一样效果的,都是比较的是它们在内存中的存放地址。但是对于某些类来说,为了满足自身业务需求,可能存在 equals 方法被复写的情况,这时使用 equals 方法比较需要看具体的情况,例如 String 类,使用 equals 方法会比较它们的值;
转至:https://www.jianshu.com/p/9cbed9f33a4d

Java中存在无符号类型吗?如果有无符号类型,怎么处理?

Java不支持无符号数据类型。byte,short,int和long都是有符号数据类型。对于有符号数据类型,值范围的一半存储正数,一半用于负数,因为一个位用于存储有符号值的符号。
Java在包装器类中有一些静态方法,以支持处理带符号值中的位的操作,就像它们是无符号整数一样。
例如,对于输入-10,返回的值将是2^8 +(-10),也就是246。负数以2的补码形式存储。 值-10将被存储为11110110。最高有效位1表示它是一个负数。
前7位(1110110)的2的补码是001010,十进制为10。
如果考虑实际位11110110,在一个字节中作为无符号整数,其值为246(128 + 64 + 32 + 16 + 0 + 4 + 2 + 0)。

Java抽象类和接口的区别

1)抽象类可以有默认的方法实现完全是抽象的,接口根本不存在方法的实现;
2)抽象类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现;
3)抽象类可以有构造器,而接口不能有构造器;
4)抽象方法可以有public、protected和default这些修饰符;接口默认修饰符是public,不可以使用其它修饰符;
5)抽象类在java语言中所表示的是一种继承关系,一个子类只能存在一个父类,但是可以存在多个接口。
6)如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。

Java封装类

封装的优点:
(1)良好的封装能够减少耦合;
(2)类内部的结构可以自由修改;
(3)可以对成员变量进行更精确的控制;
(4)隐藏信息,实现细节;

Java多态

(1)继承;(2)重写;(3)父类引用指向子类对象;
多态优点:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。
多态的实现方式:
(1)重写:这个内容已经在上一章节详细讲过,就不再阐述,详细可访问:Java 重写(Override)与重载(Overload)。
(2)接口:生活中的接口最具代表性的就是插座,例如一个三接头的插头都能接在三孔插座中,因为这个是每个国家都有各自规定的接口规则,有可能到国外就不行,那是因为国外自己定义的接口类型。java中的接口类似于生活中的接口,就是一些方法特征的集合,但没有方法的实现。具体可以看 java接口 这一章节的内容。
(3)抽象类和抽象方法

多线程是什么?多线程会出现什么问题?你的理解?

多线程:多个线程并发执行的过程。
并发问题(安全性问题):
(1)Java 内存模型规定了所有的变量都存储在主内存中,每条线程有自己的工作内存;
(2)线程的工作内存中保存了该线程中用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。
(3)线程访问一个变量,首先将变量从主内存拷贝到工作内存,对变量的写操作,不会马上同步到主内存。
(4)不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。
原子性,即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
可见性,当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性,有序性指的是数据不相关的变量在并发的情况下,实际执行的结果和单线程的执行结果是一样的,不会因为重排序的问题导致结果不可预知。

线程几种实现方式,状态转换,ThreadPoolExecutor参数解析,执行流程

实现方式:(1)继承Thread 重写run方法,实例化一个对象,调用start();
(2)实现Runable接口,如果自己的类已经extends另一个类,就无法直接extends Thread,此时,可以实现一个Runnable接口。
(3)实现Callable接口通过FutureTask包装器来创建Thread线程;
(4)使用ExecutorService、Callable、Future实现有返回结果的线程
Java线程五种状态:新建状态,可运行,运行,死亡,阻塞;
新建状态:新建一个线程对象,在没有调用start()方法之前;
可运行(就绪状态):就绪状态,调用start()方法之后进入就绪状态, 但是并不是说只要调用start()方法线程就马上变为当前线程,在变为当前线程之前都是为就绪状态。值得一提的是,线程在睡眠和挂起中恢复的时候也会进入就绪状态。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权;
运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。线程被设置为当前线程,开始执行run()方法。就是线程进入运行状态。
阻塞状态:阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。线程被暂停,比如说调用sleep()方法后线程就进入阻塞状态。
(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
join()方法解释:join()方法只会使主线程进入等待池并等待t线程执行完毕后才会被唤醒。并不影响同一时刻处在运行状态的其他线程。
PS:join源码中,只会调用wait方法,并没有在结束时调用notify,这是因为线程在die的时候会自动调用自身的notifyAll方法,来释放所有的资源和锁。
死亡状态:线程执行结束,线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
ThreadPoolExecutor参数解析:ThreadPoolExecutor是一个非常重要的类,用来构建带有线程池的任务执行器,通过配置不同的参数来构造具有不同规格线程池的任务执行器。

1
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)

corePoolSize核心线程数、maximumPoolSize最大线程数、keepAliveTime线程执行完后空闲的存活时间、workQueue任务队列、ThreadFactory 创建线程的工厂,通过他可以创建线程时做一些想做的事,比如自定义线程名称、RejectedExecutionHandler 线程数和队列都满的情况下,对新添加的任务的处理方式。

ReentrantLock原理

AQS:维护了一个volatile语义(支持多线程下的可见性)的共享资源变量state和一个FIFO线程同步等待队列(多线程竞争state被阻塞时会进入此队列)。是一个用于构建锁和同步容器的框架。事实上concurrent包内许多类都是基于AQS构建,例如ReentrantLock,Semaphore,CountDownLatch,ReentrantReadWriteLock,FutureTask等。
ReentrantLock的基本实现可以概括为:先通过CAS尝试获取锁。如果此时已经有线程占据了锁,那就加入AQS队列并且被挂起。当锁被释放之后,排在CLH队列队首的线程会被唤醒,然后CAS再次尝试获取锁。在这个时候,如果:
非公平锁:如果同时还有另一个线程进来尝试获取,那么有可能会让这个线程抢先获取;
公平锁:如果同时还有另一个线程进来尝试获取,当它发现自己不是在队首的话,就会排到队尾,由队首的线程获取到锁。
可重入锁。可重入锁是指同一个线程可以多次获取同一把锁。ReentrantLock和synchronized都是可重入锁。
可中断锁。可中断锁是指线程尝试获取锁的过程中,是否可以响应中断。synchronized是不可中断锁,而ReentrantLock则提供了中断功能。
公平锁与非公平锁。公平锁是指多个线程同时尝试获取同一把锁时,获取锁的顺序按照线程达到的顺序,而非公平锁则允许线程“插队”。synchronized是非公平锁,而ReentrantLock的默认实现是非公平锁,但是也可以设置为公平锁。
CAS操作(CompareAndSwap)。CAS操作简单的说就是比较并交换。CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。
以ReentrantLock为例,state可以用来表示该锁被线程重入的次数。当state为0表示该锁不被任何线程持有;当state为1表示线程恰好持有该锁1次(未重入);当state大于1则表示锁被线程重入state次。因为这是一个会被并发访问的量,为了防止出现可见性问题要用volatile进行修饰。

什么情况下使用ReenTrantLock?

需要实现ReenTrantLock的三个独有功能时:
(1)ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
(2)ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
(3)ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。

ReenTrantLock和Synchronized区别?

(1)锁的实现:
Synchronized是依赖于JVM实现的,而ReenTrantLock是JDK实现的,有什么区别,说白了就类似于操作系统来控制实现和用户自己敲代码实现的区别。前者的实现是比较难见到的,后者有直接的源码可供阅读。
(2)性能的区别:
在Synchronized优化以前,synchronized的性能是比ReenTrantLock差很多的,但是自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用synchronized,其实synchronized的优化我感觉就借鉴了ReenTrantLock中的CAS技术。都是试图在用户态就把加锁问题解决,避免进入内核态的线程阻塞。

CPU突然飙升的过程?

写起来思路很清晰,但实际上整个过程花费了大半天的时间,其实排查的过程中,有很多关键点没有抓到,有很多现象,是可以凭经验条件反射的推断出原因的:
(1)看到cpu使用率上涨,用jstack看使用cpu的线程,以及该线程在跑什么代码。
(2)找到是gc线程,然后看gc曲线是否正常。
(3)看堆内存曲线,正常的曲线是锯齿形的,如果不是,一次full GC之后内存没有明显下降,那基本可以推断发生内存泄漏了。
(4)怀疑是内存泄漏的问题,可以跑jmap,然后拉到MAT分析。
(5)第四步比较耗时的话,可以同时跑这个命令:jmap -histo pid。看看有没有线索。

线程同步的方式?

(1)Synchronized关键字修饰的同步方法;
(2)Synchronized修饰的同步代码块;
(3)使用特殊域变量(volatile)实现线程同步;
(4)ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
ReenreantLock类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例
lock() : 获得锁
unlock() : 释放锁
(5)使用局部变量实现线程同步,如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
(6)使用阻塞队列实现线程同步,前面5种同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。 使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。 本小节主要是使用LinkedBlockingQueue来实现线程的同步,LinkedBlockingQueue是一个基于已连接节点的,范围任意的blocking queue。
队列是先进先出的顺序(FIFO),关于队列以后会详细讲解~
(7)使用原子变量实现线程同步原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作。即-这几种行为要么同时完成,要么都不完成。在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。

有哪些具体的垃圾收集器?

(1)串行垃圾回收器(Serial):为单线程环境设计,只使用一个线程进行垃圾回收,会暂停所有用户线程,不适合服务器环境;
(2)并行垃圾回收器(Patallel):多个垃圾收集线程并发工作,用户状态为暂停状态,适用于科学计算、大数据
处理首台处理等弱交互场景;
(3)并发垃圾回收器(CMS):用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程,适用对响应时间有要求的场景;
(4)G1垃圾回收器将堆内存分成不同的区域然后并发的对其进行垃圾回收;G1是一种服务器端的垃圾收集器,应用在多处理器和大容量内存环境中,在实现搞吞吐量的同时尽可能的满足垃圾收集暂停时间的要求另外它还具有一下的特性:
像CMS收集器一样,能与应用程序线程并发执行。
整理休闲空间更快。
需要更多的时间来预测GC停顿时间。
不希望牺牲大量的吞吐性能。
不需要更大的Java Heap.
https://juejin.im/post/6844904000286883848

什么时候考虑把CMS换成G1或者这两个收集器各自适合在什么场景下?

CMS(年老代-并行-收集器)获取最短回收停顿时间为目标的收集器。比较适合互联网响应式应用场景,采用的是“标记-清除”算法。
G1(Garbage First)是目前最为前沿的垃圾回收器,在前面的内容中已经提过JDK1.8以后的生产实践新生代、年老代都可以采用G1作为垃圾回收器。其采用的收集算法是”标记-清除-整理“,所以不会产生内存碎片。
Parallel Scavenge (新生代-并行-收集器)采用的也是多线程,以及复制收集算法,与其他同类型收集器不同的是,它的关注点是达到一个可控制吞吐量的目标,吞吐量=运行用户代码时间/(运行用户代码时间+垃圾回收的时间),假设虚拟机总共运行了100分钟,其中垃圾回收花了一分钟,那么吞吐量就是99%。高吞吐量的目的是为了高效的利用CPU时间,从而尽快的完成程序运算任务,主要适合后台运算不需要有太多交互的应用场景。
Parallel Old(年老代-并行-收集器)是Parallel Scavenge的老年代版本,是多线程,采用标记-整理算法的收集器。

BIO/NIO/AIO区别

BIO:同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。
NIO同步非阻塞的I/O模型,单线程中从通道读取数据到buffer,同时可以继续做别的事情,当数据读取到buffer中后,线程再继续处理数据。写数据也是一样的。
(1)IO流是阻塞的,NIO流是不阻塞的;
(2)IO 面向流(Stream oriented),而 NIO 面向缓冲区(Buffer oriented);有数据都是用缓冲区处理的,包括读和写。最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte 数组。除了ByteBuffer,还有其他的一些缓冲区,事实上,每一种Java基本类型(除了Boolean类型)都对应有一种缓冲区。
(3)NIO 通过Channel(通道) 进行读写。通道是双向的,可读也可写,而流的读写是单向的。无论读写,通道只能和Buffer交互。因为 Buffer,通道可以异步地读写。
(4)NIO有选择器,而IO没有。选择器用于使用单个线程处理多个通道。因此,它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。 因此,为了提高系统效率选择器是有用的。
AIO:它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

NIO适用场景:
服务器需要支持超大量的长时间连接。比如10000个连接以上,并且每个客户端并不会频繁地发送太多数据。例如总公司的一个中心服务器需要收集全国便利店各个收银机的交易信息,只需要少量线程按需处理维护的大量长期连接。

Java内存模型(JMM)

JMM就是一组规则,这组规则意在解决在并发编程可能出现的线程安全问题,并提供了内置解决方案(happen-before原则)及其外部可使用的同步手段(synchronized/volatile等),确保了程序执行在多线程环境中的应有的原子性,可视性及其有序性。

Java浅谈AQS

所谓AQS,指的是AbstractQueuedSynchronizer,它提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架,ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier等并发类均是基于AQS来实现的。AQS维护了一个volatile语义(支持多线程下的可见性)的共享资源变量state和一个FIFO线程等待队列(多线程竞争state被阻塞时会进入此队列)。
资源的共享方式分为两种:(1)独占式,只有单个线程能够成功获取资源并执行,如ReentrantLock。(2)共享式。多个线程可成功获取资源并执行,如Semaphore/CountDownLatch等。主要包括下面方法:
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

GC Roots的对象

根搜索算法是JVM用来判断对象是否存活的算法,此算法基本思路为通过一系列的“GC Roots”对象作为起始点,从这些节点往下搜索,当一个对象和GC Roots不可达时,则该对象是无用的,可被回收。
可作为GC Roots的对象有:
1、虚拟机栈(栈帧中的本地变量表)中引用的对象;
2、方法区中类静态属性引用的对象;
3、方法区中常量引用的对象;
4、本地方法栈中JNI中引用的对象;

线程逃逸问题

this逃逸是指在构造函数返回之前其他线程就持有改对象的引用。条用尚未构造完全的对象可能引发令人疑惑的错误。因此应该避免逃逸的发生。
this逃逸经常发生在构造函数中启动线程或注册监听器时
解决办法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ThisEscape {
private Thread t;
public ThisEscape() {
t = new Thread(new EscapeRunnable());
// ...其他代码
}
public void init() {
t.start();
}
private class EscapeRunnable implements Runnable {
@Override
public void run() {
// 在这里通过ThisEscape.this就可以引用外围类对象, 此时可以保证外围类对象已经构造完成
}
}
}

多线程中lock和Synchronized的区别?

synchronized是基于JVM实现的,内置锁,Java中的每一个对象都可以作为锁。对于同步方法,锁是当前实例对象。对于静态同步方法,锁是当前对象的Class对象。对于同步方法块,锁是Synchonized括号里配置的对象。Lock是基于在语言层面实现的锁,Lock锁可以被中断,支持定时锁。Lock可以提高多个线程进行读操作的效率。通过对比得知,Lock的效率是明显高于synchronized关键字的,一般对于数据结构设计或者框架的设计都倾向于使用Lock而非Synchronized。
Lock和Synchronized关键字的区别如下:
(1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
(2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
(3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
(4)Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)
性能上来说,在资源竞争不激烈的情形下,Lock性能稍微比synchronized差点(编译程序通常会尽可能的进行优化synchronized)。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。

Java中static、final、static final的区别?

static 修饰 属性,方法,代码段,内部类;在类初始化时加载,初始化后可以修改;static修饰的属性和方法跟具体对象无关;
final 修饰属性,方法和类;可以编译时或在运行时初始化,一旦初始化后就不可修改;
static final 修饰属性时表示一旦给值,就不可修改,并且可以通过类名访问;修饰方法时不可被重写,可以在不new 对象时调用;

Java类加载机制?

JDK JRE区别?

JDK主要包含三部分:
第一部分就是Java运行时环境,JVM。
第二部分就是Java的基础类库,这个类库的数量还是非常可观的。
第三部分就是Java的开发工具,它们都是辅助你更好的使用Java的利器。
下面是JDK附带的一些重要组件:
apt 注解处理工具
javadoc 文档生成器,可以自动从源代码生成说明文档
jar 归档器,将相关的类库打包到一个JAR文件中。还可以帮助管理JAR文件
jConsole Java监控和管理平台
jhat Java堆分析工具
jstack 打印Java线程的堆栈信息
keytool 策略创建和管理工具
jarsigner Java签名和验证工具
同JRE,JDK也依赖于平台,所以要下载与机器相对应的JDK包
只有JVM还不能成class的执行,因为在解释class的时候JVM需要调用解释所需要的类库lib,而jre包含lib类库。

性能调优工具jmap

命令jmap是一个多功能的命令。它可以生成 java 程序的 dump 文件, 也可以查看堆内对象示例的统计信息、查看 ClassLoader 的信息以及 finalizer 队列。

单例模式双重校验锁、懒汉模式和饿汉模式

饿汉模式:饿汉模式在类加载的时候就对实例进行创建,实例在整个程序周期都存在。这种实现方式适合单例占用内存比较小,在初始化时就会被用到的情况。优缺点:优点是只在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题。缺点是即使这个单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了。
懒汉模式:如果某个单例使用的次数少,并且创建单例消耗的资源较多,那么就需要实现单例的按需创建,这个时候使用懒汉模式就是一个不错的选择。
双重校验锁:
加锁的懒汉模式看起来即解决了线程并发问题,又实现了延迟加载,然而它存在着性能问题,依然不够完美。synchronized修饰的同步方法比一般方法要慢很多,如果多次调用getInstance(),累积的性能损耗就比较大了。因此就有了双重校验锁,先看下它的实现代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if (instance == null) { // Single Checked
synchronized (Singleton.class) {
if (instance == null) { // Double checked
instance = new Singleton();
}
}
}
return instance;
}
}

Java中内存泄露

由于Java的JVM引入了垃圾回收机制,垃圾回收器会自动回收不再使用的对象,了解JVM回收机制的都知道JVM是使用引用计数法和GC ROOT可达性分析算法来判断对象是否是不再使用的对象,本质都是判断一个对象是否还被引用。那么对于这种情况下,由于代码的实现不同就会出现很多种内存泄漏问题(让JVM误以为此对象还在引用中,无法回收,造成内存泄漏)。

java classloader加载机制

JDK中提供了三个ClassLoader,根据层级从高到低为:
Bootstrap ClassLoader,主要加载JVM自身工作需要的类。
Extension ClassLoader,主要加载%JAVA_HOME%\lib\ext目录下的库类。
Application ClassLoader,主要加载Classpath指定的库类,一般情况下这是程序中的默认类加载器,也是ClassLoader.getSystemClassLoader() 的返回值。(这里的Classpath默认指的是环境变量中配置的Classpath,但是可以在执行Java命令的时候使用-cp 参数来修改当前程序使用的Classpath)
JVM加载类的实现方式,我们称为 双亲委托模型:
如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委托给自己的父加载器,每一层的类加载器都是如此,因此所有的类加载请求最终都应该传送到顶层的Bootstrap ClassLoader中,只有当父加载器反馈自己无法完成加载请求时,子加载器才会尝试自己加载。
双亲委托模型的重要用途是为了解决类载入过程中的安全性问题。
假设有一个开发者自己编写了一个名为Java.lang.Object的类,想借此欺骗JVM。现在他要使用自定义ClassLoader来加载自己编写的java.lang.Object类。然而幸运的是,双亲委托模型不会让他成功。因为JVM会优先在Bootstrap ClassLoader的路径下找到java.lang.Object类,并载入它

Java多线程运行过程中可能存在的开销

1)线程的创建:创建线程的使用是直接向系统申请资源的,对操作系统来说,创建一个线程的代价是十分昂贵的, 需要给它分配内存、列入调度,同时在线程切换的时候还要执行内存换页,CPU的缓存被清空,切换回来的时候还要重新从内存中读取信息,破坏了数据的局部性。
2)上下文切换:当前任务执行一个时间片后会切换到下一个任务。在切换之前,上一个任务的状态会被保存下来,下次切换回这个任务时,可以再加载这个任务的状态,任务从保存到再加载的过程就是一次上下文切换。
说明:
1)时间片是CPU分配给各个线程的时间,时间片一般是几十毫秒。
2)CPU通过给每个线程分配CPU时间片,并且不停地切换线程来实现多线程。因为时间片非常短,所以感觉多个线程是在同时执行。
减少上下文切换的方法:
1)无锁并发编程:
多线程竞争锁时,会引起上下文切换,所以在使用多线程处理数据时,可以采用一些策略来避免使用锁。
常见的策略:将数据按照id的哈希值进行切分,不同的线程处理不同段的数据。
2)锁分离技术:
举例:ConcurrentHashMap
3)CAS算法
java的Atomic包使用CAS算法来更新数据,而不需要加锁。
4)使用最少的线程
避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态。

Spring AOP的动态代理方式

实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
AOP用来封装横切关注点,具体可以在下面的场景中使用:
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging  
调试logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence 持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务

关于反射

单例模式最根本的在于类只能有一个实例,如果通过反射来构建这个类的实例,单例模式就会被破坏;
除了反射以外,反序列化过程也会破坏单例模式;
枚举实现单例可以阻止反射及序列化的漏洞;

Integer中的intvalue

valueOf中127在-128到127之间对象会被缓存,因此生成了127的Integer后再次引用时候,读取的对象是同一个,因此相同。 对于Integer、Short、Byte、Character、Long 这些包装类,都有一个常量池,常量池的范围是-128~127之间。
==和equal()区别:Integer的equals方法会先判断实例是否是Integer类型,再判断数值是否相等。Double,Float等包装类的equals方法也是如此;==如果是基本类型会比较值是否相等,如果是对象类型会比较引用是否相同;

内存可见性和原子性:Synchronized和Volatile的比较

1)Synchronized保证内存可见性和操作的原子性
2)Volatile只能保证内存可见性
3)Volatile不需要加锁,比Synchronized更轻量级,并不会阻塞线程(volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。)
4)volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化(如编译器重排序的优化)
5)volatile是变量修饰符,仅能用于变量,而synchronized是一个方法或块的修饰符。volatile本质是在告诉JVM当前变量在寄存器中的值是不确定的,使用前,需要先从主存中读取,因此可以实现可见性。而对n=n+1,n++等操作时,volatile关键字将失效,不能起到像synchronized一样的线程同步(原子性)的效果。

关于抽象类,static,final的再次探讨

抽象类:抽象类其实是可以实例化的,但是他的实例化方式不是通过new方式来创建对象,而是通过父类的引用来指向子类的实例来间接地实现父类的实例化(因为子类要实例化前,一定会先实例化他的父类。这样创建了继承抽象类的子类的对象,也就把其父类(抽象类)给实例化了).但是:接口是不能被实例化的(接口压根就没有构造函数)。
原因:抽象类之所以不能被实例化,是因为它里面都是抽象方法,实例化对象调用其里面的方法没有意义,我们需要做的就是覆写掉里面的抽象方法。
1、抽象类不能被实例化,实例化的工作应该交由它的子类来完成,它只需要有一个引用即可。
2、抽象方法必须由子类来进行重写。
3、只要包含一个抽象方法的抽象类,该方法必须要定义成抽象类,不管是否还包含有其他方法。
4、抽象类中可以包含具体的方法,当然也可以不包含抽象方法。
5、子类中的抽象方法不能与父类的抽象方法同名。
6、abstract不能与final并列修饰同一个类。
7、abstract不能与private、static、final或native并列修饰同一个方法。
final
1)修饰类:不能被继承,类中的方法不会被覆盖,默认都是final修饰的方法;
2)修饰方法:不能被重写,可以被继承;
注意:父类中的private成员方法不能被子类覆盖,因此,private方法默认是final型的(可以查看编译后的class文件)
3)修饰变量:用final修饰后变为常量。包括静态变量、实例变量和局部变量这三种

Java四大访问修饰符,三大特性

Java中四大访问修饰符:

| 修饰符   | 当前类   | 同包     | 子类  | 其它包
| public  | 可以     | 可以     |可以   | 可以
| protect | 可以     | 可以     |可以   | 不可以
| default | 可以     | 可以     |不可以 | 不可以
| private | 可以     | 不可以   |不可以  | 不可以

封装、继承和多态

总结

有条不紊,勇往直前。

坚持原创技术分享,您的支持将鼓励我继续创作!