- 请你谈谈你对jvm的理解?
- Java8虚拟机和之前的变化更新?
- 什么是OOM?什么是栈溢出StackOverFlowError?怎么分析?
- jvm的常见调优参数有哪些?
- 内存快照如何抓取?怎么分析Dump文件?
- 谈谈jvm中,类加载器你的认识?
1. JVM的位置
2. JVM的体系结构
3. 类加载器
4. 双亲委派机制
5. 沙箱安全机制
6. Native
7. PC寄存器
8. 方法区
9. 栈
10. 三种JVM
11. 堆
12. 新生区、老年区
13. 永久区
14. 堆内存调优
15. GC
常用算法
16. JMM
JVM(Java Virtual Machine)是 Java 虚拟机,用于运行 Java 编译后的二进制字节码,最后生成机器指令。JVM 是 Java 能够跨平台的核心。JDK : (Java Development Kit),Java 开发工具包。JDK 是整个 Java 开发的核心,集成了 JRE 和javac.exe,java.exe,jar.exe 等工具。JRE : (Java Runtime Environment),Java 运行时环境。主要包含两个部分,JVM 的标准实现和 Java 的一些基本类库。它相对于 JVM 来说,多出来的是一部分的 Java 类库。
JVM 与操作系统之间的关系: JVM 上承开发语言,下接操作系统,它的中间接口就是字节码。
因此,JVM 的位置取决于你安装了哪个 JDK 版本和你的操作系统。如果你使用 Windows 操作系统,则可以在 cmd 中输入 java -version 命令来查看 JDK 的安装位置。如果你使用 Linux 操作系统,则可以在终端中输入 which java 命令来查看 JDK 的安装位置。
垃圾回收,指的的堆内存的垃圾回收
在 JVM 中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫描那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收
JVM 的垃圾回收机制主要是针对堆内存消亡的对象的回收和内存分配。在 JVM 进行垃圾回收之前,首先就是判断哪些对象是垃圾,也就是说,要判断哪些对象是可以被销毁的,其占有的空间是可以被回收的
Java 的垃圾回收机制主要采用了引用计数法和可达性分析算法两种方式来判断哪些对象可以被销毁。其中引用计数法是为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。但是它有一个缺点是不能解决循环引用的问题。可达性分析算法则从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。
Java 的垃圾回收机制主要采用了四种垃圾回收算法:标记-清除算法、复制算法、标记-整理算法和分代算法。其中标记-清除算法效率不高,无法清除垃圾碎片;复制算法内存使用率不高;标记-整理算法效率较高;分代算法则根据对象存活周期的不同将内存划分为几块,一般包括年轻代、老年代和永久代。
JVM调优,99%是调堆
JVM 调优的目的是追求更低的系统延迟和更高的系统吞吐量,衡量系统在稳定状态下所需要的最低内存占用量。JVM 调优主要是针对垃圾收集器的收集性能优化,令运行在虚拟机上的应用能够使用更少的内存以及延迟获取更大的吞吐量
JVM 调优一般包含多个层次,比如:架构调优、代码调优、JVM 调优、数据库调优、操作系统调优等。其中架构调优和代码调优是 JVM 调优的基础,架构调优是对系统影响最大的
JVM 调优主要是通过不断测试调整 JVM 的运行参数,尽可能让对象都在新生代 (Eden) 里分配和回收,尽量别让太多对象频繁进入老年代,避免频繁对老年代进行垃圾回收,同时给系统充足的内存大小,避免新生代频繁的进行垃圾回收
加载Class文件
- 引导类加载器(BootstrapClassloader):用C++编写,是JVM自带的类加载器;负责加载Java的核心类库。(该加载器无法直接获取)
- 扩展类加载器(ExtClassloader):负责加载/jre/lib/ext目录下的jar包。
- 应用程序类加载器(AppClassloader):负责加载java -classpath或-D java.class.path所指的目录下的类与jar包。(最常用的加载器)
- 类加载器接收到一个加载请求时,他会委派给他的父加载器,实际上是去他父加载器的缓存中去查找是否有该类,如果有就加载返回,如果没有则继续委派给父类加载,直到顶层类加载器。
- 如果顶层类加载器也没有加载该类,则会依次向下查找子加载器的加载路径,如果有就加载返回,如果都没有,则会抛出异常。
- 这种工作机制可以保证Java核心库的类型安全,防止用户自己编写的同名类被优先加载,从而确保Java程序的稳定运行
Java沙箱安全机制是一种用于保护Java代码和本地系统之间的安全技术,它可以限制Java代码对本地资源的访问,防止其造成潜在的危害。Java沙箱安全机制的发展经历了几个阶段,从最初的完全隔离到后来的可配置的权限控制,以及引入了代码签名、域和安全管理器等概念。Java沙箱安全机制的基本组件包括字节码校验器、类加载器、存取控制器、安全管理器和安全软件包等
凡是使用了native关键字的,说明Java的作用范围已经达不到了,它会去调用底层的C语言的库。
- 进入本地方法栈。
- 调用本地方法接口。JNI
JNI的作用:扩展Java的使用,融合不同的语言为Java所用。(最初是为了融合C、C++语言)
因为Java诞生的时候,C和C++非常火,想要立足,就有必要调用C、C++的程序。
所以Java在JVM内存区域专门开辟了一块标记区域Native Method Area Stack,用来登记native方法。
在最终执行(执行引擎执行)的时候,通过JNI来加载本地方法库中的方法。
程序计数器:Program Counter Register
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向像一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。
Method Area方法区(此区域属于共享区间,所有定义的方法的信息都保存在该区域)
方法区是被所有线程共享,所有字段、方法字节码、以及一些特殊方法(如构造函数,接口代码)也在此定义。静态变量,常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中但是实例变量存在堆内存中,和方法区无关
static final, Class, 常量池
栈内存,主管程序的运行,生命周期和线程同步;
线程结束,栈内存也就释放了,对于栈来说,不存在垃圾回收问题。
8大基本类型、对象引用,实例的方法。
- Sun公司的HotSpot。(java -version查看)
- BEA的JRockit
- IBM的J9VM
Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的。
类加载器读取了类文件后,一般会把什么东西放到堆中?
类、方法、常量、变量、保存我们所有引用类型的真实对象。堆内存中细分为三个区域:
- 新生区(伊甸园区)Young/New
- 养老区 old
- 永久区 Perm
新生区又叫做伊甸园区,包括:伊甸园区、幸存0区、幸存1区。
这个区域是常驻内存的。
用来存放JDK自身携带的Class对象、Interface元数据,存储的是Java运行时的一些环境或类信息~。
这个区域不存在垃圾回收。
关闭JVM虚拟机就会释放这个区域的内存。什么情况下,在永久区就崩了?
- 一个启动类,加载了大量的第三方jar包。
- Tomcat部署了太多的应用。
- 大量动态生成的反射类;不断的被加载,直到内存满,就会出现OOM
这个区域常驻内存的。用来存放JDK自身携带的Class对象。Interface元数据 ,存储的是ava运行时的一些环境或类信息
什么是永久代和元空间??
方法区是一种规范,不同的虚拟机厂商可以基于规范做出不同的实现,永久代和元空间就是出于不同jdk版本的实现。
方法区就像是一个接口,永久代与元空间分别是两个不同的实现类。
只不过永久代是这个接口最初的实现类,后来这个接口一直进行变更,直到最后彻底废弃这个实现类,由新实现类—元空间进行替代。
jdk1.8之前:
jdk1.8以及之后:在堆内存中,逻辑上存在,物理上不存在(元空间使用的是本地内存)
Java OOM异常通常是由于内存泄漏或者内存溢出导致的。内存泄漏指的是程序中已经不再使用的对象仍然被引用,导致垃圾回收器无法回收,最终导致内存溢出。而内存溢出则是指程序在申请内存时,没有足够的空间供其使用,最终导致程序崩溃
Java OOM异常通常有以下几种原因:
- Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常。
- 无法在Java堆中分配对象。
- 应用程序保存了无法被GC回收的对象。
- 应用程序过度使用finalizer。
好处:没有内存的碎片。
坏处:浪费了内存空间(多了一半空间to永远是空)。假设对象100%存活(极端情况),不适合使用复制算法。
使用场景
复制算法最佳使用场景:对象存活度较低的时候(新生区)
- 优点:不需要额外的空间。
- 缺点:两次扫描,严重浪费时间,会产生内存碎片。
标记清除压缩(改进)
内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)
内存整齐度:复制算法=标记压缩算法>标记清除算法
内存利用率:标记压缩算法=标记清除算法>复制算法思考一个问题:难道没有最优算法吗?
答案:没有,没有最好的算法,只有最合适的算法——》GC:分代收集算法年轻代:
- 存活率低
- 复制算法
老年代:
- 区域大:存活率高
- 标记清除(内存碎片不是太多)+标记压缩混合实现
JMM(Java Memory Model),Java的内存模型。
缓存一致性的协议,用来定义数据读写的规则。
JMM定义了线程工作内存和主内存的抽象关系:线程的共享变量存储在主内存中,每个线程都有一个私有的本地工作内存。
使用volatile关键字来解决共享变量的可见性的问题。
Java内存模型是围绕着并发编程中原子性、可见性、有序性这三个特征来建立的。
JMM定义了8种操作来完成(每一种操作都是原子的、不可再拆分的)。
- lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
- unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
- read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
- load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
- use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎(每当虚拟机遇到一个需要使用到该变量的值的字节码指令时将会执行这个操作)。
- assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量(每当虚拟机遇到一个给该变量赋值的字节码指令时执行这个操作)。
- store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
- write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
一个或多个程序指令,要么全部正确执行完毕不能被打断,或者全部不执行
当一个线程修改了某个共享变量的值,其它线程应当能够立即看到修改后的值。
程序执行代码指令的顺序应当保证按照程序指定的顺序执行,即便是编译优化,也应当保证程序源语一致。