Java内存区域

运行时数据区域:
程序计数器
通过改变计数器的值来选取下一条字节码指令,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,即“线程私有”如果线程执行的是Java方法,那么计数器记录的是正在执行的虚拟机字节码指令的地址,如果是native方法,则计数器的值为空,此区域没有OutOfMemoryError情况。
Java虚拟机栈
线程私有,描述Java方法执行的内存模型,每个方法都会创建栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等。。方法调用直至完成,对应栈帧入栈到出栈。局部变量表内存空间在编译期间完成分配,运行期间不会改变其大小。线程请求栈的深度大于所允许的深度,则抛出StackOverflowError异常;如果扩展时无法申请到足够的内存,就抛出OutOfMemoryError异常。
设置本地方法栈大小-Xoss(其实无效,应使用后面的),栈容量只由-Xss设定。
单个线程下,抛出的都是StackOverflowError。
如果是建立过多线程导致的内存溢出,可以通过减少最大堆和减少栈容量(每个线程被分配到的栈大小)来换取更多的线程。
本地方法栈
执行Native方法,会有StackOverflowError和OutOfMemoryError异常。
Java堆
jvm中最大的一块,线程共享,唯一目的是存放对象实例。垃圾收集器的主要管理区域,当在堆中没有内存完成实例分配,且堆无法再扩展时,将会抛出OutOfMemoryError异常。异常信息为java.lang.OutOfMemoryError :  java heap space
堆的最小值-Xms,最大值-Xmx
方法区
线程共享,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。JDK1.7之前称为“永久代”,1.7及以后被“元空间”替代。当方法去无法满足内存分配需求,抛出OutOfMemoryError。在经常动态生成大量Class的应用中,要注意类的回收,否则可能导致方法区内存溢出。
通过-XX:PermSize和-XX:MaxPermSize限制方法区大小。
运行时常量池
存放编译期生成的各种字面量和符号引用,常量并非只有编译期能产生,在运行期间可能将新的常量放入常量池中,String的intern()方法便利用此特性。对于intern()的解释:1.如果常量池中存在该内容的String,则返回该串引用;2.如果不存在,则创建该String,返回引用。JDK1.7中常量池在方法区中,1.8后在堆中。
再jdk1.6中,intern()方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久带中该字符串实例的引用。
在jdk1.7中,intern()方法不会再复制实例,只是在常量池中记录首次出现的实例引用。
直接内存:
OutOfMemoryError。
DirectMemory容量可以通过-XX:MaxDirectMemorySize制定,如果不指定,则默认与Java堆最大值(-Xmx)一样。
由DirectMemory导致的内存溢出,明显特征是Heap Dump文件中不会看见明显的异常。即如果OOM后Dump文件很小,程序直接或间接使用了NIO,则考虑是DirectMemory。
 
jdk1.8内存图
虚拟机中的对象
对象的创建:new指令->检查常量池及是否加载初始化过->分配内存->保证线程安全->初始化为0->设置对象头->执行<init>方法
对象所需的内存大小在类加载完成后便可完全确定。
为对象分配内存的两种方式
  1. 指针碰撞:堆中内存绝对规整,用过的内存和空闲的内存放在分界点指针两边。 
  2. 空闲列表:堆中内存不规整,虚拟机维护一个列表,记录那些内存块可用,在分配时找到足够打的空间划分给实例。
保证线程安全两种方式
  1. 对分配内存空间的动作进行同步处理,采用CAS配上失败重试的方式保证更新操作的原子性。
  2. 把内存分配的动作按照线程划分在不同的空间之中进行,每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲。
对象的内存布局
  1.对象头
    1.运行时数据(Mark Word):如哈希码、GC分代年龄、锁状态标志、线程持有的锁等等,是非固定大小
    2.类型指针:对象指向它的类元数据的指针,通过该指针可以确定这个对象是哪个类的实例。
  2.实例数据:父类继承的和子类中定义的都在里面,相同宽度的字段总是分配到一起,在此前提下,父类定义变量出现在子类之前。
  3.对齐数据:不是必然存在,对象大小必须是8字节的整数倍,对象头部分正好是8的倍数,故当实例数据部分没有对齐时,需要通过对齐填充来补全。
对象的访问定位
通过句柄访问:在Java堆中划出一块内存作为句柄池,Java栈里面的变量指向句柄地址,句柄指向Java堆中的对象实例数据和方法区中的对象数据类型。优点:在对象被移动时只改变句柄的实例数据指针,不需要改变Java栈变量指针。
通过直接指针访问:(jvm使用)Java栈里面的变量直接指向Java堆中的对象实例数据,对象实例数据中的指针指向方法区中的对象类型数据。优点:速度快,因为节省了一次指针定位的时间开销。
类的静态变量存储在方法区,实例变量存储在堆中。
原文地址:https://www.cnblogs.com/qulingyuan/p/10877355.html