Java内存模型

本文深入探讨Java运行时数据区的各个部分,包括方法区、虚拟机栈、本地方法栈、堆和程序计数器等核心概念。同时,详细介绍了对象的创建过程、内存布局和访问定位,以及如何通过JVM参数进行调优。最后,分析了常见的内存溢出问题及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


【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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值