JVM
1. JVM 的位置
在操作系统上,可以看成是一个 软件,包含在 JRE 中
三种JVM
- Sun 公司 HotSpot(使用最多)
- BEA JRockit
- IBM J9 VM
2. JVM 的体系结构
不会发生垃圾回收的区域:
JVM 调优的区域:
3. 类加载器
加载器类别
- 虚拟机自带的加载器
- 系统类(根)加载器(最终执行)
- 扩展类加载器
- 应用程序加载器
4. 双亲委派机制
采用这种机制,安全
AppClassLoder(应用程序加载器) --> PlatformClassLoder(扩展类加载器,JDK 1.9 之后) --> BootstrapClassLoder(系统类加载器)
执行过程:
- 类加载器收到类加载的请求
- 将这个请求向上委托给父类加载器去完成,一直向上委托,直到系统类加载器
- 系统加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则,抛出异常,通知子加载器进行加载
- 重复步骤 3
- 如出现 null :说明 java 调用不到,因为底层是 c、c++ 写的
5.沙箱安全机制
Java 安全模型的核心就是 Java 沙箱(sandbox)
定义:沙箱是一个限制程序运行的环境,沙箱机制就是将 Java 代码限定在虚拟机特定的运行范围中,并且严格限制代码对贝蒂系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。
沙箱主要限制系统资源访问,那系统资源包括什么?
- CPU
- 内存
- 文件系统
- 网络
目前的安全机制实现,引入了域(Domain)的概念,虚拟机会把所有代码加载到不同的系统域和应用域。
组成沙箱的基本组件:
- 字节码校验器(bytecode verifier)
- 类加载器
类加载器采用的机制是双亲委派机模式
6. Native 关键字
凡是带了 native 关键字的,说明 java 的作用范围达不到了,会去调用底层 c 语言的库!
会进入本地方法栈,调用本地方法接口:JNI
JNI 作用:扩展 java 的使用,融合不同的编程语言为 Java 所用!最初:c、c++
它在内存区域中专门开辟了一块标记区域:Native Method Stack,登记 native 方法,在最终执行的时候,加载本地方法库中的方法通过 JNI
7. PC 寄存器
程序计数器(Program Counter Register)
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向像一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计
8. 方法区
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数、接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间:
静态变量(static)、常量(final)、类信息(构造方法、接口定义)、运行时的常量池 都存在方法区中,但是 实例变量存在堆内存中,和方法区无关
9. 栈
先进后出
栈内存,主管程序的运行,生命周期和线程同步
线程结束,栈内存也就释放啦,对于栈来说,不存在垃圾回收问题
栈溢出(可能是 递归调用 造成,循环调用,不断压栈)
存在栈里的内容:
- 8 大基本类型
- 对象引用
- 实例的方法
栈运行原理:栈帧
栈满了:StackOverFlowError
10. 堆
一个 JVM 只有一个堆内存,堆内存的大小是可以调节的
类加载器读取了类文件后,把什么东西放到堆中?
- 类
- 方法
- 常量
- 变量
- 所有引用类型的真实对象
堆内存中细分为三个区域:
- 新生区
- 老年区
- 元空间(永久区)
GC 垃圾回收,主要发生在 新生区 和 老年区
当一个对象经历了 15 次 GC 后,就会进入 老年区
假设内存满了,会发生 OOM(堆内存不够)
11. 新生区、老年区、元空间(JDK8之前为永久区)
永久区用来存放 JDK 自身携带的 Class 对象、Interface 元数据、存储的是 java 运行时的一些环境或类信息,这个区域不存在垃圾回收,关闭虚拟机会释放这个区域的内存
若一个启动类,加载了大量的第三方 jar 包,Tomcat 部署了太多的应用,大量动态生成的反射类,不断的被加载,直到内存满,就会出现 OOM
元空间 逻辑上存在,物理上不存在
12. 堆内存调优
分析出现 OOM 的原因:
-
尝试扩大堆内存空间看结果(默认情况下:分配的总内存是电脑内存的 1/4,而初始化的内存为:1/64)
-
使用工具进行分析第几行代码出错
内存快照分析工具,Jprofiler
作用:可以分析 Dump 内存文件,快速定位内存泄漏,获得堆中的数据,获得大的对象
13. GC 常用算法
-
引用计数法
-
复制算法
在幸存区中,from 和 to 会不停交换,谁空谁是 to,为了保证其中一个是空的,使用复制算法将其中一个复制到 to 中,再将另一个作为 to,保证 to 是空的
好处:没有内存的碎片
坏处:浪费了内存空间
-
标记清除算法
扫描对象,对活着的对象进行标记
清楚没有标记的对象,进行删除
优点:不会浪费内存空间
缺点: 两次扫描,消耗了更多的时间。且又内存碎片产生
-
标记压缩算法
防止内存碎片产生,再次扫描,向一端移动存活的对象,但多了一个移动成本
14. GC 算法总结
内存效率:复制效率 > 标记清楚算法 > 标记压缩算法 (时间复杂度)
内存整齐度:复制算法 = 标价压缩算法 > 标记清除算法
内存利用率:标记压缩算法 = 标记清除算法 > 复制算法
没有最好的算法,只有最合适的算法
GC 为分代收集算法
年轻代:
存活率低:复制算法
老年代:
区域大,存活率高
标记清除(内存碎片不是太多)+ 标记压缩混合 实现