浅谈:JVM垃圾回收
一、四种类加载器(双亲委托/全盘委托机制)
1.启动类加载器: 加载 Java 核心类库,无法被 Java 程序直接引用。 2.扩展类加载器: 加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。 3.系统类加载器: 它根据 Java 应用的类路径(classpath)来加载 Java 类,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader() 来获取。 4.用户自定义类加载器: 通过继承java.lang.ClassLoader类的方式实现。
二、JVM内存模型
堆:
老年代(Old)占2/3,
年轻代(Young)占1/3,
年轻代包含Eden区和Survivor区,Survivor区包含From(S0区)区和To(S1区),默认Eden区、From区、To区的比例为8:1:1,
当Eden区内存不足时会触发Minor gc,没有被回收的对象进入到Survivor区,同时分代年龄+1,
再次触发Minor gc时,From区中的对象会移动到To区,Minor gc会回收Eden区和From区中的垃圾对象,对象的分代年龄会再次增加,
当分代年龄增加到15以后,对象会进入到老年代。当老年代内存不足时,会触发Full gc,
如果Full gc无法释放足够的空间,会触发OOM内存溢出,在进行Minor gc或Full gc时,会触发STW(Stop The World)即停止用户线程。
非堆(永久代/元空间区):
常量(final修饰)、静态变量(static修饰)、类信信息(版本、字段等)、运行时常量池,操作的是直接内存。
三、JVM运行时数据区
1.程序计数器
存放下一条指令所在单元的地址的地方。
2.线程栈(虚拟机栈)
JVM的每一个线程对应一个线程栈,一个线程的每个方法会分配一块栈帧内存空间。栈帧超过了栈的深度,会报StackOverflowError。
局部变量表: 存储基本数据类型(int、float、byte等),如果是引用数据类型,则存储的是其在堆中的内存地址,也就是指向对象的一个指针。
操作数栈: 主要用来存计算过程的中间结果,计算过程中变量临时的存储空间。CPU会从操作数栈中弹出所需的操作数,计算后压入到操作数栈顶。
i++ 和 ++i 的区别:
1、i++: 从局部变量表取出 i 并压入操作栈(load memory),对局部变量表中的 i 自增 1(add&store memory),将操作栈栈顶值取出使用,如此线程从操作栈读到的是自增之前的值。
2、++i: 先对局部变量表的 i 自增 1(load memory&add&store memory),然后取出并压入操作栈(load memory),再将操作栈栈顶值取出使用,线程从操作栈读到的是自增之后的值。
i++ 不是原子操作, 1、i 从局部变量表(内存)取出,2、压入操作栈(寄存器),操作栈中自增,3、使用栈顶值更新局部变量表(寄存器更新写入内存),这3步可能被另一个线程打断,产生数据互相覆盖问题。
动态链接: 在Java源文件被编译成字节码文件时,所有的变量和方法引用都作为符号引用存到class文件的常量池中。通过常量池中指向方法的符号引用来表示一个方法调用了其他方法时。动态链接是将符号引用转换为直接引用。
方法出口:
方法执行时有两种退出情况:
1.正常退出,即正常执行到任何方法的返回字节码指令,如 return、ireturn、areturn 等;
2.异常退出。
方法退出就返回至方法当前被调用的位置。
1.返回值压入上层调用栈帧。
2.异常信息抛给能够处理的栈帧。
3.PC计数器指向方法调用后的下一条指令。
3.本地方法栈
与虚拟机栈结构一致,本地方法栈执行的是Java底层由C++编写的native方法。
4.元空间
JDK1.8用元空间代替永久代,元空间是方法区的实现,用来存放常量(final修饰)、静态变量(static修饰)以及类的类的版本、字段、方法、接口等
该部分的内存默认情况下是off heap的(堆的一个逻辑分区)内存,大小不受jvm大小的限制,属于操作系统内存。
5.堆
堆是JVM所管理内存的最大一部分,在虚拟机启动时启动。
对象实例和数组都在堆上分配。堆内存不足,也会报OutOfMemoryError。
三、JVM垃圾回收(GC)
1. 判定堆中对象是否为垃圾
引用计数法: 在对象中添加一个引用计数器,当有地方引用这个对象时,引用计数器值+1,当引用失效时,计数器值-1。两个对象循环引用的时,各自的计数器始终不会变成0,导致无法回收,引起内存泄露。(一般不采用)。
可达性分析法: 从GCRoots的对象(虚拟机栈、方法区的类属性引用的对象、方法区中常量引用的对象、本地方法栈中引用的对象)的引用连来判断是否为垃圾。
2.回收算法
标记–清除: 首先通过根节点,标记所有从根节点开始的可达对象。清除阶段,清除所有未被标记的对象。 标记–整理(压缩): 标记-压缩算法从根节点开始,对所有可达对象做一次标记。将所有的存活对象压缩到内存的一端,清理边界外所有的空间。 标记-复制: 将内存区域均分为了两块(记为S0和S1),创建对象的时只用其中的一块(如S0),当S0使用完之后,将S0上面存活的对象全复制到S1上去,然后将S0全部清理掉。 新生代中: 每次收集都会有大量对象死去,只要复制少量对象以及更改引用,所以可以选择复制算法。 老年代中: 没有额外的空间对它进行分配担保,因此选择标记清除或标记整理算法进行垃圾收集。
垃圾收集器
垃圾收集器 | 工作方式 | 作用空间 | 算法 | 特点 | 适用场景 |
---|---|---|---|---|---|
Serial | 串行 | 新生代 | 复制算法 | 响应速度优先 | 适用于单CPU环境下的client模式 |
ParNew | 并行 | 新生代 | 复制算法 | 响应速度优先 | 多CPU环境Server模式下与CMS配合使用 |
Parallel | 并行 | 新生代 | 复制算法 | 吞吐量优先 | 适用于后台运算而不需要太多交互的场景 |
Serial Old | 串行 | 老年代 | 标记-整理(压缩)算法 | 响应速度优先 | 适用于单CPU环境下的Client模式 |
Paraller Old | 并行 | 老年代 | 标记-整理(压缩)算法 | 吞吐量优先 | 适用于后台运算而不需要太多交互的场景 |
CMS | 并发 | 老年代 | 标记-清除算法 | 响应速度优先 | 适用于互联网或B/S业务 |
G1 | 并发、并行 | 新生代、老年代 | 标记-整理(压缩)算法 | 响应速度优先 | 响应速度优先 |
3.JVM调优
JVM 提供的常用工具 jconsole(bin目录下):用于对 JVM 中的内存、线程和类等进行监控的视图监控工具; jvisualvm(bin目录下):JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等的视图监控工具。 jps:用来显示本地的 Java 进程,可以查看本地运行着几个 Java 程序,并显示他们的进程号。命令格式:jps. jinfo:运行环境参数:Java System 属性和 JVM 命令行参数,Java class path 等信息。命令格式:jinfo 进程 pid. jstat:监视虚拟机各种运行状态信息的命令行工具。命令格式:jstat -gc 123 250 20. jstack:可以观察到 JVM 中当前所有线程的运行情况和线程当前状态。命令格式:jstack 进程 pid. jmap:观察运行中的 JVM 物理内存的占用情况(如:产生哪些对象,及其数量)。命令格式:jmap [option] pid.
4.JVM 参数
参数 说明 实例 -Xms 初始堆大小,默认物理内存的1/64 -Xms512M -Xmx 最大堆大小,默认物理内存的1/4 -Xms2G -Xmn 新生代内存大小,官方推荐为整个堆的3/8 -Xmn512M -Xss 线程堆栈大小,jdk1.5及之后默认1M,之前默认256k -Xss512k -XX:NewRatio=n 设置新生代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4 -XX:NewRatio=3 -XX:SurvivorRatio=n 年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:8,表示Eden:Survivor=8:1:1,一个Survivor区占整个年轻代的1/8 -XX:SurvivorRatio=8 -XX:PermSize=n 永久代初始值,默认为物理内存的1/64 -XX:PermSize=128M -XX:MaxPermSize=n 永久代最大值,默认为物理内存的1/4 -XX:MaxPermSize=256M -verbose:class 在控制台打印类加载信息 -verbose:gc 在控制台打印垃圾回收日志 -XX:+PrintGC 打印GC日志,内容简单 -XX:+PrintGCDetails 打印GC日志,内容详细 -XX:+PrintGCDateStamps 在GC日志中添加时间戳 -Xloggc:filename 指定gc日志路径 -Xloggc:/data/jvm/gc.log -XX:+UseSerialGC 年轻代设置串行收集器Serial -XX:+UseParallelGC 年轻代设置并行收集器Parallel Scavenge -XX:ParallelGCThreads=n 设置Parallel Scavenge收集时使用的CPU数。并行收集线程数。 -XX:ParallelGCThreads=4 -XX:MaxGCPauseMillis=n 设置Parallel Scavenge回收的最大时间(毫秒) -XX:MaxGCPauseMillis=100 -XX:GCTimeRatio=n 设置Parallel Scavenge垃圾回收时间占程序运行时间的百分比。公式为1/(1+n) -XX:GCTimeRatio=19 -XX:+UseParallelOldGC 设置老年代为并行收集器ParallelOld收集器 -XX:+UseConcMarkSweepGC 设置老年代并发收集器CMS -XX:+CMSIncrementalMode 设置CMS收集器为增量模式,适用于单CPU情况。