Java内存构成及垃圾回收

4 篇文章 0 订阅
4 篇文章 0 订阅

Java内存构成及垃圾回收

摘要 1
java虚拟机运行时内存构成 2
2.1 JVM运行时内存构成 2
2.2 JVM内存溢出 2
Java垃圾收集算法 3
3.1 常用垃圾收集算法 3
3.2 分代垃圾收集流程及JVM策略 3
3.3 常用垃圾收集器及优缺点 4
Serial收集器: 4
ParNew收集器: 5
Parallel Scavenge收集器: 5
Parallel Old收集器: 5
CMS收集器: 5
G1收集器: 7
Java7-8 JVM内存改进 7

摘要
在阅读本文前可以问自己如下几个问题,如果这些问题对你来说不算问题,恭喜,你已对Java内存及垃圾回收了如指掌,如果还不能准确回答这些问题,那我们可以开始共同探讨了。
(1) java内存模型由哪几部分组成,各部分内存都有什么特点,适合用什么类型的垃圾收集器?对象访问在Java内存模型中是怎样实现的?
(2) GC FULLGC有什么区别?一个Java对象想要躲过回收都会经历怎样的生命周期?
(3) JVM在GC时进行了哪些优化来保持整个内存的健康状态
(4) JVM常用垃圾收集器有哪些?各有什么优缺点,及其使用场景
(5) java8对内存模型做了哪些改进,原因是什么,达到了什么效果?
Java语言和C++之间有一堵内存自动管理的墙,使用Java开发可以不用手动管理内存和指针,JVM虚拟机自动回收垃圾。但了解JVM内存的构造及垃圾收集原理,可以帮助我们在生产环境调整参数以提高JVM性能,并帮助我们写出更优质的Java代码。
java虚拟机运行时内存构成
2.1 JVM运行时内存构成

        JVM运行时内存构成如上图所示。其中方法区和堆是线程共有的,虚拟机栈、本地方法栈、程序计数器是线程私有的,各部分存放内容介绍如下。
      (1)程序计数器   当前线程所执行字节码的行号指示器。
      (2)Java虚拟机栈  java方法执行时的内存模型。每个方法在执行时都会创建一个栈帧,存储局部变量表、操作栈、动态链接、方法出口等信息。
       每个线程都有自己独立的栈空间;虚拟机栈内只存基本类型和对象引用数据;方法中的局部变量在栈空间中。
     (3)本地方法栈 虚拟机栈为虚拟机方法服务,本地方法栈为本地方法服务。
     (4)堆  存放所有对象实例及属性,垃圾回收重点。
      堆内存使用分代垃圾收集算法,堆内存又分为新生代和老年代,新生代又分为Eden区,Survior1区和Survior2区。
      (5)方法区  方法区内有常量池。 方法区存放已被虚拟机加载的类信息、静态变量、常量、即时编译器编译后的代码等数据。别名 永久代
      (6)直接内存 NIO、Native函数直接分配的堆外内存。DirectBuffer引用也会使用此部分内存。
     如何访问一个对象及其类型信息:
     (1)使用句柄访问方式,Java堆中划分一部分内存用来作为句柄池,栈中引用存储对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。
     (2)使用直接指针访问, 堆对象需要考虑如何放置访问类型数据的相关信息。

2.2 JVM内存溢出
在JVM申请内存时,如果内存不足,则会溢出,JVM内存各部分溢出情况如下:
(1)Java堆内存溢出 OutOfMemoryError 当创建大量对象并且对象生命周期都很长时,触发溢出。
(2)JVM虚拟机栈和本地方法栈溢出
StackOverflowError 线程请求的栈深度大于虚拟机允许的最大深度(递归方法循环调用)
OutOfMemoryError 虚拟机在扩展栈时无法申请到足够的内存空间,通过不断创建线程触发
(3)方法区溢出 OutOfMemoryError:PermGen space
运行时常量池溢出:string 不断intern
方法区存储class信息,通过cglib等创建大量class可触发
Java垃圾收集算法
Java垃圾收集首先要找到待收集的对象,比较古老的是引用计数法,Hotspot采用GCRoot追踪法,从”GC roots”开始扫描(这里的roots包括线程栈、静态常量等),给能够沿着roots到达的对象标记为”live”,最终所有能够到达的对象都被标记为”live”,而无法到达的对象则为”dead”。效率和存活对象的数量是线性相关的。
3.1 常用垃圾收集算法
标记-清除算法(Mark-Sweep) 最基本的清理算法,后续算法都从此算法演进而来。
缺点:标记,清除效率低;清除后存有大量不连续的内存碎片,空间碎片多导致大对象分配找不到连续内存空间而出发另一次GC
(2)复制算法(Copying Collector 复制算法) 为解决效率问题提出的算法,将内存划分大小相等两部分,每次使用From,回收时存活的复制到To,然后清理From,to From互换。
缺点:只使用一半内存,浪费。
(3)标记-整理算法(Mark-Sweep-Compact )
清理时不是直接清理,而是将存活的往一端复制,然后清除边界外内存。
(4)分代收集算法
分代收集算法是把Java堆分成年轻代和老年代,在年轻代对象朝生夕死,采用复制算法,只需要付出少量复制成本就可以完成垃圾收集;在老年代对象存活率高,没有额外空间进行分配担保,采用标记-清理或标记整理算法。
3.2 分代垃圾收集流程及JVM策略
分代收集算法中Java堆分为年轻代和老年代,其中年轻代又分为Eden区和SurviorFrom 和SurviorTo区。Eden区分配新创建的对象,Survior区存放GC后存活的对象,老年代存放长期存活的对象。

分代垃圾收集流程如下:
对象在Eden区分配内存空间;
当Eden区内存空间不足时触发MinorGC,Eden区中存活对象移动至SurviorFrom区;存活对象年龄加1;
对象继续在Eden区分配内存空间,空间不足触发MinorGC,此时Eden区中存活对象和SurviorFrom区存活对象复制到SurviorTo区,Eden区和surviorFrom区清空,surviorFrom到To区年龄变为2,Eden到To区年龄变1;SurviorFrom和SurviorTo角色互换;
当SurviorTo区空间不足,MinorGC后存活的对象将通过分配担保机制进入老年代;
老年代空间不足将触发FullGC/MajorGC,出现FullGC通常会伴随至少一次的MinorGC。
内存分配与回收的几个策略:
大对象直接进入老年代,避免在Eden区及两个Survior区发生大量的内存拷贝。对象大小通过-XX:PretenureSizeThreshold参数设置。
长期存活的对象将进入老年代。对象每熬过一次MinorGC年龄加1,默认到15岁会被晋升到老年代,年龄阀值通过-XX:MaxTenuringThredshold来设置。
动态对象年龄判断:如果再Survior区中某一年龄的对象大小总和超过Survior空间的一半,大于等于该年龄的对象直接进入老年代。
空间分配担保, 在发生MinorGC时,JVM会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则改为直接进行一次FullGC,如果小于,则查看参数HandlerPromotionFailure是否允许担保失败,如果允许,只会进行MinorGC,不允许则执行FullGC。
3.3 常用垃圾收集器及优缺点

Serial收集器:

-XX:+UserSerialGC参数打开此收集器
Client模式下新生代默认的收集器。
较长的stop the world时间
简单而高效
ParNew收集器:

-XX:+UserParNewGC
+UseConcuMarkSweepGC时默认开启
Serial收集器的多线程版本
默认线程数与CPU数目相同
-XX:ParrallelGCThreads指定线程数目
Parallel Scavenge收集器:

新生代并行收集器
采用Copy算法
主要关注的是达到可控制的吞吐量,“吞吐量优先”
-XX:MaxGCPauseMillis -XX:GCTimeRAtion两个参数精确控制吞吐量
-XX:UseAdaptiveSizePolicy GC自适应调节策略
Server模式的默认新生代收集器

Parallel Old收集器:

-XX:+UseParallelGC -XX:+UseParallelOldGC启用此收集器
Server模式的默认老年代收集器
Parallel Scavenge的老年代版本,使用多线程和”mark-sweep”算法
关注点在吞吐量以及CPU资源敏感的场合使用
一般使用Parallel Scavenge + Parallel Old可以达到最大吞吐量保证

CMS收集器:

并发低停顿收集器

-XX:UseConcMarkSweepGC 开启CMS收集器,(默认使用ParNew作为年轻代收集器,SerialOld作为收集失败的垃圾收集器)
以获取最短回收停顿时间为目标的收集器,重视响应速度,希望系统停顿时间最短,会和互联网应用。

四个步骤:

初始标记 Stop the world: 只标记GC roots能直接关联到的对象,速度很快。
并发标记:进行GC roots tracing,与用户线程并发进行
重新标记 Stop the world:修正并发标记期间因程序继续运行导致变动的标记记录
并发清除
CMS有以下的缺点:

CMS是唯一不进行compact的垃圾收集器,当cms释放了垃圾对象占用的内存后,它不会把活动对象移动到老年代的一端
对CPU资源非常敏感。不会导致线程停顿,但会导致程序变慢,总吞吐量降低。CPU核越多越不明显
无法处理浮动垃圾。可能出现“concurrent Mode Failure”失败, 导致另一次full GC ,可以通过调整-XX:CMSInitiatingOccupancyFraction来控制内存占用达到多少时触发gc
大量空间碎片。这个可以通过设置-XX:UseCMSCompacAtFullCollection(是否在full gc时开启compact)以及-XX:CMSFullGCsBeforeCompaction(在进行compact前full gc的次数)

G1收集器:

G1算法在Java6中还是试验性质的,在Java7中正式引入,但还未被广泛运用到生产环境中。它的特点如下:

使用标记-清理算法

不会产生碎片
可预测的停顿时间
化整为零:将整个Java堆划分为多个大小相等的独立区域
-XX:+UseG1GC可以打开此垃圾回收器
-XX:MaxGCPauseMillis=200可以设置最大GC停顿时间,当然JVM并不保证一定能够达到,只是尽力。

Java7-8 JVM内存改进
(1)Java7带来的内存方面的一个很大的改变就是String常量池从Perm区移动到了Heap中。调用String的intern方法时,如果存在堆中的对象,则会直接保存对象的引用,而不会重新创建对象。
(2)Java7正式引入G1垃圾收集器用于替换CMS。
(3)Java8中,取消掉了方法区(永久代),使用“元空间”替代,元空间只与系统内存相关。
(4)Java 8 update 20所引入的一个很棒的优化就是G1回收器中的字符串去重(String deduplication)。由于字符串(包括它们内部的char[]数组)占用了大多数的堆空间,这项新的优化旨在使得G1回收器能识别出堆中那些重复出现的字符串并将它们指向同一个内部的char[]数组,以避免同一个字符串的多份拷贝,那样堆的使用效率会变得很低。可以使用-XX:+UseStringDeduplication这个JVM参数来试一下这个特性。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值