响应时间(RT):系统处理请求的时间
吞吐量:单位时间内处理请求的数量
QPS:每秒的响应请求数,也即是最大吞吐能力。
(吞吐量的增大,有时会把平均响应时间作为牺牲,来换取一段时间处理更多的请求)
问题 | 答案 |
---|---|
JVM 内存划分 栈参考: https://blog.csdn.net/seebetpro/article/details/49202037 https://blog.csdn.net/airjordon/article/details/72867397 局部变量表:是一组变量值的存储空间,它用于存储方法,参数,以及方法内部定义的局部变量 操作数栈:也是被组织成一个以字长为单位的数组,不是通过索引来访问,而是通过标准的栈操作—压栈和出栈—来访问的。虚拟机把操作数栈作为它的工作区——大多数指令都要从这里弹出数据,执行运算 | 程序计数器(线程私有): Java方法 指向下一条将被执行的指令;native方法 值不会被定义 Java虚拟机栈(线程私有):每个方法在执行的时候都会创建一个栈帧;以帧为单位的压栈和出栈操作,栈帧存储:局部变量表(4个字节,8种类型,基本类型数据、对象引用)、操作数栈、动态连接信息、方法正常完成和异常完成信息 本地方法栈:调用本地方法时使用的栈 方法区(线程共享):存储元(Meta)数据:类信息、常量、静态变量 运行时常量池(方法区的一部分):字面量(Literal),如文本字符串、final常量值,符号引用 (字段方法这些符号引用) 堆(线程共享):所有创建的Java对象实例,新生代、老年代的划分 直接内存:Java的NIO可以使用Native方法直接在java堆外分配内存,使用DirectByteBuffer对象作为这个堆外内存的引用 |
堆内存结构 | 图形化工具:JConsole,VisualVm 命令行工具:jstat,jmap
新生代的Eden区域: 对象优先分配在该区域,同时JVM可以为每个线程分配一个私有的缓存区域,称为TLAB(Thread Local Allocation Buffer) 新生代的Survivor区域: 当Eden区域内存不足时会触发Minor GC,也称为新生代GC,在Minor GC存活下来的对象,会被复制到Survivor区域中 老年代: 老年代放置长生命周期的对象,通常是从Survivor区域拷贝过来的对象,不过当对象过大的时候,无法在新生代中用连续内存的存放,那么这个大对象就会被直接分配在老年代上。 永久代: 存储Java类的元数据、常量池、Intern字符串等。在JDK8之后,就将永久代移除,而引入元数据区的概念。 Vritual空间 如果Xms小于Xmx,堆的大小不会直接扩展到上限,而是留着一部分等待内存需求不断增长时,再分配给新生代。Vritual空间便是这部分保留的内存区域 |
OOM可能发生在哪些区域上? | 堆内存:没有内存完成对象实例的分配,并且堆无法再扩展时;可以通过-Xmx和-Xms来控制堆内存的大小。发生堆上OOM的可能是存在内存泄露,也可能是堆大小分配不合理。 Java虚拟机栈和本地方法栈:
方法区:JVM对永久代的垃圾回收并不积极,如果往永久代不断写入数据,例如String.Intern()的调用,在永久代占用太多空间导致内存不足,也会出现OOM的问题 |
内存泄露的实例 | 堆内存泄露:(不再会被使用的对象的内存不能被回收,长生命周期对象持有短生命周期对象的引用)
容器使用时的内存泄露:容器作为一个类的成员变量,甚至是一个静态(static)的成员变量时 提供了close()方法的对象(除非其显式的调用了其close()方法(或类似方法)将其连接关闭,否则是不会自动被GC回收的 单例模式:它的生命周期与整个程序的生命周期看做差不多的,所以是一个长生命周期的对导致的内存泄露(这个对象持有其他对象的引用); |
java 问题定位工具 | |
什么样的对象会被认定为“垃圾”? | 没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾 |
判断对象为“垃圾”的算法 | 引用计数算法: 每个对象(不是引用)都有一个引用计数,该对象分配给一个变量,该变量计数设置为1,对象的某个引用超出了作用域后(该对象丢弃不再使用),对象的引用计数减1 优点: 简单 缺点:无法检测出循环引用
可达性分析算法: 当一个对象到GC ROOT没有任何引用链,则证明这个对象是不可用的。 可作为GC ROOT的对象:1.栈中(栈帧中局部变量表)中引用的对象 2.方法区中静态属性引用的对象 3.方法区中常量引用的对象 4.本地方法栈中JNI引用的对象 |
垃圾回收算法 | 标记-清除算法: 标记阶段:标记出所有需要被回收的对象。从引用根节点开始标记所有被引用的对象 清除阶段:回收被标记的对象所占用的空间,停止所有工 不足:暂停可能会很长、堆产生碎片
复制算法:内存缩减到原来的一半 标记阶段:和标记清除算法一样 清除阶段:内存按容量划分为大小相等的两块,每次只使用其中的一块,存活着的对象复制到另外一块上面
标记-整理算法 标记阶段:和标记清除算法一样 清除阶段:不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存
分代收集算法 大部分JVM的垃圾收集器采用的算法 新生代都采取复制算法 老年代使用标记-清理或标记-整理算法 |
什么时候进行垃圾回收? | 1.程序调用System.gc()时也会触发Full GC 2.新生代的Eden区满发生新生代GC;老年代已满则触发Full GC |
JVM 参数设置 | 栈:-Xss:设置每个线程的堆栈大小 堆:-Xms:初始堆大小 -Xmx:最大堆大小 年轻代、老年代:-XX:NewSize=n:设置年轻代大小 -XX:NewRatio=n:设置年轻代和年老代的比值 年轻代Survivor区:-XX:SurvivorRatio=n 年轻代中Eden区与两个Survivor区的比值 永久代:-XX:MaxPermSize=n:设置最大持久代大小 -XX:MinPermSize=n:设置初始持久代大小
注: NewRatio = 4 老年:年轻=4:1 XX:SurvivorRatio = 8 Eden :两个Survivor = 8: 2 |
垃圾回收器 参考: | 新生代垃圾收集器采用的都是复制算法,老年代垃圾手机采用的有标记-清除算法、标记-整理算法
新生代: Serial串行收集器:单线程收集器,在执行GC过程时,会暂定所有用户线程 ParNew收集器:GC线程由单线程变为多线程,会暂定所有用户线程 Parallel(平行) Scanvage:与ParNew类似 但提升吞吐量,会暂定所有用户线程
老年代: Serial Old:单线程收集器,会暂定所有用户线程,标记-整理算 Parallel Old:同Parallel Scanvage ,标记-整理
CMS:标记-清除 过程:初始标记、并发标记、重新标记、并发清除 缺点:无法处理浮动垃圾(并发清除)、空间碎片、吞吐量会下降(占用了用户线程) G1: 新生代-复制 老年代-整理 过程:初始标记、并发标记、最终标记、筛选回收(对各个区域中的回收价值进行排序,选择允许时间内,价值最大的进行回收) G1收集器将java堆划分成大小相等的独立区域,通过跟踪各个区域里面垃圾堆积的价值大小(回收所获得空间大小以及回收所需时间的经验值),在后台维护一个优先列表,根据允许收集时间,选择一个价值最大的区域进行回收 |
java bean 生命周期 | 加载->链接(验证+准备+解析)->初始化(使用前的准备)->使用->卸载 五个阶段 |
类加载器及 双亲委派模式 | 双亲委派模式: 1. 收到加载请求,直接给父加载器,一直到根 2.从根到下尝试加载 类加载器之间的关系不是继承,而是使用组合来复用父加载器的代码 为什么使用双亲委派模式?
|
加载阶段完成任务 | a.通过一个类的全限定名来获取定义此类的二进制字节流。 b.将定义类的二进制字节流所代表的静态存储结构转换为方法区的运行时数据结构。 c.在java堆中生成一个代表该类的java.lang.Class对象,作为方法区数据的访问入口。 |
链接 | 读入到内存的类的二进制数据合并到虚拟机的运行时环境中去 1验证 验证一下这个类是否合法,文件格式验证、元数据验证、符号引用验证 2准备 为类的静态变量分配内存并设为jvm默认的初值 3解析 常量池中的符号引用转换为直接引用 类或接口名、字段名、方法名转换为具体的内存地址 |
初始化 | 类的初始化过程是这样的:按照顺序自上而下运行类中的变量赋值语句和静态语句,如果有父类,则首先按照顺序运行父类中的变量赋值语句和静态语句。 |
class 文件结构 | 字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件中 魔数、Class文件的版本、常量池入口、访问标志、类索引、父类索引与接口索引集合、字段表集合、方法表集合、属性表集合 |