最近关于JVM和垃圾回收,阿里云上进行了具体的学习巩固,以下是学习总结。
内容介绍:
一、JVM
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。 JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。
Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。
二、JVM 组成
- Class Files: .java文件,通过 java 编译成 .class文件。
- Class Loader Subsystem:将所需的类加载到内存,必要时将类实例化成实例。
- Runtime Data Areas:JVM 运行时区域,图中中间部分是进程的内存逻辑结构,由下面几部分构成:
- Heap:堆,所有线程共享的内存空间,存放创建的所有对象。堆是靠 GC 垃圾回收器管理的。
- Java Threads:Java 栈,每个线程会分配一个栈,存放线程用的本地变量、方法参数和返回值等。
- Program Counter Registers:PC 寄存器,每一个线程用于记录当前线程正在执行的字节码指令地址。因为线程需要切换,当一个线程被切换回来需要执行的时候,知道执行到哪里了。
- Method Area:方法区,所有线程共享的内存空间,存放已加载的类信息、常量和静态变量。
- Native Internal Threads:本地方法栈,为本地方法执行构建的内存空间,存放本地方法执行时的局部变量、操作数等。所谓本地方法,简单的说是非 java 实现的方法,例如操作系统的 C 编写的库提供的本地方法,Java 调用这些本地方法接口执行。本地方法应该避免直接编程使用,因为 Java 可能跨平台使用,如果用了 Windows API,换到了 Linux 平台部署就有了问题。
三、虚拟机
目前 Oracle 官方使用的是 HotSpot,它最早由一家名为 “Longview Technologies” 公司设计,使用了很多优秀的设计理念和出色的性能,1997年该公司被 SUN 公司收购。后来随着JDK-起发布了 HotSpotVM。目前 HotSpot 是最主要的 VM。
四、GC 垃圾收集器
堆内存里面经常创建、销毁对象,内存也是被使用、被释放。如果不妥善处理,一个使用频繁的进程,将可能有内存容量,但是无法分配出可用内存空间,因为没有连续成片的内存了,内存全是碎片化的数据。
- 回收基本算法(java不使用):
每一个堆内对象上都与一个私有引用计数器,记录着被引用的次数,引用计数清零,该对象所占用堆内存就可以被回收。循环引用的对象都无法引用计数归零,就无法清除。 - 标记清除 Mar-Sweep :
分垃圾标记阶段和内存释放阶段。标记阶段,找到所有可访问对象打个标记。清理阶段,遍历整个堆,对未标记对象清理。
每一块表示使用的内存,需要判断是否使用,进行标记,清理未标记的内存,形成碎片化内存。其缺点就是会造成大量的内存碎片。 - 复制 Copying:
先将可用内存分为大小相同两块区域A和B,每次只用其中一块,比如A。当A用完后,则将A中存活的对象复制到B。复制到B的时候连续的使用内存,最后将A一次性清除干净。
缺点是则损一半内存使用量,因为内存对半划分了,复制过程CPU也是有代价的。好处是没有碎片,复制过程中保证对象使用连续空间。 - 标记-压缩 Mark-Compact:
分垃圾标记阶段和内存整理阶段。标记阶段,找到所有可访问对象打个标记。
内存清理阶段时,整理时将对象向内存一端移动,整理后存活对象连续的集中在内存一端。
- 分代收集算法:
既然上述垃圾回收算法都有优缺点,能不能对不同数据进行区分管理,不同分区对数据实施不同回收策略,分而治之。
1.7及以前,堆内存分为新生代、老年代、持久代。
1.8开始,持久代没有了,取而代之 MetaSpace。
五、STW
对于大多数垃圾回收算法而言,GC 线程工作时,需要停止所有工作的线程,称为 Stop The World。GC 完成时,恢复其他工作线程运行。这也是 VM 运行中最头疼的问题。
- 分代堆内存:
六、新生代回收
起始时,所有新建对象都出生在 eden,当 eden 满了,启动 GC。这个称为 Young GC,Minor GC。
先标记 eden 存活对象,然后将存活对象复制到from (假设本次是from,也可以是to,它们可以调换),eden 剩余所有空间都"清空"。GC 完成。
继续在eden创建新对象,当eden满了。启动GC,注意,此时会标记eden+from区域的存活对象,然后将存活对象复制到to。将 eden 和from"清空"。根据上面图例,假设 c 在from存活区已经为垃圾。视图如下:
继续新建对象,当 eden 满了,启动 GC。
先标记 eden 和 to 中存活对象,然后将存活对象复制到from。将 eden 和from"清空"。
以后就重复上面的步骤。
大多数对象都不会存活很久,而且创建活动非常多,新生代就需要频繁垃圾回收。
但是,如果一个对象一直存活,它最后就在 from、to 来回复制,如果 from 区中对象复制次数达到阈值,就直接复制到老年代。
七、老年代回收
进入老年代的数据较少,所以老年代区被占满的速度较慢,所以垃圾回收也不频繁。老年代GC称为 Old GC,Major GC。
由于老年代对象一般来说存活次数较长,所有较常采用标记-压缩算法。
此标记-压缩算法是要到一定程度才会执行。
Full GC:对所有“代”的内存进行垃圾回收
Minor GC 比较频繁,Major GC 较少。但一般 Major GC 时,由于老年代对象也可以引用新生代对象,所以先进行一次 Minor GC。然后在 Major GC 会提高效率。可以认为回收老年代的时候完成了一次 Full GC。
八、GC 触发条件
Minor GC 触发条件:当 eden 区满了触发
Full GC 触发条件:
·老年代满了(达到一定阈值就应该做压缩,不要等满了,那就太晚了!!)
·新生代搬向老年代,老年代空间不够
·持久代满了
·System.gc()手动调用。不推荐
九、调整策略
-
源头上减少垃圾的生成
-
减少 STW 时长,串行变并行
-
减少 GC 次数,要分配合适的内存大小
-
对 JVM 调整策略应用极广
-
在 WEB 领域中 Tomcat 等
-
在大数据领域 Hadoop 生态各组件
-
在消息中间件领域的 Kafka 等
-
在搜索引擎领域的 ElasticSearch.Solr 等
-
在不同领域对 JVM 需要不同的调整策略
-
十、垃圾收集器类型
按回收线程数:
-
串行垃圾回收器:一个 GC 线程完成回收工作
-
并行垃圾回收器:多个 GC 线程同时一起完成回收工作,充分利用CPU 资源
指的是GC线程是否串并行
按工作模式不同:
- 并发垃圾回收器:让 GC 线程垃圾回收某些阶段可以和工作线程一起进行。
- 独占垃圾回收器:只有 GC 在工作,STW 一直进行到回收完毕,工作线程才能继续执行。
指的是 GC 线程和工作线程是否一起运行。
一般情况下,我们大概可以使用以下原则:
-
客户端或较小程序,内存使用量不大,可以使用串行回收;
-
对于服务端大型计算,可以使用并行回收;
大型 WEB 应用,用户端不愿意等,尽量少的 STW,可以使用并发回收;
参数 | 说明 | ju |
---|---|---|
-Xms | 设置应用程序初始使用的堆内存大小(新生代+老年代) | -Xms2g |
-Xmx | 设置应用程序能获得的最大堆,内存早期VM不建议超过32G,内存管理效率下降 | -Xms4g |
-X:NewSize | 设置初始新生代大小 | |
-XX:MaxNewSize | 设置最大新生代内存空间 | |
-X:NewRatio | 以比例方式设置新生代和老年代 | -X:NewRatio=2 new/old=1/2 |
-XX:SurvivorRatio | 以比例方式设置eden和survivor | -XX:SurvivorRatio=6 eden/survivor-6/1 survivor/new=1/8 |
-Xss | 设置线程的栈大小 |
接下来就可以进行测试调整
public class HelloWorld extends Thread{
public static void main(String[] args) {
try {
while (true){
Thread.sleep(2000);
System.out.println("hello smallDrama");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
启动程序,去jdk\bin\jvisualvm.exe 双击启动如下:
发现最大空间给了4G,最小是分配了200多MB,实际可以看出不过10到20MB,我们可以调优分配 最小内存:128MB,最大内存:512MB; 输入命令 :java -Xms128m -Xmx512m HelloWorld 如下:
查看程序内存分配:
可以看到最大最小为我们最新分配的内存空间大小。
Tomcat调优:
默认是不需要置顶内存的,-Xmx大约使用了1/4的内存。
在 tomcat 的bin/catalina.sh中增加
JAVA_OPTS="-server Xmx512m -Xms128m -XX:NewSize=48m -XX:MaxNewSize=200m"
-server:VM运行在server模式,为在服务器端最大化程序运行速度而优化。
-client: VM运行在Client模式,为客户端环境减少启动时间而优化。
启动观察:
十一、垃圾回收器
新生代
-
新生代串行收集器:单线程、独占式串行,回收算法标记-复制。
-
新生代并行收集器:将单线程的串行收集器变成了多线程并行、独占式。
-
新生代并行回收收集器:多线程并行、独占式,使用复制算法,关注调整吞吐量。
老年代
-
老年代串行收集器;单线程、独占式串行,回收算法使用标记-压缩。
-
老年代并行回收收集器;多线程、独占式并行,回收算法使用标记-压缩,关注调整吞吐量。
-
CMS 收集器
- Concurrent Mark Sweep 并发标记清除算法
- 在某些阶段尽量使用和工作线程一起运行,减少停顿时长。是互联网站点服务端 BS 系统上较佳的回收算法。
- 分为4个阶:初始标记、并发标记、重新标记、并发清除,在初始标记、重新标记时需要 STW。
-
G1 收集器
- Garbage First 是最新垃圾回收器,从 DK1.6 实验性提供,JDK1.7发布,其设计目标是在多处理器、大内存服务器端提供优于 CMS 收集器的吞吐量和停顿控制的回收器。建议 JDK8 再考虑它。
- 基于标记压缩算法。+UseG1GC。
- 分为4个阶段:初始标记、并发标记、最终标记、筛选回收。并发标记并发执行,其它阶段 STW 只有 GC 线程并行执行。
十二、垃圾收集器设置