参考:https://blog.csdn.net/yerenyuan_pku/article/details/103320757
一些常见面试题(全是理论)
- 谈谈你对 JVM 的理解?Java8 虚拟机和之前的变化
- 什么是 OOM,什么是栈溢出 StackOverflowError?怎么分析?
OOM,全称是 Out Of Memory,内存用完了。(JVM 没有足够的内存来为对象分配空间并且垃圾 回收器也没有空间可以回收了,就会抛出这个 error)
原因
:1)分配的少了(启动时 VM 参数可以指定虚拟机本身可使用的内存大小)
2)应用用的太多了(会造成内存泄露和内存溢出)
最常见的 OOM 情况
:
1)堆内存溢出
2)方法区溢出
3)StackOverflowError 栈溢出
分析
—heapdump
dump 堆的内存镜像,可以采用两种方式:
1. 设置 JVM 参数 HeapDumpOnOutOfMemoryError
2. 使用 JDK 自带的 jamp 命令 "jmap -dump:format=b,file=heap.bin
dump 堆的内存镜像,使用工具分析:mat、jhat
详细解释:https://www.cnblogs.com/ThinkVenus/p/6805495.html
- JVM 的常用调优参数有哪些?
- 内存快照如何抓取,怎么分析 Dump 文件?
- 谈谈 JVM 中类加载器你的认识?
一些概念理解
# 1.JVM 的位置
# 2.JVM 的体系结构
# 3. 类加载器
作用:加载 class 文件,
类是模板,对象是具体的
具体对象放在堆里,对象引用放在栈里,存在的是对象的地址
getClass()
getClassLoader()
1. 类加载器收到类加载器的请求
2. 将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器
3. 启动加载器检查是否能够加载当前类,能加载就结束,使用当前的加载器,否则,抛出异常,通知子加载器加载
4. 重复步骤 3
Class Not Found~
null:Java 调用不到,
Java:c+±- 去掉的繁琐部分,如指针、内存管理
类装载器(ClassLoader)负责加载 class 文件,class 文件在文件开头有特定的文件标示,ClassLoader 只负责 class 文件的加载,至于它是否可以运行,则由 Execution Engine 决定。
jvm 虚拟机自带的类加载器有四种:
1. 启动类加载器(Bootstrap):c++ 编写
2. 扩展类加载器(Extension):Java 编写
3. 应用程序类加载器(AppClassLoader):或者叫系统类加载器,用于加载当前应用 classpath 下的所有类
4. 用户自定义加载类:Java.lang.ClassLoader 的子类,用户可以定制类的加载方式
双亲委派,向上委托 4321
# 4. 双亲委派机制
类加载的时候的一个安全机制,为了保证安全
app(当前应用程序)->exc (扩展)->boot
如上图所示,自顶向下加载,保证代码安全,防止恶意代码对源代码的修改。
# 5. 沙箱安全机制
详细说明参考:https://www.cnblogs.com/MyStringIsNotNull/p/8268351.html
就是说你写了一个 Java 程序,默认情况你是可以任务访问这台机器的任何资源的。但是你把程序部署到正式的服务器上的时候,要保证你的程序不会对这个服务器有伤害,访问一些不该访问的资源。
消除安全隐患,有两种办法:
1. 让你的程序在一个限定权限的账号下运行
2. 利用 Java 的 沙箱机制
来限定程序不为非作歹。
沙箱(sandbox)是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机特性的运行范围,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离。
组成沙箱的基本 组件
:字节码校验器、类装载器、存取控制器、安全管理器、安全软件包
沙箱包含的 要素
:权限、代码源、保护域、策略文件、密钥库
# 6.Native
加个 native 关键字,说明 Java 的作用范围达不到了,会去调用底层 C 语言的库
会进入本地方法栈,调用本地方法接口,即 JNI
JNI 的作用:扩展 Java 的使用,融合不同的编程语言为 Java 使用!最初:C、C++
Java 诞生的时候:C、C 横行,想要立足,必须要调用 C、c
在 Execution 执行的时候,加载本地方法库
它再内存区域中专门开辟了一块标记区域,Native Method Stack 本地方法栈
,限定只标记了 native
的方法才能塞进去。塞进去的方法但凡想要被执行,只能求助于操作系统,然后调用 本地方法接口
(操作系统的),调用本地方法接口时还需要 本地方法库
(类似于 jar 包,即 dll 动态连接库)的支持,最后,本地方法想要运行,还得把这个方法先做一个 入栈
的操作。
目前这个方法使用的越来越少了,除非是跟硬件相关的应用,比如通过 Java 程序驱动打印机或者 Java 系统管理生产设备,在企业级应用中少见。因为现在的异构领域间的通信很发达,比如可以 Socket 通信,也可以使用 WebService 等。
# 7.PC 寄存器
每个线程都有一个程序计数器,线程私有,它是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,即即将要执行的指令)由执行引擎读取下一条指令。
pc 寄存器(程序计数器)不是用来做存储的,是用来做计算的。
# 8. 方法区
方法区是被所有线程 共享
的,所有字段和方法字节码,以及一些特殊方法如构造函数、接口代码等也在此定义。简单说,所有定义的方法的信息都保存在该区域,此区属于共享区间。
类加载器把.class 文件读到内存里面变成 Class(元数据模板)之后,变成的 Class(元数据模板)就存放在方法区里面,相应地,所有的 Class(元数据模板)包含的信息都会放进去,包含的信息如下图所示。
静态变量、常量、类信息(构造函数、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,与方法区无关。
static、final、Class 模板、常量池
# 9. 栈
一种数据结构,先进后出、后进先出
程序 = 数据结构 + 算法:持续学习
码农 = 框架 + 业务逻辑:吃饭
为什么 main 方法先执行,最后结束?
就是使用了栈
栈内存,主管程序的运行,生命周期和线程同步
线程结束,栈内存也就释放了,对于栈来说,不存在垃圾回收问题
一旦线程结束,栈就 over 了
其中,8 种基本类型的变量 + 对象的引用变量 + 实例方法都是在函数的栈内存中分配的。
栈运行原理:栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存块,是一个数据集,也是一个有关方法和运行期的数据集。当一个方法被调用时会产生一个栈帧。
更加详细的栈内的内存结构如下:
反正就是说,每执行一个方法都会产生一个栈帧,保存到栈的顶部,顶部栈就是当前的方法,该方法执行完毕后会自动将此栈帧出栈,这个栈帧被弹出栈的时候就证明这个方法执行完毕了。
栈满了:StackOverError
# 10. 三种 JVM
-
Sun 公司 HotSpot 命令行可以查
C:\Users\Lenovo>java -version
java version “1.8.0_202”
Java™ SE Runtime Environment (build 1.8.0_202-b08)
Java HotSpot™ 64-Bit Server VM (build 25.202-b08, mixed mode) -
BEA 公司 JRockit 最快,适合财务那些业务,很少用
-
IBM 平台 j9 VM
# 11. 堆
heap,一个 jvm 只有一个堆内存,堆内存的大小是可以调节的
类加载器读取了类文件后,一般会把什么东西放在堆中?类,方法,常变量~,保存我们所有引用类型的真实对象
堆内存还要细分为三个区:
-
新生区(伊甸园、幸存区(过度到养老区))
-
养老区:一般不会被干掉
-
永久区
1.8 之后永久区用元空间代替了
GC 垃圾回收,主要是在伊甸园区和养老区
假设内存满,OOM,堆内存不够 OutOfMemoryError
# 12. 新生区、老年区
-
新生区:
类诞生和成长,甚至死亡的地方
伊甸园区:所有的对象都是在伊甸园区 new 出来
幸存区(0、1):
真相:经过研究,99% 的对象都是临时对象!
这个图一目了然
# 13. 永久区
这个区域常驻内存的,用来存放 JDK 自身携带的 class 对象。Interface 元数据,存储的是 Java 运行时的一些环境或类信息,这个区域不存在垃圾回收,关闭虚拟就会释放这个区域的内存
jdk1.6 之前:永久代,常量池是在方法区
jdk1.7:永久代,但是慢慢退化了,去永久化,常量池在堆中
jdk1.8:无永久代,常量池在元空间
方法区在元空间里,即堆里,也叫非堆(方法区相当于一个接口,而元空间时它的一个实现)
方法区里有一个小空间是常量池
方法区的实现:永久区、元空间
# 14 堆内存调优
参考链接:https://blog.csdn.net/yerenyuan_pku/article/details/103327463?spm=1001.2014.3001.5502
Java8 堆内存的分布情况:
要多堆内存进行调优,必然要接触到以下三个参数:
使用:在调节 VM 参数的窗口设置:-Xms1024m -Xmx1024m -XX:+PrintGCDetails
在控制台打印相关信息
所以这里设置的内存大小只包括新生区和养老区的总和大小,即调整的是物理上的堆内存大小。
当出现 OOM 内存不足的时候如果查看信息和分析信息呢?
前面面试题第二题其实也说过了,下面放一些示例的图片:
设置把堆内存镜像 dump 出:使用 JVM 参数
使用 mat 工具分析 dump 文件:(是 eclipse 的工具,可以在上面安装即可)
上面生成的 dump 文件是默认存储在工程的根目录下,用 - XX:HeapDumpPath=目录
这个参数可以修改 dump 文件的位置
这样就可以在 D 盘看到这个 dump 文件了
如果使用的不用 eclipse 工具,也可以使用 jdk 自带的 jvisualvm.exe
工具来解析 dump 文件,在安装路径的 bin 目录下,直接双击这个程序,打开对应的 dump 文件就可以了。
# 15.GC 常用方法
参考链接:https://blog.csdn.net/yerenyuan_pku/article/details/103387312?spm=1001.2014.3001.5502
-
什么是 GC?就是分代收集算法。
-
GC 的作用域?
GC 按照回收的区域分为普通 GC 也叫
轻量级GC
(Minor GC),一种是全局 GC 也叫重量级GC
(Major GC or Full GC) -
GC 的四大算法
引用计数法:
在程序 new 一个对象出来的时候,我们给他一个计数器,有人引用这个对象的时候,这个计数器就自增 1,引用取消的时候减 1,当计数器为 0 的时候就 JVM 就可以把他回收掉了。已经淘汰了!
复制算法
(Copying): 更详细的讲解可以看链接啦。
就是说 new 对象放在伊甸园区,
然后再第一次进行垃圾回收的时候勒,就将存活下来的对象 copy 到幸存 0 区,
然后第二次将存活的对象(包括伊甸园和幸存 0 区)copy 到幸存 1 区,清空幸存 0 区了;
然后继续第三次的时候将伊甸园和 1 区的对象 copy 到 0 区,这样反复。
到 15 次(默认,可以通过 -XX:MaxTenuringThreshold
设置)还活下来的话就复制到老年区
标记清除算法
在内存用满的时候,GC 线程触发把程序暂停,然后堆内存遍历两次,第一次标记 被引用的对象,要存活下来,第二次清除没被标记的对象。
但是这时候内存的碎片的。
标记压缩算法
老年代一般是由标记清除或者是标记清除与标记整理的混合实现。
标记清除压缩算法
就是上面两种算法的结合,先用多次的标记清除,然后这时候内存空间已经千疮百孔了,到处都是内存碎片,然后这时候统一用一次标记压缩就好了。
- 三种算法总结:
基于上面的考虑,老年代一般是由标记清除或者是标记清除与标记压缩的混合实现。以 HotSpot 中的 CMS 回收器为例(只限于 Java8),CMS 是基于 Mark-Sweep 实现的,对于对象的回收效率很高,而对于碎片问题,CMS 采用基于 Mark-Compact 算法的 Serial Old 回收器做为补偿措施:当内存回收不佳(碎片导致的 Concurrent Mode Failure 时),将采用 Serial Old 执行 Full GC 以达到对老年代内存的整理。
# 16.JMM
参考链接:https://zhuanlan.zhihu.com/p/258393139
温馨提醒一下,这里有些人会把 Java 内存模型误解为 Java 内存结构,然后答到堆,栈,GC 垃圾回收,最后和面试官想问的问题相差甚远。实际上一般问到 Java 内存模型都是想问多线程,Java 并发相关的问题。
如果面试的话,重点是 Java 内存模型 (JMM) 的 工作方式
, 三大特征
,还有 volatile
关键字。为什么喜欢问 volatile 关键字呢,因为 volatile 关键字可以扯出很多东西,比如可见性,有序性,还有内存屏障等等。
全称 Java Memory Model,JMM,Java 内存模型,它不仅仅是 JVM 内存分区。
那这个 JMM 到底是什么东东啊?
用来干嘛的?
因为硬件生产商和操作系统的不同,内存的访问就有一定的差异,为了避免相同的代码在不同的系统下运行出现各种问题,Java 内存模型(JMM)就屏蔽掉各种硬件和操作系统的内存访问差异,实现让 Java 在不同的平台下都有相同的并发效果。
工作方式
:
Java 内存模型规定所有的变量都存储在主内存中,包括实例变量,静态变量,但是不包括局部变量和方法参数。每个线程都有自己的工作内存,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程对变量的操作都在工作内存中进行。线程不能直接读写主内存中的变量。
不同的线程之间也无法访问对方工作内存中的变量。线程之间变量值的传递均需要通过主内存来完成。
每个线程的工作内存都是独立的,线程操作数据只能在工作内存中进行,然后刷回到主存。这是 Java 内存模型定义的线程基本工作方式。
三个特征
:原子性、可见性、有序性
原子性(synchronized)
可见性(volatile、synchronized、final)
有序性(volatile、synchronized)
八大内存交互操作
:
volatile
:
1. 保证线程间变量的可见性
2. 禁止 CPU 进行指令重排序
# 17 总结
1. 百度
2. 思维导图
单点登录 | SSO