JVM内存区域
方法区(线程共享)
-
也称"永久代” 、“非堆”,它用于存储虚拟机加载的类信息、常量、静态变量,是各个线程共享的内存区域,默认最小值为16MB,最大值为64MB,可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小
-
永久代的回收有两种:常量池中的常量和无用的类信息,判断类“无用”的三个条件:
- 该类所有的实例都已经被回收
- 加载该类的ClassLoader已经被回收
- 类对象的Class对象没有被引用(即没有通过反射引用该类的地方)
- 内存不足时抛出OutOfMemoryError:PermGen space
堆(线程共享)
-
堆是java虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,在JVM启动时创建,该内存区域存放了对象实例及数组
-
通过-Xms(最小值)和-Xmx(最大值)参数设置,-Xms为JVM启动时申请的最小内存,-Xmx为JVM可申请的最大内存,默认当空余堆内存小于40%时,JVM会增大Heap到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列;当空余堆内存大于70%时,JVM会减小heap的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列,对于运行系统,为避免在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样
-
由于现在收集器都是采用分代收集算法,堆被划分为新生代和老年代,新生代主要存储新创建的对象和尚未进入老年代的对象。老年代存储经过多次新生代GC(Minor GC)任然存活的对象
-
gc后,没有足够的内存分配,也不能再扩展抛出OutOfMemoryError:Java heap space
新生代
- 程序新创建的对象都是从新生代分配内存,新生代由Eden Space和两块相同大小的Survivor Space(通常又称S0和S1或From和To)构成,占比默认8:1:1,可通过-Xmn参数来指定新生代的大小,也可以通过-XX:SurvivorRation来调整Eden Space及Survivor Space的大小
老年代
-
用于存放经过多次新生代GC任然存活的对象,例如缓存对象,新建的对象也有可能直接进入老年代,主要有两种情况:
①.大对象,可通过启动参数设置-XX:PretenureSizeThreshold=1024(单位为字节,默认为0)来代表超过多大时就不在新生代分配,而是直接在老年代分配。
②.大的数组对象,数组中无引用外部对象
-
老年代所占的内存大小为-Xmx对应的值减去-Xmn对应的值
虚拟机栈
- 描述方法执行的内存模型:每个方法被执行的时候 都会创建一个“栈帧”用于存储局部变量表(包括参数)、操作栈、方法出口等信息,每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程,线程私有
本地方法栈
- 与虚拟机栈基本类似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务
程序计数器
- 是最小的一块内存区域,它的作用是当前线程所执行的字节码的行号指示器,在虚拟机的模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、异常处理、线程恢复等基础功能都需要依赖计数器完成
直接内存
- 直接内存并不是JVM管理的内存,是JVM运行内存之外的机器内存,在jdk中可以通过调用native方法来分配直接内存;然后在JVM堆中通过DireactByteBuffer来操作直接内存,两者之间使用基于通道和缓冲区的IO方式
垃圾回收机制
如何判断Java对象需要被回收?
我们常说的垃圾回收指的是回收掉堆内存,那如何来判断堆内存里的对象需要回收呢,业界通用有以下两种办法
引用计数算法
- 引用计数法记录着每一个对象被其它对象所持有的引用数,被引用一次就加一,引用失效就减一;引用计数器为0则说明该对象不再可用;当一个对象被回收后,被该对象所引用的其它对象的引用计数都应该相应减少,它很难解决对象之间的相互循环循环引用实例
可达性分析算法
- 从GC Root对象向下搜索其所走过的路径称为引用链,当一个对象不再被任何的GC root对象引用链相连时说明该对象不再可用
- GC root对象包括四种:方法区中常量;静态变量引用的对象;虚拟机栈中变量引用的对象;本地方法栈中引用的对象;
- 上图中GC Roots左边的对象都有引用链相关联,所以他们不是 死亡对象(详细说明见下文),而在GCRoots右边的对象没有引用链相关联,所以会被Java虚拟机判定为死亡对象而被回收
何为死亡对象?
- Java GC采用可达性分析算法
- Java虚拟机在进行死亡对象判定时,会经历两个过程。如果对象在进行可达性分析后没有与GC Roots相关联的引用链,则该对象会被JVM进行第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,如果当前对象没有覆盖该方法,或者finalize方法已经被JVM调用过都会被虚拟机判定为“没有必要执行”,则该对象将会被放置在一个叫做F-Queue的队列当中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它,在执行过程中JVM可能不会等待该线程执行完毕,因为如果一个对象在finalize方法中执行缓慢,或者发生死循环,将很有可能导致F-Queue队列中其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。如果在finalize方法中该对象重新与引用链上的任何一个对象建立了关联,即该对象连上了任何一个对象的引用链,例如this关键字,那么该对象就会逃脱垃圾回收系统;如果该对象在finalize方法中没有与任何一个对象进行关联操作,那么该对象会被虚拟机进行第二次标记,该对象就会被垃圾回收系统回收。值得注意的是finalize方法JVM系统只会自动调用一次,如果对象面临下一次回收,它的finalize方法不会被再次执行
垃圾回收算法
标记—清除算法
- 标记清除算法分为“标记”和“清除”两个阶段:首先标记出需要回收的对象,标记完成之后统一清除对象
- 主要缺点:
- 标记和清除过程效率不高
- 标记清除之后会产生大量不连续的内存碎片
复制算法
- 它将可用内存容量划分为大小相等的两块,每次只使用其中的一块,当这一块用完之后,就将还存活的对象复制到另外一块上面,然后在把已使用过的内存空间一次清理
- 这样使得每次都是对其中的一块进行内存回收,不会产生碎片等情况,只要移动堆订的指针,按顺序分配内存即可,实现简单,运行高效
- 主要缺点: 内存缩小为原来的一半。
标记—整理算法
- 标记操作和“标记-清除”算法一致,后续操作不只是直接清理对象,而是在清理无用对象完成后让所有存活的对象都向一端移动,并更新引用其对象的指针。
- 主要缺点: 在标记-清除的基础上还需进行对象的移动,成本相对较高,好处则是不会产生内存碎片。
分代收集算法
- 分代收集算法是目前大部分JVM的垃圾收集器采用的算法,它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域,一般情况下将堆区划分为老年代和新生代
- 老年代的特点是每次垃圾收集时只有少量对象需要被回收,采用标记—整理算法
- 新生代的特点是每次垃圾回收时都有大量的对象需要被回收,采用复制算法,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor(From)中还存活的对象复制到另一块Survivor(To)空间中,然后清理掉Eden和刚才使用过的Survivor(From)空间,此时From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”,“To”的Survivor区域是空的
Java内存分配策略
1.对象优先在Eden上分配
- 大多数情况下,对象优先在新生代Eden区域中分配。当Eden内存区域没有足够的空间进行分配时,虚拟机将触发一次 Minor GC(新生代GC)。Minor GC期间虚拟机将Eden区域的对象移动到其中一块Survivor区域
2.大对象直接进入老年代
- 所谓大对象是指需要大量连续空间的对象,虚拟机提供了一个XX:PretenureSizeThreshold参数,令大于这个值的对象直接在老年代中分配。
3.长期存活的对象将进入老年代
- 虚拟机采用分代收集的思想管理内存,那内存回收时就必须能识别那些对象该放到新生代,哪些该到老年代中。虚拟机为每个对象定义了一个对象年龄Age,每经过一次新生代GC(Minor GC)后任然存活,将对象的年龄Age增加1岁,当年龄到一定程度(默认为15)时,将会被晋升到老年代中,对象晋升老年代的年龄限定值,可通过-XX:MaxTenuringThreshold来设置
4.动态对象年龄判定
- 为了使内存分配更加灵活,虚拟机并不要求对象年龄达到MaxTenuringThreshold才晋升老年代 如果Survivor区中相同年龄所有对象大小的总和大于Survivor区空间的一半,年龄大于或等于该年龄的对象在Minor GC时将复制至老年代
5.空间分配担保
- 当出现大量对象在Minor GC后仍然存活的情况,就需要老年代进行分配担保,把Survivor无法容纳的对象直接进入老年代,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,决定是否进行Full GC来让老年代腾出更多空间。如果大于,则进行一次full Gc ,如果小于,则查看HandlePromotionFailure 设置是否允许担保失败,如果 允许,那只会进行Minor GC; 如果不允许,则也要改为进行一次Full GC
垃圾收集器
- 垃圾回收算法是内存回收的理论基础,而垃圾收集器就是内存回收的具体实现。下面介绍一下HotSpot(JDK 7)虚拟机提供的几种垃圾收集器,用户可以根据自己的需求组合出各个年代使用的收集器
1. Serial 收集器 串行
- 单线程的串行新生代收集器,复制算法,它在垃圾收集的时候会暂停其它所有工作线程,直到收集结束,一般在客户端模式下使用
2. ParNew收集器 并行
- ParNew收集器是Serial的多线程版本,复制算法,一般运行在Server模式下首先的新生代收集器。通常老年代使用CMS收集器与区合作,参数:-XX:+UseConcMarkSweepGC,比较适合web服务的收集器
3. Parallel Scavenge收集器 并行
- 复制算法的收集器,并且是多线程的。该收集器主要目的就是达到一个可控制的吞吐量,就是CPU的利用率。于是该收集器比较适合后端运算比较多的服务
4.Serial Old收集器 串行
- 单线程串行的老年代收集器,复制算法
5. Parallel Old 收集器 并行
- 标记-整的算法,该收集器比较适合和Parallel Scavenge收集器进行组合
6. CMS收集器
- 一种以获取最短回收停顿时间为目标的老年代收集器,它是一种并发收集器,目前大部分的B/S系统都使用CMS的收集器,一般和PerNew进行组合
- 标记-清除算法。分四个阶段:初始标记,并发标记,重新标记,并发清除
G1收集器
- G1收集器是当今收集器技术发展最前沿的成果,它是一款面向服务端应用,充分利用多CPU、多核环境,并行与并发收集器,并且它能建立可预测的停顿时间模型
参考文章
- https://blog.csdn.net/snow_114/article/details/80214332
- https://www.cnblogs.com/hongdada/p/6060186.html
- http://www.cnblogs.com/dolphin0520/p/3783345.html 本文参考了多篇文章,加以自己的理解和归纳,可能在描述和理解上存在一定的出入,如有不妥,请指出,谢谢谅解