JVM会怎么分配内存???大多数人的回答是内存分为堆和栈,但实际上内存的分配比堆栈更严格
内存被划分为5个大区:程序计数器、本地方法栈、虚拟机栈、堆、方法区
程序计数器(线程独享) 当前线程所执行的字节码的行号指令器,通过改变计数器的值选取虚拟机下一条需要执行的字节码指令,分支、循环、跳转、异常、线程恢复等基础功能
虚拟机栈(线程独享) JAVA方法执行的内存模型,每个方法在执行的同时都会存建一个栈帧用于存储方法出口、局部变量表、操作数栈、动态连接等信息,局部表量表(大多数人认为的栈)用于存放可知各种数据类型(Boolean、byte、char、short、int、float、long、double)、对象引用类型和returnAddress(指向了一个字节码指令地址)
本地方法栈 与虚拟机栈的作用非常相似,它们之间的区别是虚拟机栈为虚拟机执行JAVA方法服务,本地方法栈为Native方法服务
方法区(线程共享) 被虚拟机加载的常量、静态量、方法描述、类信息等。JAVA虚拟机规范把方法区描述为堆的一个逻辑部分,但它还有一个别名叫做Non-Heep 运行时常量池:方法区的一部分,用于存放编译时的字面量、符号引用
堆(线程共享) 对象
在分配对象时,对象包括了三部分内容:对象头(存储自身运行数据、类型指针)、实例数据(对象真正有意义的数据)、对齐填充(并不是所有的JVM都有,以0填充)。
对象头中用于存储自身的运行数据部分:如哈希码、GC分代年龄、锁状态标志、线程持有锁、偏向线程ID、偏向时间戳等,官方称为Mark Word Mark Word 32位空间中的25位用于存储HashCode, 4位存储对象分代年龄,2位用于锁标志位,1位固定0
对象头中用于存储类型指针部分:即对象指向的类元数据指针,虚拟机通过这个指针来确定是这个对象是哪个类的实例。如果对象是一个JAVA数组,那在对象头中还必须有一块用于记录数组长度的数据,虚拟机可以通过普通JAVA对象的元数据确定JAVA对象大小,但是从数组的元数据中无法确定数组大小
实例数据:对象真正有效信息,也是程序代码中所定义的各种类型的字段内容。无论从父类继承还是子类定义的,都需要纪录。这部分的存储顺序受虚拟机分配策略影响,部分虚拟机分配为longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers)可以看出。相同宽度总是被分配到一起。
那么对象是如何被访问的?AVA程序需要通过栈上的reference数据来操作堆上的具体对象。由于reference类型在JAVA虚拟机中只规定了一个指向对象的引用,并没有规定如何访问,所以对像的访问方式取决于虚拟机的实现而定,访问的方式大体分为使用句柄访问和使用直接指针访问
使用句柄访问:JAVA堆中将会划分出一块内存作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体信息
使用直接指针访问: JAVA堆对象的存局中就必须考虑如何放置访问数据的相关信息,而reference中存储的直接就是对象地址
了解了JAVA的内存分配,现在我们来看看JAVA在什么时候会出现OOM
JAVA堆内存的OOM:实际应用中最常见的内存溢出,JAVA堆用于存储对象实例,不断创建新对象,而GC无法清除对象(GC Roots到对象之间有可达路径),那么当对象到大最大堆容量时就会产生堆内存溢出
首先我们用如下方式修改JVM内存设置:-Xms堆最小值,-Xmx堆量大值,-Xss虚拟机栈最小值
代码:
//在Eclipse中修改JVM内存
//-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
public class HeapOOM {
public static void main(String[] args) {
List<HeapOOM> list = new ArrayList<HeapOOM>();
while (true) {
list.add(new HeapOOM());
}
}
}
结果:java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid2004.hprof ... Heap dump file created [32572164 bytes in 0.448 secs]
虚拟机栈溢出和本地方法栈溢出:
如果线程请求的栈深度大于虚拟机允许的最大深度,则抛出StackOverflowError
如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError
在单线程环境下,无论由于栈帧太大还是虚拟机栈容量太小,当内存无法分配时都将抛出StackOverflowError
在多线程环境下则可抛出OutOfMemoryError
操作系统分配给每个进程的内存是有限制的,如32位windows限制为2GB。虚拟机提供了参数来控制JAVA堆和方法区这两部分的内存的最大值,那么虚拟机栈和本地方法栈所瓜分的内存则为2GB - Xmx(最大堆容量) - MaxPermSize(最大方法区容量)。 每个线程所能分配到的栈容量越大,线程数就越少,创建线程时越容易吧剩下内存耗尽 在虚拟机默认参数的大多数情况下 (由于每个方法压入栈的帧大小并不是一样的)栈深度能达到1000-2000的空间大小,对于正常方法调用(包括递归),这个深度完全够用,但是如果创建线程过多则会造成OutOfMemoryError,在不能减少内存数的或者更换64位系统的情况下,就只能通过减少最大堆和最少栈容量来换取更多的线程
代码:
//使用-Xss参数减少栈内存容量,以抛出StackOverflowError
//定义大量本地变量,增大此方法栈帧中本地变量表的长度,以抛出StackOverflowError
//VM -Xss128k
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak(){
stackLength++;
stackLeak();
}public static void main(String[] args) throws Throwable {
JavaVMStackSOF oom = new JavaVMStackSOF();
try{
oom.stackLeak();
}catch(Throwable e){
System.out.println("stack length :" + oom.stackLength);
throw e;
}
}
}
结果:
stack length :2401 Exception in thread "main"
java.lang.StackOverflowError
at demo.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)
at demo.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)
多線程環境下的內存溢出
// VM -Xss2m
public class JavaVMStackSOF {
private void dontStop(){
while(true){ } //循環目的讓線程保持不被回收的運行狀態
}public void stackLeakByThread(){
while(true){
Thread thread = new Thread(new Runnable(){
public void run(){
dontStop();
}
});
thread.start();
}
}public static void main(String[] args) throws Throwable{
JavaVMStackSOF oom = new JavaVMStackSOF();
oom.stackLeakByThread();
}
}
結果:系統假死
方法区运行时常量池溢出
前面提到方法区中有一块区域称为运行时常量池,字符串为常量,String中的intern()方法的作用是检测常量池中是否有一个等于此String的常量,如果有则返回这个字符串的对象,如果没有则将此String标注的字符串添加到常量池中,并且返回此String引用
通过-XX:PermSize和-XX:MaxPermSize限制方法区大小,从而间接限制常量池容量
// VM -XX:PermSize=10M -XX:MaxPermSize=10M
public class RuntimeConstantPool {
public static void main(String[] args) {
//将常量String放入list中避免full GC回收常量池
List<String> list = new ArrayList<String>();
int i = 0;
while(true){
list.add(String.valueOf(i++).intern());
}
}
}
结果:
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at demo.RuntimeConstantPool.main(RuntimeConstantPool.java:16)
这样创新的模式,值得你的选择!
蜗牛学院,只为成就更好的你!
还在等什么,赶快关注蜗牛学院官方微信,加入到蜗牛学院的大家庭中来吧!