- 请你谈谈对JVM的理解?java8虚拟机和之前的变化更新
- 什么是OOM,什么是栈溢出StackOverFlowError?怎么分析
- JVM的常用调优参数有哪些
- 内存快照如何抓取,怎么分析Dump文件?
- 谈谈JVM中,类加载器你的认识
Java运行机制
既是解释性语言,又是编译性语言
编写
.java
编译
使用java编译器对.java文件进行编译,生成.class字节码文件
运行
JVM解释器将.class字节码文件翻译成机器码,并执行程序显示结果
字节码文件:二进制文件
Java虚拟机:编译后的Java程序指令不在计算机的CPU上执行,而是在JVM上执行
JVM的位置
运行在操作系统之上,JVM用C写的
JRE包含了JVM
默认hotspot实现
JVM的体系结构
栈和程序计数器不会存在垃圾
JVM调优仅存在在堆中
类加载器
作用:加载class文件~ new Student();
虚拟机自带的加载器
启动类(根)加载器
jre/lib下的rt.jar、resources.jar、charsets.jar和class等
扩展类加载器
jre/lib/ext的jar包和class文件
应用程序(系统类)加载器
当前应用的classpath下的所有类
双亲委派机制
用户自定义类加载器 User (继承classLoader重写findclass方法,加载对应类)
类加载器收到类加载的请求 Application app
将这个请求向上委托为父类加载器去完成,一直向上委托,直到启动类加载器
启动类加载器检查是否能够加载这个类,能加载就会儿三个月,使用当前的加载器,否则,抛出异常,通知自家在其进行加载
重复步骤 3
Class Not Found Exception~
null: java调用不到~ 因为c、c++写的
Java = C++— 去掉繁琐的东西:指针,内存管理
存在意义:
- 通过委派的方式,可以避免类的重复加载,当附加在其已经加载过某一个类时,子加载器就不会再重新加载这个类
- 保证安全性:bootstrap ClassLoader被加载时,只会加载JAVA_HOME/lib中的jar包的类,那么这些类不会被随意替换,避免有人自定义一个有破坏功能的java.lang.被加载。可以*有效防止核心JAVA API被篡改
有时也需要打破双亲委派机制:
沙箱安全机制
沙箱机制就是将java代码限定在虚拟机(jvm)特定的运行范围中,并且严格限制代码对本地系统资源的访问,通过这样的措施来保证对代码的有效隔离,防止对系统造成破坏
沙箱的基本组件:
字节码校验器:确保Java类文件遵循java语言规范。这样可以帮助Java实现内存保护。但并不是所有类文件都会经过字节码校验,比如核心类。
类装载器
Native关键字
1 | public class demo { |
Native Method Area/Stack
它具体做法是Native Method Stack中登记native方法,在执行引擎执行的时候加载Native Libraies
PC寄存器
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向一条指令的地址,即即将要执行的指令代码)在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计
方法区
Method Area
方法区是被所有线程共享的,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间
静态变量static、常量final、类信息Class(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关
栈
程序 = 数据结构 + 算法:持续学习
程序 = 框架 + 业务逻辑:吃饭用
为什么main()限制性,最后结束
栈:栈内存,主管程序的运行,生命周期和线程同步
线程结束,占内存也就释放了;对于栈来说,不存在垃圾回收问题
栈:8大基本类型+对象引用+实例的方法
- 每个线程都包含一个栈区,栈中只保存基本数据类型的值和对象的应用以及基础数据的引用
- 每个栈中的数据(基本数据类型和对象的应用)都是私有的,其他栈是无法进行访问的
- 栈分为三个部分:基础类型变量区、执行环境上下文,操作指令区(存放操作指令)
栈运行:java中,每执行一个方法都会产生一个栈帧
栈满抛异常:StackOverflowError
栈 + 堆 + 方法区的交互关系:
画出一个对象在内存中实例化的过程
https://blog.csdn.net/qq_43430343/article/details/119709715
https://yangyongli.blog.csdn.net/article/details/126465785
三种JVM
- Sun公司
HotSpot
- BEA
JRockit
- IBM
J9 VM
我们学习都是HotSpot
堆
Heap,一个JVM只有一个堆内存,被所有线程所共享,堆内存的大小是可以调节的,通过-Xmx(堆的最大容量)和-Xms(堆的初始容量)控制
类加载器读取了类文件后,一般会把:类、方法、常量、变量等 保存我们所有引用类型的展示对象
新生代(young)、老年代(old)、元空间
新生代:被细分为Eden和两个Survior区域,这两个Survivor区域分别被命名为from和to以示区分(默认Edem:from:to=8:1:1)
GC垃圾回收,主要是在伊甸园区和养老区
假设内存满了,OOM:堆内存不够(java.lang.OutOfMemoryError: Java heap space)
在JDK8后,永久存储区改了个名字:元空间
永久区:这个区域常驻内存。用来存放JDK自身携带的Class对象、Interface元数据,存储得到是Java运行时的一些环境或类信息,这个区域不存在垃圾回收!关闭VM虚拟机就会释放这个区域的内存
- jdk1.6之前:永久代,常量池在方法区
- jdk1.7:永久代,但是慢慢的退化了,
去永久代
,常量池在堆中 - jdk1.8之后:无永久代,方法区成为了概念,变成了元空间存在堆中,常量池在元空间(方法区和堆的关系https://blog.csdn.net/wangkexiang21/article/details/124014726)
方法区的OOM原因:如果一个启动类,加载了大量的第三方jar包;Tomcat部署了太多的应用,大量动态生成的反射类。不断的被加载。知道内存满,就会出现OOM
(字符串常量池在jdk1.6存在于方法区,1.8知乎元空间化字符串常量池移存到了堆,但是运行时常量池和类文件常量池放在元空间中)
元空间逻辑上存在,物理上不存在
1 | public class demo02 { |
堆内存调优
在一个项目中,突然出现了OOM故障,那么该如何排除研究为什么出错
- 能够看到代码第几行出错:内存快照分析工具(MAT,Jprofier)
- Debug,一行行分析代码
MAT,Jprofiler作用:
- 分析Dump内存文件,快速定位内存泄漏
- 获取堆中的数据
- 获得大的对象
// -Xms1024m -Xmx1024m -XX:+PrintGCDetails
查看GC详细信息
-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
生成对应JProfile文件查看OOM故障原因(JVM在发生内存溢出时自动的生成堆内存快照)
GC(垃圾回收机制)
GC的作用区域:堆、方法区
JVM在进行GC时,并不是对这三个区域统一回收。大部分时候,回收都是新生代
- 新生代
- 幸存区(from,to)
- 老年区
GC两种类:轻GC(普通的GC),重GC(全局GC)
题目:
- JVM的内存模型和分区,详细到每个区放什么
- 对立面的分区有哪些?Eden,from,to,老年区,说说他们的特点
- GC的算法有哪些?标记清除法,标记压缩,复制算法,引用计数器在,怎么用的?
- 轻GC和重GC分别在什么时候发生?
常有算法
https://blog.csdn.net/weixin_37288522/article/details/103944956
引用计数法(每个对象分配一个计数器记录调用次数)
复制算法
- 好处:没有内存的碎片
坏处:浪费了内存空间:多了一半空间永远是空to(假设对象100%存活->极端情况)空间换时间
复制算法最佳使用场景:对象存货度较低的时候;新生区
标记清除算法
- 优点:不需要额外的空间
- 缺点:两次扫描严重浪费时间,会产生内存碎片
标记压缩/标记整理
再优化:1.标记 2.压缩 3.清除
总结
内存效率:
复制算法>标记清除算法>标记压缩算法(时间复杂度)
内存整齐度:
复制算法=标记压缩算法>标记清除算法
内存利用率:
标记压缩算法=标记清除算法>复制算法
没有最好的算法,只有最合适的算法
GC: 分代收集算法
年轻代:
- 存活率低
- 复制算法!
老年代:
- 区域大:存活率高
- 标记清除(内存碎片不是太多) + 标记压缩混合实现
推荐书籍《深入理解JVM》
JMM(Java Memory Model)
什么是JMM
Java内存模型
用途:
缓存一致性协议,用于定义数据读写的规则
JMM定义了线程工作内存和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory)
解决共享对象可见性的问题:voliate
学习过程:
voliate:
“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。详见https://blog.csdn.net/qq_35757264/article/details/124493844