首先来看一下java运行时数据区的划分
线程私有
虚拟机栈 :存储当前栈桢 局部变量 操作数栈 动态链接 方法出口等 每一个方法的执行就对应着栈桢在虚拟机栈中的入栈出栈过程
程序计数器:因为线程要被cpu轮询来获取使用权,程序计数器的作用就是记录当前线程字节码的执行行数.
本地方法栈:
本地方法栈(Native Method Stacks)与 Java 虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的 Native 方法服务
Navtive 方法是 Java 通过 JNI 直接调用本地 C/C++ 库,可以认为是 Native 方法相当于 C/C++ 暴露给 Java 的一个接口,Java 通过调用这个接口从而调用到 C/C++ 方法。当线程调用 Java 方法时,虚拟机会创建一个栈帧并压入 Java 虚拟机栈。然而当它调用的是 native 方法时,虚拟机会保持 Java 虚拟机栈不变,也不会向 Java 虚拟机栈中压入新的栈帧,虚拟机只是简单地动态连接并直接调用指定的 native 方法。
线程共享
堆:Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象,在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分两个区域:Eden、From Survivor、To Survivor。堆大小 = 新生代 + 老年代。新生代 ( Young ) 与老年代 ( Old ) 的默认比例的值为 1:2,其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。默认的,Edem : from : to = 8 : 1 : 1
方法区: 线程共享,方法区中存储了每个类型的对应的常量池,即虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等等。(HotSpot虚拟机上开发部署人员更愿意成为“永久代”,Permanent Generation)
又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
运行时常量池:维护在方法区中
gc堆
java堆与垃圾收集器
对于线程私有的虚拟机栈、本地方法栈及程序计数器所占用的内存来说,他们会随着线程的结束而被自动回收,而java堆因为是垃圾收集器的主要管理对象,常被称为GC堆
判断对象是否存活的算法:
- 引用计数算法
- 算法思想:给对象添加一个引用计数器,每当有一个地方引用它,就将计数器加一;当引用失效计数器就减一。任何时刻当计数器为0时,就不可能再被引用。
- 优点:实现简单,判定效率也很高
- 缺点:主流的java虚拟机没有选用计数器算法的主要原因在于,它很难解决循环引用问题。
- 可达性计数算法: java, C#等语言所采用的判断对象是否存活的算法。
- 算法思想: 通过一系列称为”GC Roots"的对象作为起点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象没有任何引用链与GC roots相连时,则此对象是不可用对象,可被判定为可回收对象。
- 可作为GC Roots对象:
- 虚拟机栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区常量引用的对象
- 本地方法栈中JNI引用的对象
引用:
- 强引用
- 定义:Object obj = new Object();
- 特点:只要强引用还在,垃圾回收器永远不会回收被引用的对象
- 软引用
- 定义:使用SoftReference类来实现软引用,软引用对象最常用于实现内存敏感的缓存。
- 特点:在系统将要发生内存溢出异常之前,会把软引用纳入回收范围中进行二次回收。
- 弱引用
- 定义:比软引用更弱,WeakReference来实现,弱引用最常用于实现规范化的映射。可以使用弱引用的isEnQueued方法返回对象监控对象是否已经被垃圾回收器标记为即将回收的垃圾。
- 特点:被弱引用关联的对象,只能生存到下一次垃圾收集发生之前。
- 虚引用
- 定义:也称为幽灵引用或幻影引用,是最弱的一种引用关系。PhantomReference来实现。主要用于检测对象是否已经从内存中删除。
- 特点:一个对象时候有虚引用的存在,完全不会对其生存时间产生任何影响,也无法通过虚引用来取得一个对象实例。
垃圾收集算法:
- 标记-清除算法:
- 复制算法: 目前商业虚拟机采用这种方法来回收新生代。
- 主要思想:在内存中按容量分为大小相等的两块,每次值使用其中一块,当这一块的内存用完后,将还存活的对象复制到另一块,之后清空已使用过的这一块内存。因为98%的对象都是创建之后很快失效,因此不需要两块相等比例的内存,进而将内存分为一块较大的Eden区,和两块较小的Survivor区,每次使用Eden和一块Survivor。当回收时将Eden和该块Survivor的存活的对象复制到另一块Survivor,之后清空Eden和上一块Survivor。(Hotspot默认Eden:Survivor = 8 : 1)。
- 标记-整理算法: 复制算法在对象存活比例较高时效率将会降低,因此回收老年代所采用的算法是标记-整理算法。
- 分代收集算法: 当前商业虚拟机的垃圾回收器都是采用“分代收集”
- 根据对象存活周期的不同将内存划分为几块,一般是把java堆分为新生代和老年代,这样就可以根据每个年代不同的特点选择适当的收集方法。
Minor GC 与Full Gc
Minor GC 是发生在新生代中的垃圾收集动作,所采用的是复制算法。
新生代几乎是所有 Java 对象出生的地方,即 Java 对象申请的内存以及存放都是在这个地方。Java 中的大部分对象通常不需长久存活,具有朝生夕灭的性质。
当一个对象被判定为 “死亡” 的时候,GC 就有责任来回收掉这部分对象的内存空间。新生代是 GC 收集垃圾的频繁区域。
当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,在经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳
( 上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域 ( 即 to 区域 ) 中,然后清理所使用过的 Eden 以及 Survivor 区域 ( 即 from 区域 ),并且将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,可以通过参数 -XX:MaxTenuringThreshold 来设定 ),这些对象就会成为老年代。
但这也不是一定的,对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代。
Full GC 是发生在老年代的垃圾收集动作,所采用的是标记-清除算法。
现实的生活中,老年代的人通常会比新生代的人 “早死”。堆内存中的老年代(Old)不同于这个,老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 “死掉” 了的。因此,Full GC 发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长。
另外,标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作。
配置
垃圾回收器经典算法
1. Reference counting(引用计数)
思想
当对象创建并赋值时该对象的引用计数器置1,每当对象给任意变量赋值时,引用记数+1;一旦退出作用域则引用记数-1。一旦引用记数变为0,则该对象可以被垃圾回收。
优势
对程序的执行来说,每次操作只需要花费很小块的时间。这对于不能被过长中断的实时系统来说有着天然的优势
不足
不能够检测到环(两个对象的互相引用);同时在每次增加或者减少引用记数的时候比较费时间。在现代的垃圾回收算法中,引用记数已经不再使用
2.Mark-sweep(标记清理)
思想
每次从根集出发寻找所有的引用(称为活对象),每找到一个,则对其做出标记,当追踪完成之后,所有的未标记对象便是需要回收的垃圾。
3.Copying collection(复制收集)
思想
将内存划分为两块,一块是当前正在使用;另一块是当前未用。每次分配时使用当前正在使用内存,当无可用内存时,对该区域内存进行标记,并将标记的对象全部拷贝到当前未用内存区,这是反转两区域,即当前可用区域变为当前未用,而当前未用变为当前可用,继续执行该算法。
不利
拷贝算法需要停止所有的程序活动,然后开始冗长而繁忙的copy工作
4. Generational garbage collection(分代)
思想
将内存区域分两块(或更多),其中一块代表年轻代,另一块代表老的一代。针对不同的特点,对年轻一代的垃圾收集更为频繁,对老代的收集则较少,每次经过年轻一代的垃圾回收总会有未被收集的活对象,这些活对象经过收集之后会增加成熟度,当成熟度到达一定程度,则将其放进老代内存块中。
JVM分别对新生代和老年代采用不同的垃圾回收机制
GC触发条件
Eden区满了触发Minor GC,这时会把Eden区存活的对象复制到Survivor区,当对象在Survivor区熬过一定次数的Minor GC之后,就会晋升到老年代(当然并不是所有的对象都是这样晋升的到老年代的),当老年代满了,就会报OutofMemory异常
minorGC
新生代GC,指发生在新生代的垃圾收集动作,所有的Minor GC都会触发全世界的暂停(stop-the-world),停止应用程序的线程,不过这个过程非常短暂
新生代通常存活时间较短基于Copying算法进行回收,所谓Copying算法就是扫描出存活的对象,并复制到一块新的完全未使用的空间中,对应于新生代,就是在Eden和FromSpace或ToSpace之间copy。新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。当连续分配对象时,对象会逐渐从Eden到Survivor,最后到老年代
执行机制
串行GC(SerialGC)
单线程,工作时必须暂停其他工作线程。多用于client机器上,使用复制算法
并行回收GC(ParallelScavenge)
复制算法,可控制吞吐量的收集器。吞吐量即有效运行时间。
并行GC(ParNew)
serial收集器的多线程版本,server模式下虚拟机首选的新生代收集器。复制算法
Major GC/Full GC (老年代GC)
采用标记(Mark)算法来进行回收,所谓标记就是扫描出存活的对象,然后再进行回收未被标记的对象,回收后对用空出的空间要么进行合并、要么标记出来便于下次进行分配,总之目的就是要减少内存碎片带来的效率损耗
执行机制
串行GC(Serial MSC)
并行GC(Parallel MSC)
并发GC(CMS)