第一章 JAVA内部区域与内存溢出异常
一、前言
二、虚拟机内存划分
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内存参数设置过小。
一、前言
二、虚拟机内存划分
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内存参数设置过小。