JVM故障诊断与性能优化--分析Java堆

找到内存溢出的原因

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 尹伟丞
 *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);
		}
	}
}

输出结果:

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());
		}
	}
}

输出结果:

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

使用visualVM打开







猜你喜欢

转载自blog.csdn.net/yinweicheng/article/details/80995112