Java

Java四大特性,多态存在的条件
抽象、继承、封装、多态
继承 + 重写 + 父类引用指向子类对象


Java对象的完整生命周期
创建阶段(Creation)、应用阶段(Using)、不可视阶段(Invisible)、不可到达阶段(Unreachable)、可收集阶段(Collected)、终结阶段(Finalized)与释放阶段(Free)。
对象创建流程:加载该对象所在的类文件(就是编译后的.class 文件)进入内存;在堆上分配一块跟类对象大小一样的内存;将对象所在的内存值初始化为 0;初始化对象头,包括、对象的哈希码、对象的GC分代年龄等信息;调用类的 init 函数进行 Java 层面的对象初始化。
参考文档:Java对象生命周期Java对象的生命周期


Java参数传递方式
方法参数传递方式是按值传递。
如果参数是基本类型,传递的是基本类型的字面量值的拷贝。
如果参数是引用类型,传递的是该参量所引用的对象在堆中地址值的拷贝。


Java字节码
参考文档:java字节码学习


Java类加载机制,ClassLoader双亲委派机制
“Class文件”:包括但不限于磁盘文件、网络、数据库、内存或者动态产生等。
Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程称作虚拟机的类加载机制。关于在什么情况下需要开始类加载过程的第一个阶段“加载”,《Java虚拟机规范》中没有强制约束,可以由虚拟机具体实现。
ClassLoader作用是将.class文件加载到JVM,通常情况下,当调用类构造器/类静态变量或方法时,ClassLoader会主动将.class文件加载到内存。
Java中ClassLoader:BootstrapClassLoader,ExtClassLoader,APPClassLoader(面向用户)。
双亲委派机制:当类加载器收到加载类或资源的请求时,通常都是先委托父类加载器进行加载,也就是说,只有当父类加载器找不到类或资源时,自身才会执行实际的类或资源的加载过程。

双亲委派机制

自定义ClassLoader:a.自定义一个类继承抽象类ClassLoader;b.重写 findClass 方法;c.在 findClass中,调用defineClass方法将字节码转换成Class对象,并返回。

自定义ClassLoader

Android虚拟机无法运行.class文件,会加载转化后的.dex文件,ClassLoader:
BaseDexClassLoader:PathClassLoader和DexClassLoader父类,包含具体实现逻辑。
PathClassLoader:加载系统apk,以及被安装到手机中apk的dex文件。
DexClassLoader:可以从SD卡上加载包含class.dex的.jar和.apk文件,插件化和热修复的基础。


Java类加载过程
一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,生命周期经历:加载、验证、准备、解析、初始化、使用、卸载。
1、加载:查找字节流,并根据此字节流创建Class类的过程。加载成功的标志就是在方法区中成功创建类所对应的Class对象。
          1)通过类的全限定名获取定义此类的二进制字节流。(ZIP/JAR/EAR/WAR、网络、动  态代理)
           2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
           3)在内存中(方法区?堆中?)生成一个代表这个类的java.lang.Class对象,作为方法 区这个类的各种数据的访问入口。
2、验证:验证创建的类,将其解析到JVM中,使之能够被JVM执行。
           1)文件格式验证:0xCAFEBABE开头,主次版本号,常量池类型tag标志是否支持等
           2)元数据验证:是否有父类,是否继承被final修饰的类,如果不是抽象类是否实现父类或接口中要求实现的所有方法,类中字段、方法是否与父类产生矛盾
           3)字节码验证:对类的方法体,即Class文件中的Code属性,进行校验分析
           4)符号引用验证:验证该类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源
3、准备:为类变量(即静态变量)分配内存并设置类变量初始零值,概念上应该在方法区中进行分配,变量的具体赋值动作要到初始化阶段才会被执行。
4、解析:Java虚拟机将常量池内的符号引用替换为直接引用的过程。
5、初始化:执行类构造器<clinit>()方法的过程。【<cinit>() 方法:编译器会在将 .java 文件编译成 .class 文件时,收集所有类初始化代码和 static {} 域的代码,收集在一起成为<cinit>() 方法 】。      


Java类初始化&实例初始化
类初始化:<clinit>类第一次加载到内存时进行的过程,类初始化只进行一次(前提是被同一类加载器加载),后续使用new等实例化对象时都不再进行初始化了,初始化属于类(静态)的内容,对所有实例共享。
实例初始化:<init>实例化对象时每次都会经历的过程,初始化属于实例(非静态)的内容,实例间不共享。


Java类初始化触发场景
1、使用new关键字实例化对象;
2、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的除外);
3、调用类的静态方法;
4、使用java.lang.reflect包的方法对类型进行反射调用;
5、当类初始化时,如果发现其父类还未初始化,则需要先触发其父类的初始化;
6、虚拟机启动时,会初始化主类(包含main()方法的那个类);
7、接口中定义了JDK8新加入的默认方法(default关键字修饰的接口方法),接口要在实现类之前被初始化。


Java类初始化<clinit>()方法
1、类的初始化阶段是类加载过程的最后一个步骤,其实就是执行类构造器<clinit>()方法的过程。<clinit>()方法是Javac编译器的自动生成物,在编译期自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生,收集顺序取决于它们在源文件中出现的顺序。
2、Java虚拟机会保证在子类的<clinit>()方法执行前,保证其父类的<clinit>()方法已经执行完毕,第一个执行的是Object类。
3、<clinit>()方法对于类或接口来说非必需,没有静态语句块&变量赋值操作时,可以不生成。
4、执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法,因为只有当父接口中定义的变量被使用时,父接口才会被初始化。接口的实现类初始化时也一样不会执行接口的<clinit>()方法。
5、Java虚拟机必须保证一个类的<clinit>()方法在多线程环境中被正确的加锁同步,如果多个线程同时去初始化一个类,那么只会有其中一个线程去执行这个类的<clinit>()方法,其它线程都需要阻塞等待,知道活动的线程执行完毕<clinit>()方法。
6、实例变量(非static类型变量)的赋值,在实例构造器<init>()方法中进行。
7、变量(static + final + 基本类型/String),生成ConstantValue属性进行初始化。
8、变量(static + 无final 或者 非基本类型/String),在类构造器<init>()方法中进行初始化。


Java对象实例化时初始化顺序
实例化对象是调用对象的构造方法,构造方法也是静态方法的一种。JVM 收集实例初始化变量和 {} 域组合成实例初始化方法 <init>()
1、单个类初始化顺序:
(静态变量、静态初始化块)>(变量、初始化块)> 构造器
2、存在继承情况时:
父类(静态变量、静态初始化块)> 子类(静态变量、静态初始化块)> 父类(变量、初始化块、构造器)> 子类(变量、初始化块、构造器)
子类的静态变量和静态初始化块的初始化是在父类的变量、初始化块和构造器初始化之前就完成了。
静态变量、静态初始化块,变量、初始化块初始化的顺序,取决于它们在类中出现的先后顺序。


Java对象组成 & 对象头
对象组成:对象头,实例数据,对齐填充字节
对象头:Mark Word,指向类的指针,数组长度(只有数组对象才有)
Mark Word:记录了对象和锁有关的信息,当这个对象被synchronized关键字当成同步锁时,围绕这个锁的一系列操作都和Mark Word有关。Mark Word在32位JVM中的长度是32bit,在64位JVM中长度是64bit。

Mark Word

参考文档:Java的对象头和对象组成详解


抽象类/接口
继承是一个“是不是”的关系,而接口实现则是“有没有”的关系;
抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract方法;
抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
一个类只能继承一个抽象类,而一个类却可以实现多个接口。
参考文档:深入理解Java的接口和抽象类


为什么匿名内部类访问final的局部变量?
内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。所以,当外部类的方法结束时,局部变量就会被销毁了,但是内部类对象可能还存在,此时可能导致内部类对象访问了一个不存在的变量。为了解决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样即使外部局部变量不存在时依然可以访问它。但是,将局部变量复制为内部类的成员变量时,必须保证这两个变量是一样的,也就是如果我们在内部类中修改了成员变量,方法中的局部变量也得跟着改变。因此,就将局部变量设置为final,对它初始化后就不允许再去修改这个变量,如此就保证了内部类的成员变量和方法的局部变量的一致性,这实际上也是一种妥协。
final变量若是基本类型,其值是不能改变的,就保证了copy与原始的局部变量的值是一样的;
final变量若是引用类型,其引用是不能改变的,保证了copy与原始的变量引用的是同一个对象。


.class/Class.forName/.getClass()区别
都是返回类所对应的Class对象
.class:JVM将类加载到内存中,创建类所对应的的Class对象,但不执行初始化(不执行静态代码)
Class.forName:JVM将类加载到内存中,创建类所对应的的Class对象,并进行初始化(执行静态代码)
.getClass():返回对象运行时真正所属的Class对象
参考文档:.Class, Class.forName, .getClass()的区别


Objetct类方法,equals和==的区别,equals和hashcode的关系
equals,hashcode,wait,notify,notifyAll,toString,clone,getClass,finalize
Object默认的equals等价于==,覆盖equals后则需要具体分析。如果需要创建类对应散列表,若要判断两个对象是否相等,需同时覆盖equals和hashcode方法,如果相关对象equals返回true,那么它们的hashcode值一定相同,如果两个对象hashcode值相等,则equals不一定返回true。
参考文档:hashCode() 和 equals()


hashCode的作用,如何重载hashCode
为哈希表服务(HashSet/HashMap),result = 17; result = result * 31 + ***.hashCode();


wait()和sleep()的区别
sleep是线程类(Thread)的方法,可以在如何地方进行调用,调用会让出CPU,但监控依然保持,不会释放同步资源,到时间自动恢复;不同于wait,sleep需要捕获或者抛异常。
wait是Object的方法,只能再同步方法或者同步代码块中使用(wait依赖底层monitor对象),调用会让当前线程让出CPU并暂时释放同步资源,进入等待队列,待调用notify/notifyAll唤醒才会解除wait状态,然后参与竞争同步资源锁,进而得到执行。
参考文档:线程间的协作(wait/notify/sleep/yield/join)


Java并发编程,多线程间的共享性、互斥性、有序性、可见性和原子性
共享性:数据共享性是线程安全的主要原因之一。
互斥性:资源互斥是指同时只允许一个访问者对其进行访问,具有唯一性和排它性。通常,允许多个线程同时对数据进行读操作,但同一时间内只允许一个线程对数据进行写操作。
有序性:即程序执行的顺序按照代码的先后顺序执行,Java提供volatile来保证一定的有序性。在JMM中,为了效率运行编译器和处理器对指令进行重排序,重排序不影响单线程运行效果,但是对多线程会有影响。
可见性:指多个线程访问共享变量时,若某个线程修改改共享变量的值,其它线程能够立即看到修改后的值。
原子性:即一个操作或者多个操作,要么全部执行并且执行的过程不会被如何因素打断,要么就都不执行,如同数据库里面的事务一样。
参考文档:Java 并发编程核心理论Android 最佳并发实践之基础篇


Java锁的种类
可重入锁、可中断锁、公平锁/非公平锁、乐观锁/悲观锁、独享锁/共享锁、互斥锁/读写锁、偏向锁/轻量级锁/重量级锁、分段锁、自旋锁等
可重入锁:锁具备可重入性,即锁的分配粒度基于线程,而不是基于方法调用。
参考文档:Java锁的种类和区别


synchronized实现原理
每个对象有一个监视器锁(monitor)与之相关联,对象与其monitor之间的关系有多种实现方式,如monitor可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,当monitor被某个线程持有后,对象便处于锁定状态。但是,监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的,而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么synchronized效率低的原因。在Java虚拟机HotSpot中,monitor是由ObjectMonitor实现的,其主要数据结构如下:

ObjectMonitor

synchronized重量级锁,Java对象头中的指针,会指向monitor对象。线程执行monitorenter指令 / ACC_SYNCHRONIZED访问标识时,会尝试获取monitor的所有权。如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者;如果线程已经占有该monitor,只是重新进入,则monitor的进入数加1;如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。线程执行monitorexit指令时,当前线程必须是monitor的所有者,monitor的进入数减1,如果减1后进入数为0,那当前线程退出monitor,不再是这个monitor的所有者,后续其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权。


synchronized在JDK1.6之后的底层优化
【偏向锁】:适用于只有一个线程访问同步块场景。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令。如果线程ID并未指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行同步代码;如果CAS获取偏向锁失败,则表示有竞争,当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。
【轻量级锁】:适用于线程交替执行的场景,不允许出现竞争。在代码进入同步块时,如果同步对象锁状态为无锁状态,虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word。拷贝对象头中的Mark Word复制到锁记录中,拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,那么这个线程就拥有了该对象的锁,此对象处于轻量级锁定状态。如果更新操作失败,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则。说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。 而当前线程便尝试使用自旋来获取锁,自旋就是为了不让线程阻塞,而采用循环去获取锁的过程。
【重量级锁】:线程只有在获取monitor所有权的前提下,才可执行同步代码,否则阻塞。
参考文档:Synchronized底层优化


synchronized方法锁、对象锁、类锁的意义和区别
a.方法锁:默认使用当前对象作为锁的对象,方法锁也是对象锁。JVM根据ACC_SYNCHRONIZED标识符来实现方法的同步,调用时会检查方法的ACC_SYNCHRONIZED标识是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,并且方法执行期间其它任何线程都无法再获得同一个monitor对象,方法执行完后再释放monitor。
b.对象锁:synchronized修饰方法或代码块,通过monitorenter和monitorexit来完成。
c.类锁:锁对象是当前类的class对象,在不同线程中调用不同实例对象,也会有互斥效果。java类只有一个class对象,不同实例之间共享该class对象,类锁就是class对象的锁而已。
tips:对象锁是用来控制实例方法之间的同步,类锁是用来控制静态方法(或静态变量互斥体)之间的同步。


线程同步方法分析:lock/synchronized/reentrantLock
synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的,而监视器锁本质又是依赖于底层操作系统的Mutex Lock来实现的,操作系统实现线程间的切换需要从用户态转到核心态,成本非常高。
synchronized是Java关键字,基于JVM层面实现,使用时等待的线程会一直等待下去,不能够响应中断,synchronized无需用户手动释放锁(发生异常时也会自动释放)。Lock是一个Java接口,基于JDK层面实现,可以知道是否成功获取锁,需要用户手动释放,否则可能造成死锁。
lock:Java并发:Lock框架详解
synchronized:Java并发:Synchronized及其实现原理
reentrantLock:线程同步ReentrantLock,condition(await,signal)ReentrantLock详解


volatile实现原理
CPU缓存是为了解决CPU运算速度与内存读写速度不匹配的矛盾,CPU运算速度要比内存读写速度快得多。CPU高速缓存为某个CPU独有,只与在该CPU运行的线程有关。
缓存一致性协议(MESI协议),确保每个缓存中使用的共享变量的副本是一致的。核心思想:当某个CPU在写数据时,如果发现操作的变量是共享变量,则会通知其它CPU该变量的缓存行是无效的,因此其它CPU在读取该变量时,发现其无效会重新从主存中加载数据。
可以保证可见性和有序性,但无法保证原子性。当一个变量被volatile修饰后,则表示线程本地内存无效,某个线程修改共享变量后会立即被更新到主内存中,其它线程读取该共享变量时,会直接从主内存中读取。JVM底层通过内存屏障(内存栅栏)来实现,它是一组处理器指令,用于实现对内存操作的顺序限制。
volatile使用必须满足如下两个条件(常见场景:状态标记变量/Double Check/一个线程写多个线程读):
        a.对变量的写操作,不依赖当前值。
        b.该变量没有包含在具有其它变量的不变式中。
参加文档:volatile的使用及其原理volatile底层原理详解


happens-before原则
a.程序次序规则:一个线程内,按照代码顺序,书写在前面的操作,happens-before于书写在后面的操作。
b.锁定规则:一个unLock操作,happens-before于后面对同一个锁的lock操作。
c.volatile变量规则:对一个volatile变量的写操作,happens-before于后面对这个变量的读操作。
d.传递规则:如果操作A happens-before操作B,而操作B happens-before操作C,则可以得出,操作A happens-before操作C。
e.线程启动规则:Thread对象的start方法,happens-before此线程的每一个动作。
f.线程中断规则:对线程interrupt方法的调用,happens-before被中断线程的代码检测到中断事件的发生。
g.线程终结规则:线程中所有的操作,都happens-before线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段,检测到线程已经终止执行。
h.对象终结规则:一个对象的初始化完成,happens-before它的finalize()方法的开始。


CountDownLatch原理和用法
本质上还是使用AQS,通过“共享锁”实现,使用CountDownLatch(int count)构建CountDownLatch实例,将count参数赋值给内部计数器state,调用await()方法阻塞当前线程,并将当前线程封装加入到等待队列中,直到state等于零或当前线程被中断;调用countDown()方法使state值减一,如果state等于零则唤醒等待队列中的线程。
参考文档:CountDownLatch详解


ThreadLocal原理和用法
用于提供线程局部变量,在多线程环境下可以保证各个线程变量的独立性,相当于线程的private static类型变量。ThreadLocalMap的set()、get()和remove()方法中,都有清除无效(key为null)Entry的操作,这样做是为了降低内存泄漏发生的可能。Entry中的key使用了弱引用的方式,这样做是为了降低内存泄漏发生的概率。
参考文档:Java之ThreadLocal详解理解Java中的ThreadLocal


生产者/消费者
Java实现生产者和消费者的5种方式


String(不可变)/ StringBuffer / StringBuilder之间的区别
public final class String implements java.io.Serializable, Comparable, CharSequence
public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence
public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence
a.都是final类,都不允许被继承;
b.每次对String类进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String对象;
c.String类长度是不可变的,StringBuffer和StringBuilder类长度是可以改变的;
d.StringBuffer类是线程安全的,StringBuilder不是线程安全的;
参考文档:String、StringBuffer和StringBuilder的区别


Java泛型,类型擦除
泛型的本质是参数化类型,在不创建新的类型的情况下,通过泛型指定不同的类型来控制形参具体限制的类型。也就是说在泛型的使用中,操作是数据类型被指定为一个参数,这种参数可以被用在类、接口和方法中,分别被称为泛型类、泛型接口和泛型方法。
泛型是Java中的一种语法糖,能够在代码编写的时候起到类型检测的作用,但是虚拟机是不支持这些语法的。
泛型的优点:类型安全,避免类型的强转;提高了代码的可读性,不必要等到运行的时候才去强制转换。
不管泛型的类型传入哪一种类型的实参,对于Java来说,都会被当成同一类处理,在内存中也只占用一块空间。通俗一点来说,就是泛型只作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的信息擦除,也就是说,成功编译后的class文件是不包含任何泛型信息的。
参考文档:漫谈Java的泛型机制


线程创建的方式


线程停止方法
Java线程中断的正确姿势


Callable/Future/FutureTask
直接继承Thread/实现Runnable接口,在执行任务后无法获取结果。通过Callable和Future,可以在任务执行完毕后获取结果。Future:判断任务是否完成/中断任务/获取执行结果。
参考文档:Java并发编程:Callable、Future和FutureTask


线程池实现原理
降低资源消耗:通过复用已有线程来避免频繁创建销毁线程所造成的大量资源消耗。
提高响应速度和性能:当执行大量异步任务时能提供很好的性能。
提供线程可管理性:线程池提供了资源限制和管理手段,可以进行统一分配、监控和调优。

线程池体系


线程池结构

worker集合:保存所有的核心线程和非核心线程(类比加工厂的加工机器),其本质是一个HashSet。
等待任务队列:当核心线程的个数达到corePoolSize时,新提交的任务会被先保存在等待队列中(类比加工厂中的仓库),其本质是一个阻塞队列BlockingQueue。
ctl:是一个AtomicInteger类型,二进制高3位用来标识线程池的状态,低29位用来记录池中线程的数量。
corePoolSize:表示核心线程数量。
maximumPoolSize:表示线程池最大能够容纳同时执行的线程数,必须大于或等于1。如果和corePoolSize相等即是固定大小的线程池。
keepAliveTime:表示线程池中的线程空闲时间,当空闲时间达到此值时,线程会被销毁直到剩下corePoolSize个线程。
unit:用来指定keepAliveTime的时间单位,有MILLISECONDS、SECONDS、MINUTES、HOURS等。
workQueue:等待队列,BlockingQueue类型。当请求任务数大于corePoolSize时,任务将被缓存在此BlockingQueue中。
threadFactory:线程工厂,线程池中使用它来创建线程,如果传入的是null,则使用默认工厂类DefaultThreadFactory。
handler:执行拒绝策略的对象。当workQueue满了之后并且活动线程数大于maximumPoolSize的时候,线程池通过该策略处理请求。
注意:当ThreadPoolExecutor的allowCoreThreadTimeOut设置为true时,核心线程超时后也会被销毁。
a.当线程池中运行的线程数量还没有达到corePoolSize大小时,线程池会创建一个新线程执行提交的任务,无论之前创建的线程是否处于空闲状态。
b.当线程池中运行的线程数量已经达到corePoolSize大小时,线程池会把任务加入到等待队列中,直到某一个线程空闲了,线程池会根据我们设置的等待队列规则,从队列中取出一个新的任务执行。
c.如果线程数大于corePoolSize数量但是还没有达到最大线程数maximumPoolSize,并且等待队列已满,则线程池会创建新的线程来执行任务。
d.最后如果提交的任务,无法被核心线程直接执行,又无法加入等待队列,又无法创建“非核心线程”直接执行,线程池将根据拒绝处理器定义的策略处理这个任务。比如在ThreadPoolExecutor中,如果你没有为线程池设置RejectedExecutionHandler,这时线程池会抛出RejectedExecutionException异常,即线程池拒绝接受这个任务。

拒绝策略

newFixedThreadPool:传入的是一个无界的阻塞队列,理论上可以无限添加任务到线程池。当核心线程执行时间很长(比如sleep 10s),则新提交的任务还在不断地插入到阻塞队列中,最终造成OOM。
newCachedThreadPool:缓存线程池的最大线程数为Integer最大值。当核心线程耗时很久,线程池会尝试创建新的线程来执行提交的任务,当内存不足时就会报无法创建线程的错误。


try/catch/finally
不抛出异常情况下,程序执行完try里面的代码之后,方法不会立即结束,会继续试图寻找有没有finally代码块;如果没有finally代码块,则执行完try代码块后返回相应的值来结束整个方法;如果有finally代码块,则程序执行到try代码块里的return语句之时并不会立即返回,而是会先去执行finally里面的代码,若finally代码块里没有return或没有能够终止程序的代码,程序将在执行完finally代码之后再返回try代码块执行return语句来结束整个方法;若finally代码块里有return或含有能够终止程序的代码,则方法将在执行完finally之后被结束,不再跳回try代码块执行return。
抛出异常情况下,原理类似,把上面的try换成catch去理解即可。
参考文档:java 异常捕捉 (try catch finally)


Java序列化和反序列化
Serializable(Java自带),将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。缺点是使用了反射,序列化过程比较慢,会创建许多临时对象,容易触发GC。
Parcelable(Android专用),Parcelable方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的数据类型,这样也就实现传递对象的功能了。使用内存时,Parcelable性能高,但不建议使用在要将数据存储在磁盘上的情况。
参考文档:java序列化和反序列化序列化Serializable和Parcelable的理解和区别


Java反射机制
在程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种 动态的获取信息 以及 动态调用对象方法 的功能称为java的反射机制。
参考文档:Java 反射由浅入深


GC(Garbage Collector)垃圾回收机制【可达性分析、引用计数】
触发时机:
        a.Allocation Failure:在堆内存中分配时,可用剩余空间不足导致对象内存分配失败,此时系统触发GC。
        b.开发者主动调用该API请求一次GC。
回收算法:
        a.标记清除算法:从GC Roots集合开始,将内存整个遍历一次,保留所有可以被GC Roots直接或间接引用到的对象,而剩下的对象都当作垃圾对待并回收。优点是实现简单,无需对象移动。缺点是需要中断进程中其它组件执行,并且可能会产生内存碎片,提高了垃圾回收频率。
        b.复制算法:将现有的内存空间分为2块,每次只使用其中一块,垃圾回收时将存活对象复制到未被使用的另外一块内存中,交换2块内存的角色,完成垃圾回收。优点是实现简单、运行高效,不用考虑内存碎片。缺点是可用内存大小缩减为原来一半,对象存活率高时需要频繁复制。
        c.标记-压缩算法:从GC Roots集合开始,将内存整个遍历一次进行对象标记,然后将所有存活的对象压缩到内存的一端。优点是性价比比较高。缺点是需要对象移动,效率降低。
        d.分代回收:Java 虚拟机根据对象存活的周期不同,把堆内存划分为几块,一般分为新生代、老年代。对于新创建的对象会在新生代中分配内存,如果经过多次回收仍然存活下来,则将它们转移到老年代中。新生代又可以细分为 3 部分:Eden、Survivor0(简称 S0)、Survivor1(简称S1),并按照 8:1:1 的比例进行分配。新生代GC称为Minor GC,老年代GC称为Major GC。


GC Root
a.在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等
b.在方法区中类静态属性引用的对象,譬如Java类的静态变量
c.在方法区中常量引用的对象,譬如字符串常量池里的引用
d.在本地方法栈中JNI(Native方法)引用的对象
e.Java虚拟机内部的引用
f.所以被同步锁(synchronized关键字)持有的对象


Java引用类型
a.强引用:经常使用的引用类型,只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收 。
b.软引用:对应的java类是 java.lang.ref.SoftReference<T> ,当对象仅有软引用可达到,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收该对象,可以配合引用队列来释放软引用自身 。避免内存溢出
c.弱引用:对应的 java 类是 java.lang.ref.WeakReference<T>,仅有弱引用可达该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用指向的对象。避免内存泄漏
d.虚引用:对应的java类是 java.lang.ref.PhantomReference<T>,与 软引用和弱引用 不同的是,虚引用必须配合引用队列一起使用,因此只有一个构造方法 "PhantomReference(T referent, ReferenceQueue<? super T> q)",且虚引用的get()方法始终返回null。

Java引用类型

JVM内存分配

JVM内存分布


JVM 中的内存可以划分为若干个不同的数据区域,主要分为:程序计数器、虚拟机栈、本地方法栈、堆、方法区。
a.程序计数器:线程私有,字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理,主要用于记录当前线程执行的位置,唯一一个没有规定OOM情况。在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能恢复到正确的执行位置。
b.虚拟机栈和本地方法栈:线程私有,有StackOverflowError和OutOfMemoryError两种异常状况。线程执行某个方法时都会创建一个栈帧,每个栈帧内部包含局部变量表、操作数栈、常量池引用、动态链接、返回地址等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。
c.堆:所有线程共享内存区域,堆用来存放对象实例,是GC管理的主要区域。
d.方法区:所有线程共享内存区域,主要存储已经被JVM加载的类信息(版本、字段、方法、接口)、静态变量、常量、即时编译器编译后的代码和数据。


Java内存模型(JMM)

JMM描述的是多线程并发、CPU缓存等方面的内容,不要与JVM内存结构混淆。在每一个线程中,都会有一块内部的工作内存(working memory,即对CPU寄存器和高速缓存的抽象描述),用于保存主内存共享数据的拷贝副本。在执行任务时,CPU会先将运算所需要使用到的数据复制到高速缓存中,让运算能够快速进行,当运算完成之后,再将缓存中的结果刷回(flush back)主内存,这样CPU就不用等待主内存的读写操作了。
Java内存模型来源:CPU缓存和指令重排等优化会造成多线程程序结果不可控。
Java内存模型:本质上是一套规范,最重要的一条是happens-before原则。


Java堆栈内存的区别


Java集合,Collection容器/SparseArray/Deque双端队列(ArrayQueue/LinkedList),阻塞队列BlockingQueue,ArrayList与LinkedList区别与联系
SparseArray:SparseArray详解及源码简析
PriorityQueue:深入理解Java PriorityQueue
java集合:由浅入深理解java集合 系列
阻塞队列:Java 中的阻塞队列   阻塞由ReentrantLock实现


HashMap原理,与HashTable区别,LinkedHashMap(LruCache)原理,线程安全集合ConcurrentHashMap及实现原理
HashMap:图解HashMap原理
LinkedHashMap:图解LinkedHashMap原理
LruCache:彻底解析Android缓存机制——LruCache
ConcurrentHashMap:解读Java8中ConcurrentHashMap是如何保证线程安全的
HashMap非线程安全:为什么HashMap线程不安全


注解Annotation
参考文档:深入浅出Java注解


编译时注解APT(ButterKnife/EventBus/Dagger2/Arouter)
javac的一个工具,在编译时扫描和处理注解,根据需求自动生成一些代码,省去了手动编写,相比反射在运行时处理注解大大提高了程序性能,其核心是AbstractProcessor类。使用APT时,必须在Java Library中进行。
JavaPoet是square公司的开源框架,可以用对象的方式来生成代码。
参考文档:Java编译时注解处理器(APT)详解


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容

  • 1.说一下面向对象的三大特征,有什么特点? 继承、封装、多态继承:继承就是子类通过extends继承父类的方法,是...
    MrAnt阅读 564评论 0 0
  • 面向对象的三个特征 封装,继承,多态.这个应该是人人皆知.有时候也会加上抽象. 多态的好处 允许不同类对象对同一消...
    Blizzard_liu阅读 1,295评论 0 6
  • Hashmap的原理,增删的情况后端数据结构如何位移 在JDK1.6,JDK1.7中,HashMap采用位桶+链表...
    囧略囧阅读 637评论 0 0
  • JAVA中的几种基本数据类型是什么,各自占用多少字节? 在栈中可以直接分配内存的数据是基本数据类型;引用数据类型:...
    chs_sandy阅读 256评论 0 0
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,520评论 28 53