Java堆
堆结构图:
Java8将永久代换成了元空间。
新生区
新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。
新生区又分为两部分: 伊甸区(Eden space)和幸存者区(Survivor pace) ,所有的类都是在伊甸区被new出来的。
幸存区有两个: 0区(Survivor 0 space)和1区(Survivor 1 space)。
当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园区中的不再被其他对象所引用的对象进行销毁。
然后将伊甸园中的剩余对象移动到幸存 0区。若幸存 0区也满了,再对该区进行垃圾回收,然后移动到 1 区。
那如果1 区也满了呢?再移动到养老区。若养老区也满了,那么这个时候将产生MajorGC(FullGC),进行养老区的内存清理。
若养老区执行了Full GC之后发现依然无法进行对象的保存,就会产生OOM异常“OutOfMemoryError”。
如果出现java.lang.OutOfMemoryError: Java heap space异常,说明Java虚拟机的堆内存不够。原因有二:
(1)Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整。
(2)代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)。
GC垃圾回收
Java堆从GC的角度还可分为新生代(Eden区、From Survivor区和To Survivor区)和老年代。
MinorGC的过程(复制->清空->互换)
1:eden、SurvivorFrom 复制到 SurvivorTo,年龄+1
首先,当Eden区满的时候会触发第一次GC,把还活着的对象拷贝到SurvivorFrom区,当Eden区再次触发GC的时候会扫描Eden区和From区域,对这两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域(如果有对象的年龄已经达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1
2:清空 eden、SurvivorFrom
然后,清空Eden和SurvivorFrom中的对象,也即复制之后有交换,谁空谁是to
3:SurvivorTo和 SurvivorFrom 互换
最后,SurvivorTo和SurvivorFrom互换,原SurvivorTo成为下一次GC时的SurvivorFrom区。部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代
实际而言,方法区(Method Area)和堆一样,是各个线程共享的内存区域,它用于存储虚拟机加载的:类信息+普通常量+静态常量+编译器编译后的代码等等,虽然JVM规范将方法区描述为堆的一个逻辑部分,但它却还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。
对于HotSpot虚拟机,很多开发者习惯将方法区称之为“永久代(Parmanent Gen)” ,但严格本质上说两者不同,或者说使用永久代来实现方法区而已,永久代是方法区(相当于是一个接口interface)的一个实现,jdk1.7的版本中,已经将原本放在永久代的字符串常量池移走。
永久区(java7之前有)
永久存储区是一个常驻内存区域,用于存放JDK自身所携带的 Class,Interface 的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭 JVM 才会释放此区域所占用的内存。
在Java8中,永久代已经被移除,被一个称为元空间的区域所取代。元空间的本质和永久代类似。
元空间与永久代之间最大的区别在于:
永久带使用的JVM的堆内存,但是java8以后的元空间并不在虚拟机中而是使用本机物理内存。
因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory, 字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由MaxPermSize 控制, 而由系统的实际可用空间来控制。
如何判定垃圾和垃圾回收算法
如何判断一个对象是否是垃圾?
引用计数算法(Reference Counting)
引用计数算法:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。
优点:判定效率高
缺点:占用空间,不能解决循环引用的问题
可达性分析算法(Reachability Analysis)
可达性分析算法:这个算法的基本思路就是通过一系列称为GC Roots的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称引用链(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的.
分代收集(Generational Collection)
分代收集理论建立在以下假说之上:
1)弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。
2)强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。
设计原则:收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。
显而易见,如果一个区域中大多数对象都是朝生夕灭,难以熬过垃圾收集过程的话,那么把它们集中放在一起,每次回收时只关注如何保留少量存活而不是去标记那些大量将要被回收的对象,就能以较低代价回收到大量的空间;
如果剩下的都是难以消亡的对象,那把它们集中放在一块,虚拟机便可以使用较低的频率来回收这个区域,这就同时兼顾了垃圾收集的时间开销和内存的空间有效利用。
新生代收集( Minor GC/Young GC):指目标只是新生代的垃圾收集。
老年代收集( Major GC/Old GC):指目标只是老年代的垃圾收集。目前只有CMS收集器会有单独收集老年代的行为。
混合收集( Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集。目前只有G1收集器会有这种行为。
整堆收集( Full GC):收集整个Java堆和方法区的垃圾收集。
标记-复制算法
标记-复制算法:也称为复制算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
应用范围:年轻代
缺点:如果内存中多数对象都是存活的,这种算法将会产生大量的内存间复制的开销。
标记-清除算法(Mark-Sweep)
标记-清除算法:算法分标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。
应用范围:老年代
缺点:
第一个是执行效率不稳定,如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低;
第二个是内存空间的碎片化问题,标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
标记-整理算法(Mark-Compact)
标记-整理算法:其中的标记过程仍然与标记-清算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
应用范围:老年代
总结:
标记-清除算法与标记-整理算法的本质差异在于前者是一种非移动式的回收算法,而后者是移动式的。
堆内存调优:
-Xms : 设置初始分配大小,默认为物理内存的1/64
-Xmx : 最大分配内存,默认为物理内存的1/4
-XX:+PrintGCDetails : 输出详细的GC处理日志
VM参数:-Xms1024m -Xmx1024m -XX:+PrintGCDetails
package com.fll.jvm;
public class RuntimeGetMem {
public static void main(String[] args) {
/**
* -Xms : 设置初始分配大小,默认为物理内存的1/64
* -Xmx : 最大分配内存,默认为物理内存的1/4
* -XX:+PrintGCDetails : 输出详细的GC处理日志
*/
long maxMemory = Runtime.getRuntime().maxMemory();//返回java虚拟机试图使用的最大内存量
long totalMemory = Runtime.getRuntime().totalMemory();//返回java虚拟机中的内存总量
System.out.println("-Xmx:MAX_MEMORY = "+maxMemory+" (字节) 、"+(maxMemory/(double)1024/1024)+"MB");
System.out.println("-Xms:TOTAL_MEMORY = "+totalMemory+" (字节) 、"+(totalMemory/(double)1024/1024)+"MB");
System.out.println("CPU核心: "+Runtime.getRuntime().availableProcessors());
}
}
测试OOM异常:
package com.fll.jvm;
import java.util.Random;
public class OOM_Test01 {
public static void main(String[] args) {
/**
* -Xms : 设置初始分配大小,默认为物理内存的1/64
* -Xmx : 最大分配内存,默认为物理内存的1/4
* -XX:+PrintGCDetails : 输出详细的GC处理日志
*/
/**
* 在Eclipse中,右键项目-->Run As-->Run Configurations...
* 将Arguments中的VMarguments设成:
* -Xms5m -Xmx5m -XX:+PrintGCDetails
*
* OOM : OutOfMemory
*
*
*/
String str = "www.fll.com" ;
while(true){
str += str + new Random().nextInt(88888888) + new Random().nextInt(999999999) ;
}
//new 一个10MB的数组
//byte[] bytes = new byte[10*1024*1024];
/*
* 输出: java.lang.OutOfMemoryError: Java heap space
*/
}
}