【Java运行时数据区】方法区,虚拟机栈,本地方法栈,堆,程序计数器
执行引擎 —> 本地接口库 —> 本地方法库
程序计数器:当前线程执行字节码的行号指示器;每个线程都有一个独立的程序计数器,“线程私有”内存区域;
虚拟机栈:线程私有,描述Java方法执行的内存模型:栈帧。栈帧在虚拟机栈中入栈到出栈;
本地方法栈:为虚拟机使用到的Native方法服务;
Java堆(GC堆):所有线程共享,存放几乎所有对象实例(栈上分配、标量替换优化技术导致特例);垃圾收集器采用分代收集算法,Java堆细分为新生代和老年代;
方法区:内存共享区域,存储类信息、常量、静态变量和编译后的代码;HotSPot程序员称之为“永久代”;
运行时常量池:方法区的一部分,动态性。
【对象的创建过程】
1. 遇到new指令,检查指令参数在常量池中定位到类的符号引用;
2. 检查类有没有被加载、解析、初始化;若没有执行类的加载过程。
3. 为类分配堆内存,若堆是规整的,分配方式:“指针碰撞”;
若堆不规整,分配方式:“空闲列表”;(由收集器是否带有压缩整理功能决定)
PS:为保证分配内存时线程安全,要保证分配内存的原子性;或者给每个线程预留一个缓冲区。
4. 虚拟机对对象进行设置,例如类的实例、元数据信息、哈希码等等;
【对象的内存布局】对象头、实例数据、对象填充。
对象头:存储对象自身的运行数据(哈希码,GC分代、锁状态)和指向元数据的类型指针;
实例数据:存储顺序受到虚拟机分配策略参数和字段在Java源码定义顺序的影响。相同宽度的数据分配在一起,父类中定义的变量出现在子类之前。
对象填充:对象的大小必须是8的倍数。
【对象的访问定位】使用栈上的reference数据。
主流的访问方式:使用句柄和直接指针。
句柄访问:Java堆中划出空间作为句柄池,reference存放对象的句柄地址;好处 —> 对象在移动的时候只改变句柄中的实例指针,不改变reference;
指针访问:reference直接存放对象地址;好处 —> 速度更快。
【JVM参数设置】
-Xms是设置内存初始化的大小,Java堆的大小;
-Xmx是设置最大能够使用内存的大小(最好不要超过物理内存大小);
-Xmn是Java堆中新生代的大小(剩余空间属于老年代);
-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;
-XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。
-Xss是设置栈容量;
-Xoss是设置本地方法栈大小,实际无效;
-XX:MaxDirectMemorySize指定本机直接内存容量,默认与-Xmx一样;
例如:在eclipse.ini中修改,
-vmargs-Xms128M -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=128M
【Java堆溢出】内存泄漏和内存溢出
内存泄漏指你用malloc或new申请了一块内存,但是没有通过free或delete将内存释放,导致这块内存一直处于占用状态;在Java中,当被分配的对象可达但已无用(未对作废数据内存单元的引用置null)即会引起。
内存溢出指你申请了10个字节的空间,但是你在这个空间写入11或以上字节的数据,就是溢出。
内存泄漏通过优化代码解决;内存溢出通过设置JVM参数解决。
public class HeapOOM {
static class OOMObject{
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while(true) {
list.add(new OOMObject());
}
}
}
【栈溢出】
StackOverflowError:线程请求的栈深度大于最大深度;
OutOfMemoryError:扩展栈时无法申请到空间;
操作系统分配的进程大小为2GB,除去GC堆和方法区就是栈空间。每个运行的线程都会消耗栈容量;
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength ++;
stackLeak();
}
public static void main(String[] args) {
JavaVMStackSOF oom = new JavaVMStackSOF();
try {
oom.stackLeak();
}catch(Throwable e) {
System.out.println("栈深度:" + stackLength);
throw e;
}
}
}
【方法区和常量池溢出】
常量池是方法区的一部分。可以通过-XX:PermSize限制方法区大小,从而限制常量池大小。
常量池溢出实例代码:
public class RunTimeConstantPoolOOM {
public static void main(String[] args) {
//使用List保持常量池引用,避免GC回收常量池行为
List<String> list = new ArrayList<String>();
int i = 0;
while(true) {
list.add(String.valueOf(i++).intern());
}
}
}
【本地直接空间溢出】
明显特征:Heap Dump文件很小,程序直接或间接使用NIO。