小米的企业网站建设思路,c2c的网站,怎么制作网页教程,门户网站创新的方式有前言
JVM作为Java进阶的知识#xff0c;是需要Java程序员不断深度和理解的。
本篇博客介绍JVM的内存模型#xff0c;对比了1.7和1.8的内存模型的变化#xff1b;介绍了垃圾回收的语言发展#xff1b;阐述了定位垃圾的方法#xff0c;引用计数法和可达性分析发以及垃圾清…
前言
JVM作为Java进阶的知识是需要Java程序员不断深度和理解的。
本篇博客介绍JVM的内存模型对比了1.7和1.8的内存模型的变化介绍了垃圾回收的语言发展阐述了定位垃圾的方法引用计数法和可达性分析发以及垃圾清除算法然后介绍了Java中的垃圾回收器由串行、到并行再到并发最后到G1的演变最后给出了垃圾回收器的对比和使用指引。
其他相关的JVM博客文章如下
Java进阶1——JVM的内存分配 反射Class类的类对象 创建对象的几种方式 类加载何时进入内存JVM 注解 反射注解的案例Java进阶4——结合类加载JVM的过程理解创建对象的几种方式new反射Class克隆clone拷贝序列化反序列化
系列文章合集如下 【合集】Java进阶——Java深入学习的笔记汇总 再论面向对象、数据结构和算法、JVM底层、多线程、类加载 … 目录 前言引出JVM内存模型JDK1.7JDK1.8 垃圾回收基础栈空间和堆空间垃圾回收的语言发展 如何定位垃圾引用计数法可达性分析方法 垃圾清除算法标记-清除标记-复制标记-压缩 垃圾回收器总览串行Serial 和 Serial-Old并行Parallel (jdk1.8默认)并发ParNewCMS三色标记法 Garbage FirstGC回收器在jdk1.8用G1G1垃圾回收过程G1的四大特点G1的适用场景 垃圾回收器的使用G1调优快速使用8种GC回收器的比较怎么选择GC回收器 总结 引出 1.JVM的内存模型对比了1.7和1.8的内存模型的变化 2.垃圾回收的语言发展定位垃圾的方法引用计数法和可达性分析发以及垃圾清除算法 3.Java中的垃圾回收器由串行、到并行再到并发最后到G1的演变 4.垃圾回收器的对比和使用指引。
JVM内存模型
JDK1.7 Young区年轻代区Young区被划分为三部分Eden区和两个大小严格相同的Survivor区其中Survivor区间中某一时刻只有其中一个是被使用的另外一个留做垃圾收集时复制对象用在Eden区间变满的时候 GC就会将存活的对象移到空闲的Survivor区间中根据JVM的策略在经过几次垃圾收集后任然存活于Survivor的对象将被移动到Tenured区间
Tenured 年老区Tenured区主要保存生命周期长的对象一般是一些老的对象当一些对象在Young复制转移一定的次数以后对象就会被转移到Tenured区一般如果系统中用了application级别的缓存缓存中的对象往往会被转移到这一区间。
Perm 永久区Perm代主要保存class,method,filed对象这部份的空间一般不会溢出除非一次性加载了很多的类不过在涉及到热部署的应用服务器的时候有时候会遇到java.lang.OutOfMemoryError : PermGen space 的错误造成这个错误的很大原因就有可能是每次都重新部署但是重新部署后类的class没有被卸载掉这样就造成了大量的class对象保存在了perm中这种情况下一般重新启动应用服务器可以解决问题。
Virtual区最大内存和初始内存的差值就是Virtual区。
JDK1.8
jdk1.8的内存模型是由2部分组成年轻代 年老代。 年轻代Eden 2*Survivor 年老代OldGen 在jdk1.8中变化最大的Perm区用Metaspace元数据空间进行了替换。
需要特别说明的是Metaspace所占用的内存空间不是在虚拟机内部而是在本地内存空间中这也是与1.7的永久代最大的区别所在 1.7到1.8的变化 垃圾回收基础
栈空间和堆空间
栈空间方法结束自动回收
堆空间需要手动管理回收 程序中最难调试的BUG野指针空指针并发问题 所以Java为什么不让程序员来手动删除对象明白不上述的情况可能会出现在比如C语言C 的语言场景中 垃圾回收的语言发展
在C/C语言中没有自动垃圾回收机制是通过new关键字申请内存资源通过delete关键字释放内 存资源。如果程序员在某些位置没有写delete进行释放那么申请的对象将一直占用内存资源最终 可能会导致内存溢出。
C / C 语言
手动管理忘记释放释放多次开发效率低
Java Python Go语言
自动管理引入GC Garbage Collector大大减低程序员门槛 应用程序只管分配垃圾回收器负责回收 如何定位垃圾
垃圾没有引用指向的对象就是垃圾对象
如何找到垃圾算法引用计数法可达性分析方法
引用计数法
假设有一个对象A任何一个对象对A的引用那么对象A的引用计数器1当引用失败时对象A的引用计数器就-1如果对象A的计数器的值为0就说明对象A没有引用了可以被回收。 Python使用的较为多但是这种算法缺陷在于无法解决循环引用的问题 public class A {public static void main(String[] args) {TestA a new TestA();TestB b new TestB();a.b b;b.a a;a null;b null;}}class TestA {public TestB b;
}
class TestB {public TestA a;
}优缺点
优点
实时性较高无需等到内存不够的时候才开始回收运行时根据对象的计数器是否为0就可以直接回收。 在垃圾回收过程中应用程序无需挂起。如果申请内存时内存不足则立刻报out of memery 错误。局部更新对象的计数器时只是影响到该对象不会扫描全部对象。
缺点
每次对象被引用时都需要去更新计数器有一点时间开销。浪费CPU资源即使内存够用仍然在运行时进行计数器的统计。无法解决循环引用问题。最大的缺点
可达性分析方法
Java早期使用但后期更喜欢采用可达性分析方法 那什么是根呢JVM线程栈本地方法栈运行时常量池方法区的静态引用类对象
main方法启动的哪些内容就是根
垃圾清除算法
标记-清除
标记-复制
标记-压缩
标记-清除
标记-清除算法 但是这种算法导致内存碎片化过于严重 优缺点
优点
可以看到标记清除算法解决了引用计数算法中的循环引用的问题没有从root节点引用的对象都会被回收。
缺点
效率较低标记和清除两个动作都需要遍历所有的对象并且在GC时需要停止应用程序对于交互性要求比较高的应用而言这个体验是非常差的。通过标记清除算法清理出来的内存碎片化较为严重因为被回收的对象可能存在于内存的各个角落所以清理出来的内存是不连贯的。
标记-复制 标记-复制算法将内存一分为二一份专门用于复制 但是这种算法浪费内存 标记-压缩
标记-压缩算法边清理边压缩 但是这种算法效率太低 三种算法都有问题JVM采用三种的综合运用产生了各种各样的GC垃圾回收器
垃圾回收器
总览
随着内存大小不断变化演变出越来越多的垃圾回收器 分代模型新生代1、老年代2
新生代eden, 幸存区1幸存区2 比例8:1 在新生代至少就可以做到90%的对象都会被回收掉
Minor-GC触发的时机Eden满
Full-GC触发的时机Old满
串行Serial 和 Serial-Old Serial垃圾回收器内存在几M - 几十M的场景下
Serial GC是一种单线程的垃圾回收器它在一个单独的线程上运行不会使用多个线程来进行垃圾回收在回收的过程中会停止所有业务线程stop-the-world简称stw用于Minor-GC算法标记-复制算法 Serial-Old垃圾回收器
Serial-Old GC也是一种单线程的垃圾回收器它在一个单独的线程上运行不会使用多个线程来进行垃圾回收在回收的过程中会停止所有业务线程stop-the-world简称stw用于Full-GC算法标记-清除算法、标记-压缩算法
并行Parallel (jdk1.8默认)
并行垃圾收集器在串行垃圾收集器的基础之上做了改进将单线程改为了多线程进行垃圾回收这样可以缩短垃圾回收的时间。这里是指并行能力较强的机器当然了并行垃圾收集器在收集的过程中也会暂停应用程序这个和串行垃圾回收器是一样的只是并行执行速度更快些暂停的时间更短一些。 Parallel垃圾回收器内存在几十M - 几G的场景下 所以区别在于Serial是单线程的而Parallel是多线程并行的房子大了一个人忙不过来
JDK1.8 默认采用的就是Parallel 的组合 pspo
java -XX:PrintCommandLineFlags -version #在cmd中查看当前使用的GC回收器JDK11使用G1 并发ParNewCMS
当内存继续增大到 几十个G呢多线程有时也不能解决根本问题了是否是多线程越多越好呢
Concurrent GC垃圾回收器内存在几十G以上的场景下 Serial Parallel 在GC时都会打断业务线程让业务线程停止STW 并发标记清除GC意思是 GC回收线程 业务线程 可以并行处理业务线程在产生垃圾GC回收线程尽可能的让“业务线程”不需要STW的情况下就可以及时回收业务线程所产生的垃圾 尽可能少的STW可以并行回收这便是Concurrent GC垃圾回收器最厉害的地方 上述的图中也只有 初始标记 重新标记 阶段是需要STW的 CMS全称 Concurrent Mark Sweep是一款并行的、使用标记-清除算法的老年代垃圾回收器
可以搭配Serial / ParNew 这些新生代的GC回收器使用 初始标记只标记Root上的根根直接引用的对象所以尽管采用STW它的时间也不会太长 重新标记毕竟误标的对象不会太多所以STW的时间也不会太长 三色标记算法发生在初始标记并发标记重新标记阶段主要由3种颜色组成黑灰白
黑对象被标记且它的所有子对象也被标记完
灰对象被标记但是它的子对象还没有被标记或标记完
白对象没有被标记到标记阶段结束后会被当做垃圾回收掉
三色标记法
三色标记法的标记过程可以分为三个阶段初始标记(Initial Marking)、并发标记(Concurrent Marking)和重新标记(Remark), 初始标记遍历所有的根对象将根对象和直接引用的对象标记为灰色。在这个阶段中垃圾回收器只会扫描被直接或者间接引用的对象而不会扫描整个堆因此初始标记阶段的时间比校短。(Stop The World 并发标记在这个过程中垃圾回收器会从灰色对像开始遍历整个对象图将被引用的对像标记为灰色并将己经遍历过的对象标记为黑色。并发标记过程中应用程序线程可能会修改对家图因此垃圾回收器需要便用写屏障(Write Barrier)技术来保证并发标记的正确性不需要STW) 重新标记重新标记的主要作用足标记在并发标记阶段中被修改的对象以及未被遍历到的对象。这个过程中垃圾回收器会从灰色对象重新开始遍历对象图将被引用的对家标记为灰色并将已经遍历过的对象标记为黑色。(Stop The World)
在重新标记阶段结束之后垃圾回收器会执行清除操作将未被标记为可达对象的对象进行回收从而释放内存空间这个过程中垃圾回收器会将所有未被标记的对象标记为白色White
三色标记算法的BUG可能会产生多标漏标的问题漏标是最严重的问题当然CMS G1他们分别采用的解决方案也不一样 多标产生浮动垃圾浮动垃圾情况不严重下次GC线程直接回收就好 漏标 CMS垃圾回收算法在JDK1.8之后就被干掉了
CMS在增量更新阶段会根据可达性分析算法重头到尾扫描重新标记一次堆对象所以STW时间将会非常的长
Garbage FirstGC回收器 G1回收器是当下的最为主流的垃圾回收器 Garbage First 垃圾优先 G1垃圾回收器抛弃了传统从物理上进行的分代算法而采用分区region 逻辑分代的算法
G1(Garbage First)垃圾收集器是当今垃圾回收技术最前沿的成果之一。早在DK7就已加入JVM的收集器大家庭中成为HotSpot重点发展的垃圾回收技术。同优秀的CMS垃圾回收器一样G1也是关注最小时延的垃圾回收器也同样适合大尺寸堆内存的垃圾收集官方也推荐使用G1来代替选择CMS。G1最大的特点是引入分区的思路弱化了分代的概念合理利用垃圾收集各个周期的资源解决了其他收集器甚至CMS的众多缺陷。 G1将Java堆划分为多个大小相等的独立区域RegionJVM最多可以有2048个Region。 一般Region大小等于堆大小除以2048比如堆大小为4096M则Region大小为2M当然也可以 用参数-XX:G1HeapRegionSize手动指定Region大小但是推荐默认的计算方式。 G1保留了年轻代和老年代的概念但不再是物理隔阂了它们都是可以不连续Region的集合
默认年轻代对堆内存的占比是5%如果堆大小为4096M那么年轻代占据200MB左右的内存 对应大概是100个Region可以通过“-XX:G1NewSizePercent”设置新生代初始占比在系统运行中JVM会不停的给年轻代增加更多的Region但是最多新生代的占比不会超过60%可以 通过“-XX:G1MaxNewSizePercent”调整。年轻代中的Eden和Survivor对应的region也跟之前 一样默认8:1:1假设年轻代现在有1000个regioneden区对应800个s0对应100个s1对应100个。
一个Region可能之前是年轻代如果Region进行了垃圾回收之后可能又会变成老年代也就是说Region的区域功能可能会动态变化。
G1垃圾收集器对于对象什么时候会转移到老年代跟之前讲过的原则一样唯一不同的是对大对象的处理G1有专门分配大对象的Region叫Humongous区而不是让大对象直接进入老年代的 Region中。在G1中大对象的判定规则就是一个大对象超过了一个Region大小的50%比如按照上面算的每个Region是2M只要一个大对象超过了1M就会被放入Humongous中而且 一个大对象如果太大可 能会横跨多个Region来存放。
Humongous区专门存放短期巨型对象不用直接进老年代可以节约老年代的空间避免因为老年代空间不够的GC开销。 Full GC的时候除了收集年轻代和老年代之外也会将Humongous区一并回收。 G1回收器同样采用三色标记算法来完成 对象的标记但是对于漏标的问题它采用的解决方案是原始快照
原始快照GC线程在退出运行时将所有灰色节点下的所有的引用拍摄快照到Rset中GC线程恢复后直接使用快照数据
“所有的灰色对家自己引用扫描完成之前册除了对白色对象的明引用”这个条件如果被破坏了那么就不会出现漏标的问题。所以
如果灰色对象在扫描完成前到除了对白色对象的引用那么我们就在灰色对象取消引用之前先将灰色对象引用的白色对象记录下来在后续「重新标记] 阶段再以这些白色对象为根对它的引用进行扫描从而避免了漏标的问题。通过这种方式原本漏标的对象就会被重新扫描变成灰色从而变为存活状态。
但是这种方式可能会把本来真的要取消弱引用的对象给错的活了从而产生浮垃级但是就像前面说的多标的问题是可以忽略的.
在jdk1.8用G1
ZGCOracle官方, ShenandoahRedhat红帽 这2种是下一代的垃圾回收器分页垃圾回收器Epsilon 是空的回收器主要用于开发JVM GC回收器的程序员Debug使用
在JDK1.7版本正式启用移除了Experimental的标识是JDK9以后的默认垃圾回收器取代了CMs回收器以及ParallelParallel0ld组合。被Oracle官方称为全功能的垃圾收集器”。与此同时CMS已经在JDK9中被标记为废弃(deprecated)。G1在dk8中还不是默认的垃圾回收器需要使用-XX:UseG1GC来启用。
如果在jdk1.8用G1则
java -jar -XX:UseG1GC 某一个项目.jarG1垃圾回收过程
G1垃圾回收3种模式YoungGC、 Mixed GC和Full GC
G1GC的垃圾回收过程主要包括如下三个环节
年轻代GC(Young GC)老年代并发标记过程(Concurrent Marking)混合回收(Mixed GC)Full GC如果需要单线程、独占式、高强度的Full GC还是继续存在的。它针对GC的评估失败提供了一种失败保护机制即强力回收。) 顺时针young gc一young gcconcurrent mark一Mixed GC)顺序进行垃圾回收。
1.应用程序分配内存当年轻代的Ed区用尽时开始年轻代回收过程G1的年轻代收集阶段是一个并行的多个回收线程独占式(STW收集器。在年轻代回收期G1GC暂停所有应用程序线程启动多线程执行年轻代回收。然后从年轻代区间移动存活对象到Survivor区间或者老年区间也有可能是两个区间都会涉及。
2.当堆内存使用达到一定值默认45%时开始老年代并发标记过程
3.标记完成马.上开始混合回收过程。对于一个混合回收期G1GC从老年区间移动存活对象到空闲区间这些空闲区间也就成为了老年代的一部分。和年轻代不同老年代的G1回收器和其他GC不同G1的老年代回收器不需要整个老年代被回收一次只需要扫描/回收一小部分老年代的Region就可以了。同时这个老年代Region是和年轻代一起被回收的.
举个例子一个web服务器Java进程最大堆内存为4G,每分钟响应1500个请求每45秒钟会新分配大约2G的内存。G1会每45秒钟进行一次年轻代回收每31个小时整个堆的使用率会达到45%会开始老年代并发标记过程标记完成后开始四到五次的混合回收。
G1的四大特点 并行与并发 ➢并行性 G1在回收期间可以有多个Gc线程同时工作有效利用多核计算能力。此时用户线程STW➢并发性 G1拥有与应用程序交替执行的能力部分工作可以和应用程序同时执行因此一般来说不会在整个回收阶段发生完全阻塞应用程序的情况 分代收集 ➢从分代上看G1依然属于分代型垃圾回收器它会区分年轻代和老年代年轻代依然有Eden区和Survivor区。但从堆的结构上看它不要求整个Eden区、年轻代或者老年代都是连续的也不再坚持固定大小和固定数量。➢将堆空间分为若干个区域Region 这些区域中包含了逻辑上的年轻代和老年代。➢和之前的各类回收器不同它同时兼顾年轻代和老年代。对比其他回收器或者工作在年轻代或者工作在老年代 空间整合 ➢CMS“标记-清除”算法、内存碎片、若干次Gc后进行一次碎片整理➢G1将内存划分为一个个的region。 内存的回收是以region作为基本单位的。Region之间是复制算法。但整体上实际可看作是标记一压缩Mark一Compact算法两种算法都可以避免内存碎片。这种特性有利于程序长时间运行分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。尤其是当Java堆非常大的时候G1的优势更加明显 可预测的停顿时间模型 ➢由于分区的原因G1可以只选取部分区域进行内存回收这样缩小了回收的范围因此对于全局停顿情况的发生也能得到较好的控制。➢G1跟踪各个Region里面的垃圾堆积的价值大小回收所获得的空间大小以及回收所需时间的经验值在后台维护一个优先列表每次根据允许的收集时间优先回收价值最大的Region。保证了G1 收集器在有限的时间内可以获取尽可能高的收集效率。➢相比于CMSGCG1未必能做到CMS在最好情况下的延时停顿但是最差情况也要好很多。
G1的适用场景
1面向服务端应用针对具有大内存、多处理器的机器。在普通大小的堆里表现并不惊喜
最主要的应用是需要低GC延迟并具有大堆的应用程序提供解决方案 如在堆大小约6GB或更大时可预测的暂停时间可以低于0.5秒(G1通过每次只清理部分而不是全部的Region的增量式清理来保证每次GC停顿时间不会过长)。
2用来替换掉JDK1.5中的CMS收集器在下面的情况时使用G1可能比CMS好
①超过50%的Java堆被活动数据占用 ②对象分配频率或年代提升频率变化很大 ③GC停颅时间过长长于0.5至1秒。
HotSpot垃圾收集器里除了G1以外其他的垃圾收集器使用内置的JVM线程执行GC(线程优先级低)的多线程操作
而G1GC可以采用应用线程承担后台运行的GC工作即当M的GC线程处理速度慢时系统会调用应用程序线程帮助加速垃圾回收 过程。
垃圾回收器的使用
G1调优快速使用
最简单的使用G1调优3大步骤
开启G1GC设置堆的最大内存【防止内存抖动】设置最大的停顿时间【单位毫秒】
简化设置
java -jar -XX:UseG1GC -Xms4g -Xmx4g -XX:MaxGCPauseMillis200 某一个项目.jar完整设置
java -jar -XX:UseG1GC -Xms4g -Xmx4g -XX:MaxGCPauseMillis200 -XX:ParallelGCThreads8 -XX:G1HeapRegionSize16m -XX:UnlockExperimentalVMOptions -XX:G1NewSizePercent20 -XX:G1MaxNewSizePercent40 -XX:TargetSurvivorRatio50 -XX:MaxTenuringThreshold15 -XX:InitiatingHeapOccupancyPercent45 -XX:PrintGCDetails -XX:PrintGCDateStamps -XX:PrintGCTimeStamps -Xloggc:/root/gc.log 某一个项目.jar-Xms4g 设置初始堆的大小 -Xmx4g 设置最大堆的大小 -XX:UnlockExperimentalVMOptions 解锁JVM额外参数 -XX:PrintGCDetails -XX:PrintGCDateStamps -XX:PrintGCTimeStamps -Xloggc:/root/gc.log 打印详细的 GC 日志包括日期和时间戳并将其写入指定的文件 8种GC回收器的比较 怎么选择GC回收器
怎么选择垃圾收集器
1.优先调整堆的大小让JVM自适应完成。 2.如果内存小于100M使用串行收集器 3.如果是单核、单机程序并且没有停顿时间的要求串行收集器 4.如果是多CPU、需要高吞吐量、允许停顿时间超过1秒选择并行或者JVM自己选择 5.如果是多CPU、追求低停顿时间需快速响应比如延迟不能超过1秒如互联网应用使用并发收集器
官方推荐G1性能高。现在互联网的项目基本都是使用G1。 总结
1.JVM的内存模型对比了1.7和1.8的内存模型的变化 2.垃圾回收的语言发展定位垃圾的方法引用计数法和可达性分析发以及垃圾清除算法 3.Java中的垃圾回收器由串行、到并行再到并发最后到G1的演变 4.垃圾回收器的对比和使用指引。