JVM学习--内存区域

运行时数据区域:

Java虚拟机在执行Java程序的过程中把它管理的内存划分为若干个不同的数据区域

方法区 堆 虚拟机栈 本地方法栈 程序计数器

 

程序计数器

程序计数器是一块较小的内存空间,是当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,比如分支、循环、跳转、异常处理、线程恢复等都需要依靠这个计数器来完成

Java的多线程是通过线程轮流切换并分配处理器执行时间的方式实现,在任何一个确定的适合,一个处理器都只会执行一条线程中的指令,为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,各个线程之间的计数器互不影响,独立存储,所以这部分的内存区域是线程私有的

线程在执行一个Java方法,计数器记录是正在执行的虚拟机字节码指令的地址,如果是执行Native方法,计数器值为空

 

Java虚拟机栈

Java虚拟机栈的生命周期和线程相同,描述的是Java方法执行的内存模型,这部分也是线程私有的

每个Java方法在执行的同时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息

每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程

 

局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)

和引用对象(reference类型,它不等于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他和此对象有关的位置) 和returnAddress类型(指向了一条字节码指令的地址)

 

除了64位长度的long 和double 类型的数据会占用2个局部变量空间(Slot),其他数据类型会占用1个

局部变量表的内存空间是在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的

局部变量空间是完全确定的,在方法运营期间不会改变局部变量表的大小

 

关于栈溢出

两种异常情况:

1)线程请求的栈深度大于虚拟机允许的最大深度,抛出StackOverflowError

2)虚拟机在扩展栈的时候,无法申请到足够的内存空间,抛出OutOfMemoryError

-XX:Xss  设置栈的内存容量

-XX:Xmx 设置最大的堆容量

-XX:MaxPermSize 设置最大的方法区容量

操作系统分配给每个进程的内存是有限制的,32位的Windows操作系统限制为2G,那么就是2G减去Xmx,再减去MaxPermSize,程序计数器消耗内存很小,可以忽略不计,剩下的内存是由虚拟机站栈和本地方法栈瓜分,每个线程分配到的越大,可以建立的线程数就越少,建立线程时就越容易把剩下的内存耗尽,在开发多线程的应用时,出现StackOverflowError,一般可以通过异常堆栈信息中找到问题,在使用默认的虚拟机配置中,栈的深度在1000-2000完全么有问题,每个方法压入栈的帧大小不一样,但是通常情况下,对于正常的方法调用,这个深度是完全够用了,如果是建立的过多线程导致的内存溢出,在不能减少线程数的情况下,只能通过减少最大堆内存和减少栈容量来换取更多的线程

 

方法区和允许时常量池溢出

从JDK1.7开始,逐步“去永久代”,运行时常量池是方法区的一部分

String.intern()是一个Native方法,作用是:如果字符串常量池中包含了一个等于此String对象的字符串

则返回常量池中这个字符串的String对象,否则,则将此String对象中包含的字符串添加到常量池中

并且返回此String对象的引用,在JDK1.6及之前的版本中,常量池分配在永久代内,可以通过-XX:PermSize 和-XX:MaxPermSize设置大小

public static void main(String[] args) {
        String str1 = new StringBuilder("bei").append("jing").toString();
        System.out.println(str1.intern() == str1);
        String str2 = new StringBuilder("ja").append("va").toString();
        System.out.println(str2.intern() == str2);
    }

在JDK1.6中,intern方法会把首次遇到的字符串复制到永久代中,返回的也是永久代中这个字符串实例的引用,StringBuilder创建

的字符串实例在Java堆上,所以返回的不是一个引用,所以在JDK1.6上运行返回的是 false false

在JDK1.7中,intern方法不会再复制实例,只是在常量池中记录首次出现的实例引用,因此返回的引用和StringBuilder创建的那个字符串实例是同一个,但是“java” 这个字符串在执行StringBuilder之前出现过,字符串常量池中已经有它的引用了,所以不符合首次出现原则,所以在JDK1.7上运行返回的是 true false

 

posted on 2020-02-29 21:39  Flower2021  阅读(96)  评论(0编辑  收藏  举报