JVM运行时数据区和GC算法
1.JVM
1.1 JVM和JMM(java memory model )的区别
JVM是java虚拟机,英文名字是java virtual machine,jvm使得java可以跨平台运行,示例如图1所示。
图1-1 java跨平台的示意
已经有九种语言可以在jvm上运行了,都产生了.class文件,可以说jvm不仅是平台无关性,而且是语言无关性了。
Java平台无关性的支持:java语言规范,.class文件,jvm
图1-2 java平台无关性
Java语言本身也为平台无关性做了努力 int
Jvm是平台有关的,因为不同的操作系统对同一操作的指令是不同的
图1-3 jvm平台有关
Jvm的设计有指导规范,但是各家商业机构设计还是有区别。
JRockit和HotSpot 还有J9(IBM)三家主要的
主要参考 深入理解Java虚拟机 第三版 周志明 Hotpot
jdk>JRE>jvm,今天主要说的是Java虚拟机运行时数据区。JMM是java内存模型(java memory model),这两个东西没有直接的关系。JMM是多线程的基础,这里不再详细介绍了。
要想讲清楚JVM就需要把java程序的整个运行过程整出来。
1.2 java程序的运行过程
1.2.1 类的生命周期
.class
图1-4 类加载过程
- 加载:查找和装载class文件,读取class文件的形式有很多;
- 验证:复合虚拟机的规范;
- 准备:为类变量分配内存,并赋予初值。Int 0;但如果是final的话,直接初始化。
- 解析:JVM会将常量池中的符号引用替换为直接引用。
- 初始化:对类变量初始化,也就是static修饰的变量,先初始化其父类,如果是多个静态变量,自上而下初始化;
- 使用:
- 卸载:
1.2.2 类的加载机制
双亲委派模型:没有体现“双”,我觉得就是父亲委派模型。没有父子的继承关系。
图1-5 双亲委派类加载流程
1.3 JVM运行时数据区
图1-6 JVM运行时数据区
这是1.8之前的运行时数据区的划分,
1.3.1本地方法栈和虚拟机栈
非常相似,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常。
1.3.2程序计数器
可以看作是当前线程所执行的字节码的行号指示器。是线程私有的,如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)。此内存区域是唯一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域。
Q:既然本地方法不在程序计数器中指示,那本地方法的线程切换如何做?
1.3.3 堆
分配对象实例,是垃圾回收器管理的内存区域,可以被成为GC堆。如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。
1.3.4方法区
用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。在JDK8以前有的把方法区称为永久代,也使用了垃圾回收算法,从JDK8开始,把方法区全部移到了本地内存的元空间(不占用虚拟机的内存空间了,占用操作系统的内存空间),根据《Java虚拟机规范》的规定,如果方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。
1.4 java的发展
Java从9开始执行每隔半年发布一版
Java8的版本新特性:函数式接口和 Lambda 表达式、接口默认方法等
没有仔细研究
2.GC算法
(GC算法和GC收集器要解决的问题)
那些内存需要回收?
何时回收?
如何回收?
GC算法主要发生的地方是在堆和方法区(常量池 “abc”)。
那些内存需要回收?
- 引用计数器:虽然容易理解,但所有主流的虚拟机都不采用。
- 可达性分析算法:核心就是确定GC Roots,
图2-1 利用可达性分析算法判定对象是否可回收
哪些可以作为GC Roots呢?
·在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
·在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
·在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
·在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
·Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
·所有被同步锁(synchronized关键字)持有的对象。
·反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
对象的自救?
使用finalize方法,但是线程的优先级较低(final finally)
2.1堆的划分
Java堆分为新生代(占三分之一)和老年代(占三分之二),新生代又分为Eden区、Servivor To区和Servivo rFrom区,Eden区占十分之八,To和From分别占十分之一。
图 2-2 堆的划分
Q:新生代为何这么划分(为什么是8:1:1)?
经验值 可以调
几个名词:
- Minor GC/young GC:新生代收集,指目标只是新生代的垃圾收集。
Q:什么情况下会触发Minor GC
实例化之后,年轻代满了,担保机制失效(major GC)
system.gc() full GC;
2.Major GC:老年代收集,指目标只是老年代的垃圾收集。
Q:什么情况下会触发Major GC
担保机制失效
MajorGC 的速度一般会比 Minor GC 慢 10倍以上。
如何避免Major GC
3.Full GC:整堆收集,收集整个Java堆和方法区的垃圾收集。
Q:什么情况下回触发Full GC
System.gc()
4.Mixed GC:混合收集,指目标是收集整个新生代以及部分老年代的垃圾收集。
目前只有G1收集器会有这种行为。
2.2垃圾收集算法
2.2.1标记-清除算法
图2-3 标记-清除算法示意图
Q:这个算法存在的缺点 & 适合哪一代
- 效率不高(回收的多)
- 产生碎片
适合老年代, 标记的少
2.2.2 标记-复制算法
图2-4 标记复制算法示意图
Q:这个算法解决的问题 & 存在的缺点 & 适合哪一代
解决的问题:解决效率不高问题 & 解决内存碎片问题
缺点:浪费空间
适合年轻代
优化后的半区复制
怎么设计的,出现的问题是啥,怎么解决的
2.2.3 标记-整理算法
图2-5 标记整理算法示意图
Q:缺点:
效率低,stop the world 用于老年代
2.2.4 分代收集算法
应该是一个分代收集理论
在年轻代和老年代采用不同的算法
2.3垃圾收集器
堆
图 2-6 垃圾收集器的组合使用
(每个垃圾收集器采用的GC算法,以及优劣势)
2.3.1 Serial收集器
单线程,复制,在新生代
Stop the world
Serial收集器对于运行在客户端模式下的虚拟机来说是一个很好的选择。
2.3.2 ParNew收集器
Parrallel new
多线程并行,复制,在新生代,默认和CPU同等数量的线程,可配置
2.3.3 Parallel Scavenge收集器
多线程并行,复制,新生代,提高了效率
提供了两个参数
关注吞吐量(业务)/(业务+GC)
2.3.4 Serial Old收集器
单线程,标记-整理 老年代
2.3.5 Parallel Old收集器
多线程并行,标记整理,老年代
2.3.6 CMS(Concurrent Mark Sweep)收集器
并发,缩短停顿时间,标记-清除,老年代
默认启动的垃圾回收的线程数是(处理器核心数量+3)/4
关注停顿时间
它的缺点是啥
对CPU核数比较敏感
2.3.7 Garbage First收集器(G1)
G1可以针对不同区域进行回收,优先回收价值最大的Region,只要针对多CPU以及大量内存的服务器
G1收集器的原理简单描述?
它的缺点是啥?
维护一个表 region价值大
额外空间占用比较多 维护列表
在小内存的机器上表现不如CMS
2.4 总结
使用 java -XX:+PrintCommandLineFlags -version 可以查看jvm使用的垃圾收集器
JDK 8默认使用默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
JDK9默认使用G1
CMS已经不被推荐使用
垃圾收集器的选择影响系统停顿时间和吞吐能力的重要因素之一,一切设计都是为了提升程序的运行效率和吞吐量。
3.JMM
Java内存模型(Java Memory Model,JMM),JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的工作空间。如图3-1所示。
图 3-1 JMM示意图
这是java支持并发的基础。
并发解决的两个问题:线程之间如何通信,线程之间如何同步
3.1 JMM带来的问题
会出现可见性问题
出现了volatile
Synchronized加锁
Java有指令重排序,但是他定义了一些规则