1. 几个简称
JVM:Java Virtual Machine(Java虚拟机),是JRE的一部分
JRE:Java Runtime Environment
JDK:Java Development Kit, 是 Java 语言的软件开发工具包(SDK)
2. 一般使用Oracle Java虚拟机,就是原来的Sun,运行在Windows、Linux等平台。其他还有HP Java虚拟机,IBM Java虚拟机、开源Java虚拟机等。
3. JVM内存模型 (图片来源:https://blog.csdn.net/moneyshi/article/details/53033577)
总结:
堆,是JVM内存中最大的一块内存区域,该区域存放了对象实例及数组(但不是所有的对象实例都在堆中)
JVM内存=堆内存+非堆内存
堆内存=年轻代+年老代
年轻代=eden+s0+s1
非堆内存=方法区+栈
方法区=永久代(java7)
4.几个代的概念
年轻代:是所有新对象产生的地方。年轻代被分为3个部分——Enden区和两个Survivor区(From和to)当Eden区被对象填满时,就会执行Minor GC。并把所有存活下来的对象转移到其中一个survivor区(假设为from区)。Minor GC同样会检查存活下来的对象,并把它们转移到另一个survivor区(假设为to区)。这样在一段时间内,总会有一个空的survivor区。经过多次GC周期后,仍然存活下来的对象会被转移到年老代内存空间。通常这是在年轻代有资格提升到年老代前通过设定年龄阈值来完成的。需要注意,Survivor的两个区是对称的,没先后关系,from和to是相对的。
年老代:在年轻代中经历了N次回收后仍然没有被清除的对象,就会被放到年老代中,可以说他们都是久经沙场而不亡的一代,都是生命周期较长的对象。对于年老代和永久代,就不能再采用像年轻代中那样搬移腾挪的回收算法,因为那些对于这些回收战场上的老兵来说是小儿科。通常会在老年代内存被占满时将会触发Full GC,回收整个堆内存。
永久代(Perm Gen)/元空间(Metaspace):java7中的永久代,也叫方法区,存放类、方法、变量等。但是java8开始移除了永久代,以元空间代替,他们的作用类似。永久代被移除之后,这部分内存空间将全部移除,JVM的参数PermSize 和 MaxPermSize 会被忽略并给出警告(如果在启用时设置了这两个参数)。方法区移至元空间,字符串常量移至Java Heap。元空间和永久代最大的区别是,元空间并不在java虚拟机中,而是使用本地内存(最大可利用的本地内存空间,避免OOM等问题)。
5. GC(garbage collection)
GC是负责回收所有无任何引用对象的内存空间。 注意: 垃圾回收回收的是无任何引用的对象占据的内存空间,而不是对象本身。
GC机制是由JVM提供,用来清理需要清除的对象,回收堆内存
GC机制将Java程序员从内存管理中解放了出来,可以更关注于业务逻辑
在Java中,GC是由一个被称为垃圾回收器的守护线程执行的
作为一个Java开发者不能强制JVM执行GC
GC的触发由JVM依据堆内存的大小来决定
如果堆没有内存创建新的对象了,会抛出OutOfMemoryError
young gc,也叫minor gc,回收年轻代,频率快是正常的
major gc,回收年老代
full gc,回收整个堆 ---------有的也说full gc也叫major gc
cms gc,回收年老代
一般我们要注意:young gc发生的频率比较频繁是正常现象,但尽可能让他的单次执行时间少点,full gc发生的频率不应该太高。
以下内容摘自:https://blog.csdn.net/moneyshi/article/details/53033577
分代收集算法
这是当前商业虚拟机常用的垃圾收集算法。分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。
为什么要运用分代垃圾回收策略?在java程序运行的过程中,会产生大量的对象,因每个对象所能承担的职责不同所具有的功能不同所以也有着不一样的生命周期,有的对象生命周期较长,比如Http请求中的Session对象,线程,Socket连接等;有的对象生命周期较短,比如String对象,由于其不变类的特性,有的在使用一次后即可回收。试想,在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行回收,那么消耗的时间相对会很长,而且对于存活时间较长的对象进行的扫描工作等都是徒劳。因此就需要引入分治的思想,所谓分治的思想就是因地制宜,将对象进行代的划分,把不同生命周期的对象放在不同的代上使用不同的垃圾回收方式。
如何划分?将对象按其生命周期的不同划分成:年轻代(Young Generation)、年老代(Old Generation)、持久代(Permanent Generation)。其中持久代主要存放的是类信息,所以与java对象的回收关系不大,与回收息息相关的是年轻代和年老代。这里有个比喻很形象
“假设你是一个普通的 Java 对象,你出生在 Eden 区,在 Eden 区有许多和你差不多的小兄弟、×××妹,可以把 Eden 区当成幼儿园,在这个幼儿园里大家玩了很长时间。Eden 区不能无休止地放你们在里面,所以当年纪稍大,你就要被送到学校去上学,这里假设从小学到高中都称为 Survivor 区。开始的时候你在 Survivor 区里面划分出来的的“From”区,读到高年级了,就进了 Survivor 区的“To”区,中间由于学习成绩不稳定,还经常来回折腾。直到你 18 岁的时候,高中毕业了,该去社会上闯闯了。于是你就去了年老代,年老代里面人也很多。在年老代里,你生活了 20 年 (每次 GC 加一岁),最后寿终正寝,被 GC 回收。有一点没有提,你在年老代遇到了一个同学,他的名字叫爱德华 (慕光之城里的帅哥吸血鬼),他以及他的家族永远不会死,那么他们就生活在永生代。”
以下内容摘自:https://blog.csdn.net/suifeng3051/article/details/48292193
虚拟机中GC的过程
经过上面介绍,我们已经知道了JVM为何要分代回收,下面我们就详细看一下整个回收过程。
在初始阶段,新创建的对象被分配到Eden区,survivor的两块空间都为空。
当Eden区满了的时候,minor garbage 被触发
经过扫描与标记,存活的对象被复制到S0,不存活的对象被回收
在下一次的Minor GC中,Eden区的情况和上面一致,没有引用的对象被回收,存活的对象被复制到survivor区。然而在survivor区,S0的所有的数据都被复制到S1,需要注意的是,在上次minor GC过程中移动到S0中的两个对象在复制到S1后其年龄要加1。此时Eden区S0区被清空,所有存活的数据都复制到了S1区,并且S1区存在着年龄不一样的对象,过程如下图所示:
再下一次MinorGC则重复这个过程,这一次survivor的两个区对换,存活的对象被复制到S0,存活的对象年龄加1,Eden区和另一个survivor区被清空。
下面演示一下Promotion过程,再经过几次Minor GC之后,当存活对象的年龄达到一个阈值之后(可通过参数配置,默认是8),就会被从年轻代Promotion到老年代。
随着MinorGC一次又一次的进行,不断会有新的对象被promote到老年代。
上面基本上覆盖了整个年轻代所有的回收过程。最终,MajorGC将会在老年代发生,老年代的空间将会被清除和压缩。
从上面的过程可以看出,Eden区是连续的空间,且Survivor总有一个为空。经过一次GC和复制,一个Survivor中保存着当前还活着的对象,而Eden区和另一个Survivor区的内容都不再需要了,可以直接清空,到下一次GC时,两个Survivor的角色再互换。因此,这种方式分配内存和清理内存的效率都极高,这种垃圾回收的方式就是著名的“停止-复制(Stop-and-copy)”清理法(将Eden区和一个Survivor中仍然存活的对象拷贝到另一个Survivor中),这不代表着停止复制清理法很高效,其实,它也只在这种情况下(基于大部分对象存活周期很短的事实)高效,如果在老年代采用停止复制,则是非常不合适的。
老年代存储的对象比年轻代多得多,而且不乏大对象,对老年代进行内存清理时,如果使用停止-复制算法,则相当低效。一般,老年代用的算法是标记-压缩算法,即:标记出仍然存活的对象(存在引用的),将所有存活的对象向一端移动,以保证内存的连续(也就是整理内存碎片???)。在发生Minor GC时,虚拟机会检查之前每次晋升进入老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则直接触发一次Full GC,否则,就查看是否设置了-XX:+HandlePromotionFailure(允许担保失败),如果允许,则只会进行MinorGC,此时可以容忍内存分配失败;如果不允许,则仍然进行Full GC(这代表着如果设置-XX:+Handle PromotionFailure,则触发MinorGC就会同时触发Full GC,哪怕老年代还有很多内存,所以,最好不要这样做)。
关于方法区即永久代的回收,永久代的回收有两种:常量池中的常量,无用的类信息,常量的回收很简单,没有引用了就可以被回收。
永久代的回收并不是必须的,可以通过参数来设置是否对类进行回收。
6. GC的类型
串行收集器,Serial GC(-XX:+UseSerialGC):最简单的GC模式,适合单CPU的机器,性能最差,基本不用
并行收集器,使用多线程处理垃圾回收工作,速度快,效率高,理论上CPU数目越多,越能体现出并行收集器的优势,常见的并行收集器如下:
Parallel GC(-XX:+UseParallelGC):在年轻代用多线程、老年代用单线程
Parallel Old GC(-XX:+UseParallelOldGC):年轻代和老年代都用多线程
备注:如果系统注重吞吐量优先(可以理解为TPS),可以使用Parallel GC和Parallel Old GC组合,比如一些科学计算、后台处理的系统
并发(CMS)收集器:(-XX:+UseConcMarkSweepGC):串行收集和并行收集在进行垃圾回收工作时,需要暂停整个运行环境,因此,系统在垃圾回收时会有明显的暂停,并且暂停时间会因为堆越大而越长。而并发收集器可以保证大部分工作都并发执行,应用不暂停,这个垃圾收集器适用于尽可能减少应用的停顿时间,减少full gc发生的几率,适用于响应时间优先的系统(比较常用,适用于较大型的系统),但CMS是不会整理堆碎片的。CMS在并发模式工作的时候是只收集old gen的。但一旦并发模式失败(发生concurrent mode failure)就有选择性的会进行全堆收集,也就是退回到full GC。
7. 悲观策略
如果需要晋升的大小,已经大于年老代剩余空间的大小, 还不如直接触发FGC,否则,如果这些年轻代的对象全部晋升到年老代中,此时年老代空间也会不足,那么年老代也只能触发FGC,所以JVM认为在这种状态下,还不如直接执行FGC,这样可以省去后面的一些判断和处理的过程。这个就是JVM中的悲观策略
参考文章:https://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html
可以参考文章:
https://blog.csdn.net/u013309822/article/details/80346913
https://blog.csdn.net/qq_33296156/article/details/82587239
https://blog.csdn.net/jiafu1115/article/details/7024323
https://www.cnblogs.com/williamjie/p/9497906.html
https://blog.csdn.net/qq_38384440/article/details/81710887
https://blog.csdn.net/z69183787/article/details/51606410
https://blog.csdn.net/yinbucheng/article/details/72810166
https://blog.csdn.net/l_mloveforever/article/details/80546597
https://blog.csdn.net/javaMare/article/details/85271609
https://blog.csdn.net/qzqanzc/article/details/81008598
https://blog.csdn.net/u011972171/article/details/80398771