1、谈谈类加载的过程
类加载的过程分为五个阶段进行,分别是加载、验证、准备、解析和初始化这几个动作。
1、加载
在加载阶段,Java虚拟机会完成三件事情
- 通过一个类的全限定类名来获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的
java.lang.Class
对象,作为方法区这个类的各种数据的访问入口
衍生问题:
- 数组和类的加载是有区别的
- 类加载阶段的顺序问题
2、连接
连接之验证
验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
验证阶段大致上会完成四个阶段的检查动作:
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
文件格式验证:
该阶段要验证字节流是否符合Class文件格式的规范,并能被当前虚拟机处理。这一阶段可能包括如下验证点:
- 是否以魔数开头
- 主次版本号是否在当前Java虚拟机接受的范围之内
- 常量池的常量中是否有不被支持的常量类型
- 。。。
该阶段的主要目的是保证输入的字节流能正确地解析并存储于方法区之内,格式上符合描述一个Java类型信息地要求。这个阶段地验证是基于二进制字节流进行的,只有通过了这个阶段地验证之后,这段字节流才被允许进入Java虚拟机内存地方法区中进行存储,所以后面的三个验证阶段都是基于方法区的存储结构上进行的,不会再直接读取、操作字节流了
元数据验证:
该阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合《Java语言规范》的要求,这个阶段可能包括的验证点如下:
- 这个类是否有父类(除了java.lang.Object之外,所有类都应当有父类)
- 这个类的父类是否继承了不允许被继承的类(被final修饰的类)
- 如果这个类不是抽象类,是否实现了其父类或接口知中要求实现的所有方法。
- 。。。
该阶段的主要目的是对类的元数据信息进行语义校验,保证不存在与《Java语言规范》定义相悖的元数据信息
字节码验证:
该阶段是整个验证过程中最复杂的一个阶段,主要目的是通过数据流分析和控制流分析,确定程序语义是合法的、符合逻辑的。在第二阶段对元数据信息中的数据类型校验完毕以后,这阶段就要对类的方法体(Class文件中的Code属性)进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的行为,例如:
- 保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现类似于“在操作数栈放置了一个int类型的数据,使用时却按long类型来加载入本地变量表中”这样的情况。
- 保证任何跳转指令都不会跳转到方法体以外的字节指令上
- 保证方法体中的类型转换总是有效的,例如可以吧一个子类对象赋值给与它毫无继承关系、完全不相干的一个数据类型,则是危险和不合法的
- 。。。
符号引用验证:
最后一个阶段的校验行为发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段——解析阶段中发生。符号引用验证可以看作是对类自身以外(常量池中各种符号引用)的各类信息进行匹配性校验,通俗来说就是,该类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。本阶段需要校验下列内容:
- 符号引用中通过字符串描述的全限定名是否能找到对应的类。
- 在指定类中是否存在符合方法的字段描述以及简单名称所描述的方法和字段。
- 符号引用中的类、字段、方法的可访问行(private、protected、public、)是否可被当前类访问。
- 。。。
符号引用验证的主要目的是确保解析行为能正常执行,如果无法通过符号引用验证,Java虚拟机将会抛出一个java.lang.IncompatibleClassChangeError
的子类异常,
典型的如:
java.lang.IllegalAccessError
java.lang.NoSuchFieldError
java.lang.NoSuchMethodError
准备
准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量的初始值的阶段,从概念上讲,这些变量所使用的内存都应当在方法去中进行分配,但必须注意到方法去本身是一个逻辑的区域,在JDK7之前,HotSpot使用永久代来实现方法区时,实现是完全符合这种逻辑概念的:而在JDK7及以后,类变量会随着Class对象一起存放在Java堆中,这时候“类变量在方法去”就完全是一种堆逻辑概念的表述了。
对类变量设置初始值“通常情况”下是数据类型的零值。除非类变量被final修饰了
例如:
public static final int value = 123;
类的字段属性表中存在ConstantValue属性,那在准备阶段变量值就会被初始化为ConstantValue属性所指的初始值。在编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置讲value赋值为123
解析
解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程
衍生问题:
- 什么是直接引用、什么是间接引用
- 符号引用转换为直接引用的时机
- 类或者接口的解析过程、字段解析的过程、方法解析、接口方法解析的过程
3、初始化
类的初始化阶段是类加载过程的最后一个步骤,之前介绍的几个类加载的动作里,除了在加载阶段用户应用程序可以通过自定义类加载器的方式局部参与之外,其余动作完全由Java虚拟机来主导控制。知道初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。
进行准备阶段时,变量已经赋过一次系统要求的初始零值,而在初始化阶段,则会根据程序员通过程序编码制定的主管计划区初始化类变量和其他资源。也就是执行类构造器clinit
,注意clinit
并不是程序员在Java代码中直接编写的方法,他是Javac编译器的自动生成物
2、线程池的参数有哪些?执行流程和拒绝策略是什么?
线程池的七大参数如下:
- corePoolSize:核心线程数,线程池中的常驻核心线程数
- 在创建线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务
- 当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列中
- maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1、
- 相当有扩容后的线程数,这个线程池能容纳的最多线程数
- keepAliveTime:多余的空闲线程存活时间
- 当线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余的空闲线程会被销毁,直到只剩下corePoolSize个线程为止
- 默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用
- unit:keepAliveTime的单位
- workQueue:任务队列,被提交的但未被执行的任务(类似于银行里面的候客区)
- LinkedBlockingQueue:链表阻塞队列
- SynchronousBlockingQueue:同步阻塞队列
- threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程池 一般用默认即可
- handler:拒绝策略,表示当队列满了并且工作线程大于线程池的最大线程数(maximumPoolSize3)时,如何来拒绝请求执行的Runnable的策略
线程池的拒绝策略如下:
解释是什么?
线程池的等待队列满了同时救急线程也用完了,再也处理不了新的任务了,此时就需要一个拒绝的策略了
说明jdk内置的拒绝策略
jdk种内置了4种拒绝策略:
以下所有拒绝策略都实现了RejectedExecutionHandler接口
- AbortPolicy:默认,直接抛出RejectedExcutionException异常,阻止系统正常运行
- DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常,如果运行任务丢失,这是一种好方案
- CallerRunsPolicy:该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者
- DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
衍生问题:
- 线程池的工作原理
- 平时Executers使用单一/固定/可变哪种方式创建线程池?
- 线程池中的corePoolSize 和 maximumPoolSize应该设置为多少?
3、聚簇索引和非聚簇索引有什么区别?
- 聚集索引一个表只能有一个,而非聚集索引一个表可以存在多个
- 聚簇索引(InnoDB)的叶子节点就是数据节点 而非聚簇索引(myisam)的叶子节点仍然是数据记录的地址
提一提,InnoDB和MyISAM的非聚簇索引的区别
4、Zset和Java的set有什么区别?
从实现角度来分析
在redis中zset会如果同时满足保存的元素小于128并且所有元素长度都小于64,那么会使用压缩链表来实现zset,其他情况下通过跳跃表实现
在java中set通过hashMap实现,也就是通过数组加链表加红黑树实现
从使用效果来比较
redis中的zset和java中的set都只能存放不重复的元素,但是zset可以按照评分顺序进行获取,而java中的set读取是无序的
5、GET 和 POST 的区别?
- 从功能上讲,
GET
一般用来从服务器上获取资源,POST
一般用来在服务器上新增资源; - 从请求参数形式上看,
GET
请求的数据会附在URL
之后,POST
请求会把提交的数据放置在是HTTP
请求报文的请求体中; - 就安全性而言,
POST
的安全性要比GET
的安全性高,因为 GET 请求提交的数据将明文出现在URL
上,而且POST
请求参数则被包装到请求体中,相对更安全; - 从请求的大小看,
GET
请求的长度受限于浏览器或服务器对URL
长度的限制,允许发送的数据量比较小,而POST
请求理论上是没有大小限制。