整体分布:
虚拟机栈(本地方法栈和他一样滴)
线程独属滴,当执行java代码时,A线程会拥有一个自己的栈,A线程执行方法时,就会将方法做为栈帧压入A栈中,其中栈帧中,存放一个局部变量表,局部变量表中,存放了该方法中定义的局部变量(int,long等直接存放,引用类型存地址指针,指令的话就存字节码指令的地址),编译期间就确定大小。(肯定的啊,又不会存对象,大小自然是固定的),局部变量表中32位一个空间,如图所示,指令一步步被压入了栈中。栈深度过大,则抛出stackoverflow。(现在的虚拟机都支持动态扩展了,支持动态扩展的话就抛出OutofMemory异常),通过-XX:Xss:10m和-XX:XsX:10m设置每一个栈的大小
堆
就是个存储对象的地方。线程共享的。
执行到上图的时候就会建堆。
模拟异常,并打印堆栈:
虚拟机参数:
-Xms4m -Xmx4m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=G:\GIT\base
执行代码:
public class HeapOOM {
public class A{
private long a = 0L;
private int b = 0;
}
public static void main(String[] args) {
ArrayList<HeapOOM> a = new ArrayList<HeapOOM>();
while(true){
a.add(new HeapOOM());
}
}
}
执行结果:
下载Eclipse memory analyze 工具,打开该dump的文件。如图:分析是内存泄漏还是内存溢出。
请注意,当碰到oom异常时,请先打印堆栈,然后调整参数,因为堆栈(两种)+方法区+堆差不多等于虚拟机容量。如果因为线程过多(占用堆栈大小),则应减少堆栈大小,或者减小堆。
方法区
存储被虚拟机加载的类信息,常量,静态变量。编译器编译后的代码等数据。(Hotspot刚开始gc收集会僭越过来收集,并被gc当做永久代收集,主要收集常量池和类型卸载,虽然收集的令人不满意,但是还是要收集,后来逐步采用Native Memory实现,jdk7开始,),内存不足则抛出OutofMemory异常,常见的permGen space就是方法区溢出。可通过-XX:permSize和-XX:maxPermSize
模拟时可通过代理动态生成类
运行时常量池
是方法区的一部分,存放类信息中的常量,类加载完毕后,就会放入该地方。运行期间,新的常量有可能会被放入池中,String的intern()方法会用到该地方。一样会抛出OutofMemory异常,-XX:permSize和-XX:maxPermSize
顺便解释一下String的intern()方法。intern方法就是,常量池中存在的话,就返回常量池中的字符串,常量池中不存在的话,就将字符串放在常量池,然后返回常量池地址。
此处用 https://www.cnblogs.com/xys1228/p/6035480.html 该博客的例子。
博客举的例子:
// 1
String str1 = new StringBuilder("ja").append("va").toString();
System.out.println(str1.intern() == str1);
// 2
String str2 = new StringBuffer("编").append("程").toString();
System.out.println(str2.intern() == str2);
// 3
String str3 = new StringBuffer("编").append("程").toString();
System.out.println(str3.intern() == str3);
// 使用 JDK6 进行编译运行(字符串常量池就存字符串):
false, false, false
// 使用 JDK7 进行编译运行(字符串常量池存堆地址):
false, true, false
直接内存
Nio基于Native函数库直接分配堆外内存。并不属于虚拟机的内存,通过java堆中的DirectBuffer对象作为那块地方引用进行操作。避免在java堆和Native堆中来回复制数据。可通过-XX:MaxDirectMemberSize:10m设置大小,不设置就和Xmx大小相同。
java对象分配过程
java分配内存时。会给每个线程在堆中分配一个本地线程分配缓冲(TLAB),防止出现这种情况:A a = new A();另一个线程B b = new B(),堆中分配完A的内存:c地址,还没来得及将a指向c地址,b就以为那块内存是空闲的,就开始适用那片内存。TLAB就是防止那种情况的出现。-XX:+/-UseTLAB来决定。
分配完内存,就要初始化为0值。然后初始化对象头。然后初始化值。
对象在内存中存储=对象头+实例数据+对齐填充。
对象头=自身运行时数据(长度与虚拟机位数相同)+对象指向类元数据的指针。
32位情况下:自身运行时数据=25的哈希码+4对象分代年龄+1个固定位+2锁标志位。
实例数据就是java对象中的各种字段及继承的各种字段。
上图为java的两者引用方式