JDK版本:Java8及以上
模式:Q&A


一、JVM和类的关系?类实例化过程?

JVM:
当调用java命令运行一个java程序的时候,必会启动一个JVM虚拟机。该java程序的资源,变量等都在这个JVM虚拟机的内存中,直到虚拟机JVM终止,JVM内存中的数据全部丢失。

  • JVM终止的情况:
    ☛程序自然运行结束时
    ☛遇到System.exit();Runtime.getRuntime.exit()时
    ☛遇到未捕获异常或错误时
    ☛程序所在的平台强制结束了JVM进程时

JVM -> 类:

(双亲委派)JVM提供了启动类加载器 <- 库类加载器 <- 应用类加载器

载入Javavm时Windows出错_初始化


加载过程:

- ①加载、JVM加载class字节码到内存,生成class对象

载入Javavm时Windows出错_载入Javavm时Windows出错_02


>ClassLoader.loadClass(全限定类名)

=》该方法加载后未初始化,其实他调用的方法是ClassLoader.loadClass(className,false)

- ②连接校验字节码是否符合java规范,准备为static变量分配内存空间并赋予默认初值(0或null等),解析将一些符号引用替换为直接的引用,解析成为具体的class结构。

- ③初始化、激活类的静态变量的初始化Java代码和静态Java代码块初始化发生条件

☛在使用new关键字创建对象的时候

☛在使用static修饰的静态变量,静态方法的时候,会初始化声明该静态变量的类

☛在使用java反射时候(Class.forName(…))

☛在创建子类对象的时候,如果其父类还没有初始化,这先要初始化父类

☛执行的java的main方法时,会初始化该类

载入Javavm时Windows出错_JVM_03


初始化顺序

①父类的静态变量、父类的静态代码块

②子类的静态变量、子类的静态代码块

③父类的非静态成员变量、父类的构造函数

④子类的非静态成员变量、子类的构造函数


二、反射的原理,反射创建类实例的三种方式是什么?

Java反射:

在运行时通过Java的Reflection APIs取得任何一个已知名称的class的内部信息。例如:构造器Constructor、属性Filed、方法Method、方法参数Parameter、接口Interface、访问器Modifier,代理Proxy,注解Annotation等。

载入Javavm时Windows出错_JAVA_04


方式

▶通过【Class.forName(全限定类名)】

=》该方法加载后就初始化了,其实他调用的方法是Class.forName(className,true,classloader)

▶通过【类名.class】
▶通过【对象名.getClass()】

通过以上反射方式获取class对象【Class class】
1、获取class对象的Constructor

载入Javavm时Windows出错_JVM_05


2、获取class对象的Filed


getFields返回的是申明为public的属性,包括父类中定义,


getDeclaredFields返回的是指定类定义的所有定义的属性,不包括父类的。

载入Javavm时Windows出错_载入Javavm时Windows出错_06


3、获取class对象的Method


载入Javavm时Windows出错_JVM_07


4、获取class对象的实例


class.newInstance() ——》只能调用无参数的构造方法


new() ——》可以调用任何public的构造方法


三、集合List、Set、Map、Queue的区别?

载入Javavm时Windows出错_线程池_08


数组工具:Arrays.xxx(数组名[,…])

集合工具:Collections.xxx(集合名称[,…])

xxx:静态方法

List接口:实现类(按照添加顺序,可重复)

  • ArrayList:基于数组结构存储,默认初始容量为10,扩展大小为源元素个数的一半(即now=old+old/2)
  • LinkedList:基于双向链表结构存储,默认初始容量为0,扩展大小为1(即now=old+1)
  • Vecter:基于数组结构存储,默认初始容量为10(增长因子为0),扩展大小为源元素大小(即now=old+old)、线程安全。

Set接口:实现类(不可重复)
SortedSet接口:继承Set接口】

  • HashSet:基于hashMap结构存储,数据保存在Key上,不允许重复,只能有一个NULL。(其他参照Map接口)
  • LinkedHashSet:继承HashSet,按照添加顺序存储
  • TreeSet:实现了【SortedSet接口】,按照排序规则存储,其他参照TreeMap

Map接口
SortedMap接口:继承Map接口】

  • HashTable:线程安全。
  • HashMap:基于数组(16)+链表(链表转树阀值[链表长度]8<>树转链表6)+红黑树结构存储,默认初始容量为16[数组部分](加载因子为0.75即所能容纳的key-value对极限16x0.75=12个Node后扩容为原来的2倍),原因是减少Hash碰撞,扩展大小为源元素大小(即now=old*2),按照Key的HashCode值顺序存储。
  • LinkedHashMap:继承HashMap,按照添加顺序存储
  • TreeMap:实现了【SortedMap接口】,按照排序规则存储(默认升序)

ConcurrentHashMap: 实现了【ConcurrentMap接口】,线程安全但非Hashtable实现方式(分段锁)的Map实现

Queue接口

  • PriorityQueue:基于数组结构存储,初始大小为11,默认优先度为0,不允许存储NULL,通过内部comparator接口的compare方法来排序 ,若没实现接口(默认没有实现),则是元素的优先度顺序(优先度值越低,越在队列前面),非线程安全。扩展策略为:newCapacity = oldCapacity + ((oldCapacity < 64) ?(oldCapacity + 2) :(oldCapacity >> 1))

四、NIO、BIO、AIO区别?

1.什么是同步和异步?
同步和异步是针对应用程序和内核的交互而言的。
同步:指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪。
异步:是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知

2.什么是阻塞和非阻塞?
阻塞和非阻塞是针对于进程在访问数据的时候。
阻塞:读取或者写入函数将一直等待
非阻塞:读取或者写入函数会立即返回一个状态值。

一般来说I/O模型可以分为:同步阻塞(BIO),同步非阻塞(NIO),异步阻塞,异步非阻塞IO(AIO)。

BIO:是面向流的,阻塞,每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方,一个连接一个线程。
NIO:基于Reactor模式,是面向缓冲区的,非阻塞,一个请求一个线程。

核心组件

  • Chanel(通道)

主要在java.nio.channels包下增加了下面四个通道:
SocketChannel(TCP客户端通道)
ServerSocketChannel(TCP服务端通道)
FileChannel(文件通道,该通道只能时阻塞的,不能设置为非阻塞)
DatagramChannel(UDP通道)

  • Buffer(缓冲器)
  • [基本数据类型+Buffer]:ByteBuffer、IntBuffer、CharBuffer等。
  • Selector(选择器)

AIO:称作NIO.2,异步非阻塞。

  • 对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;
  • 对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。
  • 即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。 一个有效请求一个线程。

主要在java.nio.channels包下增加了下面四个异步通道:
AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel


五、线程池是什么?为什么要线程池?其执行过程?

■为什么需要线程池?

频繁的创建和销毁线程,花费的时间和消耗的系统资源都相当大,且在jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。
线程池就是尽可能减少创建和销毁线程的次数,尽量用已有线程来进行服务,解决线程生命周期开销问题和资源不足问题。

■什么是线程池?

线程池至少应包含线程池管理器工作线程任务列队任务接口等部分。

  • 线程池管理器:作用是创建、销毁并管理线程池,将工作线程放入线程池中;
  • 工作线程:是一个可以循环执行任务的线程,在没有任务是进行等待;
  • 任务列队:作用是提供一种缓冲机制,将没有处理的任务放在任务列队中;
  • 任务接口:是每个任务必须实现的接口,主要用来规定任务的入口、任务执行完后的收尾工作、任务的执行状态等,工作线程通过该接口调度任务的执行。

载入Javavm时Windows出错_JAVA_09


载入Javavm时Windows出错_JVM_10


1、如何解决多线程的下面两个问题?

  • 共享数据如何同步?
    synchronized:当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
  • 如何避免并发时数据不一致?
    ThreadLocal:为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。

2、线程池的处理流程 ?
2-1、创建线程池需要使用Executor接口的实现类 ThreadPoolExecutor 类,它的构造函数参数如下:

public ThreadPoolExecutor(
int corePoolSize, //核心线程的数量
int maximumPoolSize, //最大线程数量(核心线程+核心意以外的线程)
long keepAliveTime, //超出核心线程数量以外的线程空闲时的存活时间
TimeUnit unit, //存活时间的单位
BlockingQueue workQueue, //存放来不及处理的任务的队列,是一个BlockingQueue。
ThreadFactory threadFactory, //生产线程的工厂类,可以定义线程名,优先级等。
RejectedExecutionHandler handler // 当任务无法执行时的处理器
) {…}

载入Javavm时Windows出错_JAVA_11

2-2、线程池执行的具体方法:

public void execute(Runnable command) {
//1.当前池中线程比核心数少,新建一个线程执行任务
//2.核心池已满,但任务队列未满,添加到队列中
//3.核心池已满,队列已满,试着创建一个新线程/拒绝任务,执行RejectedExecutionHandler
}
execute和submit的区别?
execute():提交不需要返回值的任务
Future submit():提交需要返回值的任务

RejectedExecutionHandler 处理策略
1、CallerRunsPolicy:只要线程池没关闭,就直接用调用者所在线程来运行任务
2、AbortPolicy:直接抛出 RejectedExecutionException 异常
3、DiscardPolicy:悄悄把任务放生,不做了
4、DiscardOldestPolicy:把队列里待最久的那个任务扔了,然后再调用 execute() 试试看能行不,也可以实现自己的 RejectedExecutionHandler 接口自定义策略,比如如记录日志什么的

BlockingQueue 接口,常用的实现有如下几种:
① ArrayBlockingQueue:基于数组、有界,按 FIFO(先进先出)原则对元素进行排序
② LinkedBlockingQueue:基于链表,按FIFO (先进先出) 排序元素
③ SynchronousQueue:不存储元素的阻塞队列 ,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态
④ PriorityBlockingQueue:具有优先级的、无限阻塞队列

2-3、如何合理地选择 ?
1、CachedThreadPool 用于并发执行大量短期的小任务,或者是负载较轻的服务器
2、FixedThreadPool 用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量
3、SingleThreadExecutor 用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行
4、ScheduledThreadPoolExecutor 用于需要多个后台线程执行周期任务,同时需要限制线程数量的场景

自定义线程池时,如果任务是 CPU 密集型(需要进行大量计算、处理),则应该配置尽量少的线程,比如 CPU 个数 + 1,这样可以避免出现每个线程都需要使用很长时间但是有太多线程争抢资源的情况;
如果任务是 IO密集型(主要时间都在 I/O,CPU 空闲时间比较多),则应该配置多一些线程,比如 CPU 数的两倍,这样可以更高地压榨 CPU。

在Spring中对Executor接口进行封装为TaskExecutor接口,其实现类为ThreadPoolTaskExecutor。
ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();