找到内存溢出的原因
1.堆溢出
当对象的大小之和大于由Xmx指定的堆空间大小时,会发生溢出错误。
示例1--堆溢出(8G内存环境下)
public class SimpleHeapOOM {
public static void main(String[] args) {
ArrayList<byte[]> list = new ArrayList<byte[]>();
for(int i=0;i<4096;i++){
list.add(new byte[1024*1024]);
}
}
}
输出结果
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at chapter7.SimpleHeapOOM.main(SimpleHeapOOM.java:9)
为了缓解堆溢出的错误,一方面可以指定更大的堆空间,另一方面,由于堆空间的不断增长,可以通过堆分析工具找到占用堆空间的对象。
2.直接内存溢出
在NIO中,支持直接内存的使用,这块空间是直接向操作系统申请的。直接内存的申请速度比对内存慢。但是访问速度块。但是直接内存并没有被java虚拟机完全托管,若使用不当,也会导致溢出。
示例2--直接内存溢出
package chapter7;
import java.nio.ByteBuffer;
import java.util.ArrayList;
/***
* 直接内存溢出
* @author shiker
*jvm:-XX:+PrintGCDetails -XX:MaxDirectMemorySize=10M
*/
public class DirectBufferOOM {
public static void main(String[] args) {
ArrayList<ByteBuffer> list = new ArrayList<ByteBuffer>();
for(int i=0;i<1024;i++){
//将直接内存分配的对象放入list中防止被回收
list.add(ByteBuffer.allocateDirect(1024*1024));
System.out.println(i);
}
}
}
public static void main(String[] args) {
ArrayList<ByteBuffer> list = new ArrayList<ByteBuffer>();
for(int i=0;i<1024;i++){
//将直接内存分配的对象放入list中防止被回收
list.add(ByteBuffer.allocateDirect(1024*1024));
System.out.println(i);
}
}
}
输出结果:
0
1
2
3
4
5
6
7
8
9
[GC [PSYoungGen: 1331K->536K(38400K)] 1331K->536K(124416K), 0.0020488 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 536K->0K(38400K)] [ParOldGen: 0K->466K(86016K)] 536K->466K(124416K) [PSPermGen: 2566K->2565K(21504K)], 0.0122748 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:658)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:306)
at chapter7.DirectBufferOOM.main(DirectBufferOOM.java:14)
Heap
PSYoungGen total 38400K, used 1664K [0x00000007d5d00000, 0x00000007d8780000, 0x0000000800000000)
eden space 33280K, 5% used [0x00000007d5d00000,0x00000007d5ea00e0,0x00000007d7d80000)
from space 5120K, 0% used [0x00000007d7d80000,0x00000007d7d80000,0x00000007d8280000)
to space 5120K, 0% used [0x00000007d8280000,0x00000007d8280000,0x00000007d8780000)
ParOldGen total 86016K, used 466K [0x0000000781800000, 0x0000000786c00000, 0x00000007d5d00000)
object space 86016K, 0% used [0x0000000781800000,0x0000000781874a88,0x0000000786c00000)
PSPermGen total 21504K, used 2596K [0x000000077c600000, 0x000000077db00000, 0x0000000781800000)
object space 21504K, 12% used [0x000000077c600000,0x000000077c8893f8,0x000000077db00000
保证直接内存不溢出的方法是合理地进行fullGC的执行,或者设置一个系统可达的MaxDirectMemorySize的值(默认情况下等于xmx的值)。如果系统堆内存GC发生少,而直接内存使用频繁,会比较容易导致直接内存溢出。
3.过多线程导致OOM
由于每一个线程的开启也要占用系统内存,当线程数量较多时,也可能导致oom。
解决此问题的方法:一是减少堆空间,另一个方法是减少每一个线程所占用的内存空间(Xss)
4.永久区溢出
如果生成过多的类,或有太多的类型就会导致溢出
示例3--永久区的溢出
package chapter7;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class PermOOM {
static class OOMObject{
}
public static void main(String[] args) {
while(true){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2,
MethodProxy arg3) throws Throwable {
// TODO Auto-generated method stub
return arg3.invokeSuper(arg1, arg2);
}
});
enhancer.create();
}
}
}
输出结果---jdk1.8
Exception in thread "main"
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
在jdk1.8之后,永久区被保存到被元数据区替代,但是都是为了保存对象的元信息。
防止永久区溢出:增加maxpermsize的值,减少系统需要的类的数量,使用classloader合理装在各个类,并进行定期回收。
string的实现
1.string的特性
- 不可变:一旦定义不能改变(多线程中的不可变模式)
- 针对常量池的优化:值相同时,常量池中的引用相同。
- 类的final定义:没有子类。
2.string的内存泄漏
string对象是由char数组、偏移量、数组长度组成的,string的实际长度与value无关,这种结构存在内存泄漏的隐患。jdk1.7后去除了偏移量offset与数组长度count,这样string的实质性内容由value决定。
3.string常量池的位置
jdk1.6之前属于永久区的一部分,但是jdk1.7之后被移到堆中进行管理。
示例4--string常量池的位置
package chapter7;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author yinweicheng
*jvm:-Xmx5M -XX:MaxPermSize=5M
*
*/
public class StringInternOOM {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
int i = 0;
while(true){
list.add(String.valueOf(i++).intern());
}
}
}
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
int i = 0;
while(true){
list.add(String.valueOf(i++).intern());
}
}
}
输出结果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2245)
at java.util.Arrays.copyOf(Arrays.java:2219)
at java.util.ArrayList.grow(ArrayList.java:242)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)
at java.util.ArrayList.add(ArrayList.java:440)
at chapter7.StringInternOOM.main(StringInternOOM.java:11)
虽然string.intern()方法虽然返回字符串常量,但是系统不同时间的返回的常量值不一定相同。有可能在某一时刻被回收,在下一时刻又重新加入常量池。
示例5---不进行显示gc时字符串常量池的引用
package chapter7;
public class ConstantPool {
public static void main(String[] args) {
if (args.length == 0)
return;
System.out.println(System.identityHashCode((args[0] + Integer
.toString(0))));
System.out.println(System.identityHashCode((args[0] + Integer
.toString(0)).intern()));
// System.gc();
System.out.println(System.identityHashCode((args[0] + Integer
.toString(0)).intern()));
}
}
输出结果:
1730745465
973031640
973031640
进行显示GC时的输出结果:
973031640
654801575
1821413125
使用mat分析java堆
浅堆:一个对象结构所占用的内存大小。
深堆:是指对象的保留集中所有对象的浅堆大小之和。
保留集:指当对象A被垃圾回收后,可以被释放的所有对象的集合。
示例6--mat分析java堆
package chapter7;
public class WebPage {
private String url;
private String content;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
package chapter7;
import java.util.List;
import java.util.Vector;
public class Student {
private int id;
private String name;
private List<WebPage> history = new Vector<WebPage>();
public Student(int id, String name) {
super();
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<WebPage> getHistory() {
return history;
}
public void visit(WebPage history) {
this.history.add(history);
}
}
package chapter7;
import java.util.List;
import java.util.Vector;
/**
*
* @author yinweicheng
* jvm:-XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=D:/program2015/jvm/stu.hprof
*
*/
public class TraceStudent {
static List<WebPage> webpages = new Vector<WebPage>();
public static void createWebPages(){
for (int i = 0; i < 100; i++) {
WebPage we = new WebPage();
we.setUrl("http://www."+i+".com");
we.setContent(Integer.toString(i));
webpages.add(we);
}
}
public static void main(String[] args) {
createWebPages();
Student st3 = new Student(3, "billy");
Student st5 = new Student(5, "alice");
Student st7 = new Student(7, "taotao");
for (int i = 0; i < webpages.size(); i++) {
if(i%st3.getId()==0){
st3.visit(webpages.get(i));
}if(i%st5.getId()==0){
st5.visit(webpages.get(i));
}if(i%st7.getId()==0){
st7.visit(webpages.get(i));
}
}
webpages.clear();
System.gc();
}
}
用mat打开生成的stu.hprof后,获取线程视图如下:
通过出引用可以获得单个对象引用了哪些对象:
通过入引用可以查看哪些对象被改对象引用。
查看对象的支配树:
使用oql查询对象
1.select子句
//查看对象的保留集
select as retained set * from java.util.Vector v
//去除重复元素
select distinct * from java.util.Vector v
2.from子句
//通过类名搜索
select * from java.util.Vector v
//查看指定包下的实例
select * from "chapter7\..*"
//返回指定类的所有子类实例
select * from instanceof java.util.AbstractCollection
//返回该实例的类信息
select * from
objects java.util.AbstractCollection
3.where子句
//返回长度大于10的数组
select * from char[] s where s.@length >10
//模糊查询
select * from java.lang.String s where toString(s) like ".*java.*"
//不为空
select * from java.lang.String s where s.value != null