一、前言
二、虚拟机内存划分
2.1 程序计数器
2.2 Java虚拟机栈
Java Virtual Machine Stacks 生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个帧栈(Stack Frame),用于存储2.3 本地方法栈
2.4 Java堆
HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1
2.5 方法区
2.6 其他区域
三、Hotspot虚拟机
3.1 对象创建
3.2 对象内存分布
存储内容 | 标志位 | 状态 |
对象哈希码、对象分带年龄 | 01 | 未锁定 |
3.3 对象的访问定位
四、OutOfMemoryError实例
-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
4.1 Java堆溢出
/** * VM args: -Xms20m -Xmx20m -XX:HeadDumpOnOutofMemoryError * @author ycy * */ public class HeapOOM { public static void main(String[] args) { List<String> testList=new ArrayList<>(); while(true) { testList.add("ddddd"); } } }出错信息:
public static List<Object> list;
在项目中不断在list增加数据,进行操作,4.2 虚拟机栈和本地方法栈溢出
栈存储局部变量表、操作数等,那么只要不断增加数据一直到内存溢出就可以实现。package com.ycy.java.outofmermory; /** * vm args: -Xss 128k * @author ycy *产生原因:堆栈空间太小、内存不足 */ public class JavaStackSOF { //初始化堆栈长度为1 private int stackLength=1; //堆栈溢出方法 public void stackLeak() { stackLength++; stackLeak(); } public static void main(String[] args) { JavaStackSOF oom=new JavaStackSOF(); try { oom.stackLeak(); }catch(Throwable e){ System.out.println("stackLength:"+oom.stackLength); throw e; } } }出错信息:
4.3 方法区和运行时常量池溢出
运行时常量池是方法区的一部分。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package com.paddx.test.memory;
import java.util.ArrayList;
import java.util.List;
public class StringOomMock
{
static String
base = "string" ;
public static void main(String[]
args) {
List<String>
list = new ArrayList<String>();
for ( int i= 0 ;i<
Integer.MAX_VALUE;i++){
String
str = base + base;
base
= str;
list.add(str.intern());
}
}
}
|
这段程序以2的指数级不断的生成新的字符串,这样可以比较快速的消耗内存。我们通过 JDK 1.6、JDK 1.7 和 JDK 1.8 分别运行:
JDK 1.6 的运行结果:
JDK 1.7的运行结果:
JDK 1.8的运行结果:
4.4 JDK 1.8元空间Metaspace
2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
4、Oracle 可能会将HotSpot 与 JRockit 合二为一
1、符号引用(Symbols)转移到了native heap; 例子:com.ycy.test
2、字面量(interned strings)转移到了java heap; 例子: string.intern()
3、类的静态变量(class statics)转移到了java heap 例子:Class元
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集
package com.ycy.java.outofmermory; import java.util.ArrayList; import java.util.List; /** * vm args: * -XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M -XX:PermSize=10M -XX:MaxPermSize=10M * tips:在jdk8中已经不会报permGen space 错误,因为1.8的jdk中已经没有永久代,而是报java heap space * @author ycy * */ public class RuntimeContantPoolOOM { static String base = "string"; public static void main(String[] args) { //使用List 保持着常量池的引用,避免Full Gc 回收常量池行为 List<String> list=new ArrayList<>(); int i=0; while(true) { String str = base + base; base = str; list.add(str.intern()); } } }输出结果:
package com.ycy.java.outofmermory; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * -XX:MetaspaceSize=2M -XX:MaxMetaspaceSize=2M -XX:PermSize=10M -XX:MaxPermSize=10M * @author ycy * */ public class JavaMethodAreaOOM { public static void main(final String[] args) { while(true) { Enhancer enhancer=new Enhancer(); //设置需要创建子类的类 enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache(false); //通过字节码技术动态创建子类实例 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { return methodProxy.invokeSuper(objects,args); } }); } } static class OOMObject{} }
执行结果:
4.5 本机直接内存溢出
/** * VM Args:-Xms20m -Xmx20m -XX:MaxDirectMemorySize=10m * @author ycy * */ import sun.misc.Unsafe; import java.lang.reflect.Field; public class DirectMemoryOOM { private static final int _1MB=1024*1024; public static void main(String[] args) throws Exception { Field unfield= Unsafe.class.getDeclaredFields()[0]; unfield.setAccessible(true); Unsafe unsafe=(Unsafe)unfield.get(null); while (true){ unsafe.allocateMemory(_1MB); } } }
错误信息:
4.6 出现内存异常一般情况
出现内存溢出一般情况
1.内存中加载的数据量过大。
比如一次性从数据库加载过多的数据。
2.并发数量太高。
并发数量太高,导致在短时间内创建大量的对象,GC也不及回收。
3.集合类中有无用对象的引用,使用完后没有立即清除。
集合类中的对象,如果不手动进行清除,GC不是不会对集合中无用的对象进行回收。
4.代码中存在死循环,递归,或者循环次数过多产生大量的对象。
5.方法区内存溢出。
方法区存放的是Class类型信息,类名,常量池,修饰符,方法描述等信息。
使用了过多的静态变量。常量池也被大量的占用。
jvm在“运行期间” 产生了大量的类。导致填满了方法区。比如使用反射,动态代理,字节码生成技术会在运行期间产生大量的类和类型信息。如hibernate,spring第三方框架大量使用了cglib技术产生大量的动态类。
大量的jsp在编译生成java类时也有可能产生方法区溢出,GC对方法区的回收非常苛刻的,因为对于一个类的回收条件就很严格。
6.启动时JVM内存参数设置过小。