1. 运行时数据区域
1. 程序计数器(线程私有)
当前线程所执行的字节码的行号指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都要考程序计数器。(记住程序当前走到的位置,下次还回来)。线程私有。
2. Java虚拟机栈(线程私有)
和方法相关联,每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程
线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常(常见的递归层数过多导致”爆栈“)
3. 本地方法栈(线程私有)
类似于Java虚拟机栈。区别在于:
虚拟机栈为虚拟机执行Java方法本地方法栈则为虚拟机使用到的Native方法服务
Java开头通过Java Native Interface来调用本地方法(一般用C语言编写)
4. Java堆(线程共享)
Java虚拟机所管理的内存中最大的一块new出来的对象就存在于堆上垃圾收集器管理的主要区域
5. 方法区(线程共享)
存储已被虚拟机加载的类信息、常量、静态变量
6. 运行时常量池
方法区的一部分用于存放编译期生成的各种字面量和符号引用
2. 对象是如何创建的?
Object obj=new Object()
: 分析这行代码的执行过程
使用了new关键字,检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。没有的话先加载类类加载检查通过后,虚拟机将为新生对象分配内存内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值虚拟机要对对象进行必要的设置,例如将对象的哈希码、元数据、GC分代年龄、是否使用偏向锁等数据存放在对象头中执行init方法,把对象按照程序员的意愿进行初始化(给成员变量赋的值)
3. 对象的内存布局
4. 对象的访问定位
1. 句柄访问
Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息
2. 直接指针访问
Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址
二者比较:
使用句柄来访问的最大好处就是reference中存储的是稳定的句柄地址,即使对象被移动(GC过程),只需要改变句柄中的示例指针,无需变动refrence直接指针访问方式的最大好处就是速度更快, refrence直接指向实例数据,减少了一次指针访问
HotSpot虚拟机使用直接指针方式进行对象访问的
5. OutOfMemoryError异常代码演示
1. Java堆溢出
package jvm;
import java.util.ArrayList;
import java.util.List;
public class OutOfMemoryErrorDemo {
static class MyObject{
}
public static void main(String[] args) {
List<MyObject> list=new ArrayList<>();
int i=0;
while(true)
{
System.out.println(i++);
list.add(new MyObject());
}
}
}
限制Java堆的大小为20MB,不可扩展(将堆的最小值-Xms参数与最大值-Xmx参数设置为一样即可避免堆自动扩展),通过参数-XX:+HeapDumpOnOutOfMemoryError
可以让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照以便事后进行分析
2. 虚拟机栈溢出
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常
package jvm;
import java.util.ArrayList;
import java.util.List;
public class OutOfMemoryErrorDemo {
static int i=0;
public static void main(String[] args) {
f();
}
public static void f() {
System.out.println(i++);
f();
}
}
相同的Xss(线程的堆栈大小)下,如果栈中的本地数据较多,那么相应的可以递归的次数就越少
还有1种栈溢出会报OutOfMemoryError异常,如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常,比如将上面的递归改成多线程版就会出现这种问题
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注编程网的更多内容!