一,运行时数据区域

1,Java虚拟机栈

  虚拟机栈是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都回创建一个栈帧用于存储局部变量表/操作数栈/动态链接/方法出口等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。

  局部变量表存放了编译期可知的各种基本数据类型,其中64位长度的long和double类型的数据会占用2个局部变量空间,其余数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

  如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机可以动态扩展,扩展时无法申请到足够的内存,就会跑出OutOfMemoryError异常。

2,Java堆

  堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

  Java堆是垃圾收集器管理的主要区域,因此很多时候也被称作GC堆。从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆还可以细分为:新生代和老年代。

  Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像磁盘空间一样。

  如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

3,方法区

  方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息/常量/静态变量/即时编译器编译后的代码等数据。

  方法区和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。

  当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

4,运行时常量池

  它是方法区的一部分。Class文件中除了有类的版本/字段/方法/接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池存放。

二,虚拟机对象

1,对象的创建

  a. 虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载/解析和初始化过。如果没有,那必须先执行相应的类加载过程。类加载检查过后,虚拟机为新生对象分配内存,相当于从Java堆中划出一块内存来。由于对象的大小在类加载完成后就可以确定,所以也就可以确定划分出的内存大小。

  b. 划分内存有两种方式:一是指针碰撞,这种方法只有在内存空间是绝对规整时才能用,内存空间被一个分界点指针分为已用区域和未用区域。给对象分配内存就相当于将分界点指针往未用区域挪动一段与对象大小相等的距离。二是空闲列表,这种方法适用于堆不规整的时候,虚拟机维护了一个列表,记录哪些内存区域可用,然后从列表中找到一块足够大的空间划分出与对象大小相等的一块区域,然后更新列表上的记录。Java内存是否规整取决于垃圾收集器是否有压缩整理功能,如果采用压缩整理算法,系统采用的分配算法就是指针碰撞,如果采用标记清除算法,通常采用空闲列表。

  在并发情况下分配内存并不是线程安全的,有可能正在给A分配内存,指针还没改,对象B又同时使用了原来的指针。解决线程安全的方法有两种:一是对分配内存空间的动作进行同步处理--虚拟机采用CAS + 失败重试的方法保证更新操作的原子性。二是把内存分配的动作按照线程划分在不同的空间中进行,也就是说每个线程在Java堆中预先分配一小块内存,成为本息线程分配缓冲(Thread Local Allocation Buffer)。哪个线程要分配内存,就在它的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。虚拟机是否使用TLAB,可以用--XX:+/-UseTLAB参数设定。

  c. 虚拟机将分配到的内存空间都初始化为零值,这一步保证了对象的实例字段可以不赋初值就可以使用。

  d. 虚拟机对对象进行必要的设置,例如这个对象是哪个类的实例,如何找到类的元数据信息,对象的hash,对象的GC分代年龄等信息。这些信息存放在对象的对象头中。

  e. 执行对象的<init>方法,把对象按照程序员的意愿进行初始化。

一个真正的对象就算完全产生出来了。

2, 对象的内存布局

  对象在内存中存储的布局可以分为3块区域:对象头(Header)/实例数据/对齐填充。

3,对象的访问定位

  Java程序通过栈上的reference数据来操作堆上的具体对象,目前主流的访问方式有两种:使用句柄和直接指针。

  使用句柄方式:

好处是reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改。

  使用直接指针:

好处是速度更快,因为它节省了一次指针定位的时间开销。

三,OutOfMemory异常

1,Java堆溢出

  Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量达到最大堆的容量限制后就会产生内存溢出异常。

  将堆的最小值-Xms参数与最大值-Xmx参数设置为一样就可以避免堆自动扩展。通过参数-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出异常时Dump处当前的内存堆转储快照以便事后进行分析。

package com.ivy.vm;import java.util.ArrayList;
import java.util.List;/*** VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError* @author ivy**/
public class HeapOOM {static class OOMObject {}public static void main(String[] args) {// TODO Auto-generated method stubList<OOMObject> list = new ArrayList<>();while(true) {list.add(new OOMObject());}}}

结果:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid14664.hprof ...
Heap dump file created [27606448 bytes in 0.147 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat java.util.Arrays.copyOf(Arrays.java:3210)at java.util.Arrays.copyOf(Arrays.java:3181)at java.util.ArrayList.grow(ArrayList.java:261)at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)at java.util.ArrayList.add(ArrayList.java:458)at com.ivy.vm.HeapOOM.main(HeapOOM.java:21)

2, 虚拟机栈和本地方法栈溢出

  栈容量只由-Xss参数设定。在Java虚拟机规范中描述了两种异常:

  如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

package com.ivy.vm;/*** VM args: -Xss128k* @author ivy**/
public class JavaVMStackSOF {private int stackLength = 1;public void stackLeak() {stackLength++;stackLeak();}public static void main(String[] args) {// TODO Auto-generated method stub
JavaVMStackSOF oom = new JavaVMStackSOF();try {oom.stackLeak();} catch (Throwable e) {System.out.println("stack length:" + oom.stackLength);throw e;}}}

结果:

stack length:980
Exception in thread "main" java.lang.StackOverflowErrorat com.ivy.vm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:13)at com.ivy.vm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)at com.ivy.vm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)....

实验结果表明:在单个线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverflowError异常。

  上边的实验是在单线程情况下进行的,如果在多线程下就会产生内存溢出异常,但这样产生的内存溢出异常与栈空间是否足够大没有任何联系,在这种情况下,为栈分配的内存越大,反而越容易产生内存溢出异常。原因是:操作系统分配给每个进程的内存是由限制的,假设为2GB。虚拟机提供了参数来控制Java堆(Xmx)和方法区(MaxPermSize)的最大值,由于程序计数器消耗内存很小,可以忽略,再假设虚拟机进程本身耗费的内存不计算在内,那剩余的内存= 2GB - Xmx - MaxPermSize,剩余这部分内存就被虚拟机栈和本地方法栈瓜分了。每个线程分配到的栈容量越大,可以建立的线程数量自然就越少,建立线程时就越容易把剩下的内存耗尽。所以要在开发多线程的应用时要注意。

  出现StackOverflowError异常可以阅读错误堆栈找到问题所在。而且,如果使用虚拟机默认参数,栈深度在大多数情况下达到1000--2000完全没有问题,对于正常的方法调用(包括递归),这个深度应该够用了。但是如果是建立过多线程导致的内存溢出,在不能减少线程数或者更换64位虚拟机的情况下,就只能通过减少堆的最大值和减少栈容量来换取更多的线程了。

转载于:https://www.cnblogs.com/IvySue/p/7651870.html

Java虚拟机 --- 内存区域相关推荐

  1. 携程面试官问我怎么划分 Java 虚拟机内存区域,相见恨晚!

    看完记得一键三连哦,微信搜索[沉默王二]关注这个沉默但有点东西的小丑. 今天的标题绝非标题党,看下面这幅截图就明白了,读者真真的留言~ 在谈 JVM 内存区域划分之前,我们先来看一下 Java 程序的 ...

  2. Java虚拟机内存区域---学习笔记

    Java虚拟机 虚拟机: 定义:模拟某种计算机体系结构,执行特定指令集的软件. 种类: 系统虚拟机(Virtual Box .VMware) 进程虚拟机(JVM.Adobe Flash Player. ...

  3. jvm学习笔记(1)——java虚拟机内存区域

    一.java内存区域: 1.程序计数器(线程私有): 内存中较小的内存空间,可以当做当前线程所执行字节码的行号指示器.如分支.循环.跳转.异常处理.线程恢复都需要依赖这个计数器完成. 2.java虚拟 ...

  4. (二)Java虚拟机内存区域与内存溢出异常

    文章目录 在类加载完之后,JVM会做什么? 一.内存区域 1.运行时区域 二.HotSpot 虚拟机对象 1.对象的创建 2.对象的内存布局 3.对象的访问定位 三.内存溢出(OOM) 1.堆溢出 2 ...

  5. JAVA虚拟机 安全区域_Java虚拟机的内存区域

    2020年12月10日 阅读 186 关注 Java虚拟机的内存区域 最近在看<深入理解Java虚拟机>,故此写下自己的学习笔记. JVM 运行时数据区域 Java 虚拟机在执行 Java ...

  6. 【Java 虚拟机原理】垃圾回收算法 ( Java 虚拟机内存分区 | 垃圾回收机制 | 引用计数器算法 | 引用计数循环引用弊端 )

    文章目录 一.Java 虚拟机内存分区 二.垃圾回收机制 三.引用计数器算法 ( 无法解决循环引用问题 ) 一.Java 虚拟机内存分区 Java 虚拟机内存分区 : 所有线程共有的内存区域 : 堆 ...

  7. 【Java 虚拟机原理】JDK 体系结构 | Java 源码运行原理 | Java 虚拟机内存

    文章目录 一.JDK 体系结构 二.Java 源码运行原理 三.Java 虚拟机内存结构 一.JDK 体系结构 JDK 体系结构 : 下图所有的内容都是 JDK 体系中的组成元素 ; Java Lan ...

  8. 【Android 内存优化】Java 内存模型 ( Java 虚拟机内存模型 | 线程私有区 | 共享数据区 | 内存回收算法 | 引用计数 | 可达性分析 )

    文章目录 一. Java 虚拟机内存模型 二. 程序计数器 ( 线程私有区 ) 三. 虚拟机栈 ( 线程私有区 ) 四. 本地方法栈 ( 线程私有区 ) 五. 方法区 ( 共享数据区 ) 1. 方法区 ...

  9. java 虚拟机_浅谈Java虚拟机内存区

    1. Java 虚拟机内存区概述 我们在编写程序时,经常会遇到OOM(out of Memory)以及内存泄漏等问题.为了避免出现这些问题,我们首先必须对JVM的内存划分有个具体的认识.JVM将内存主 ...

  10. Java虚拟机内存的代的划分

    一 Java虚拟机为什么需要分代 # 如果每次都对整个堆空间进行垃圾回收,花费的时间肯定较长 # 不同的对象生命周期不一样,如果每次垃圾回收这些周期的较长的也都去遍历一下,显然没有意义. 所以实现分而 ...

最新文章

  1. 如何解决get和post乱码问题?
  2. Ajax的优缺点以及异步和同步的区别
  3. 手把手教你拦截Linux系统调用
  4. 十大经典数据挖掘算法:EM
  5. datatables 更新选中行 的一行数据
  6. SPI单片机发送ARM接收
  7. linux tcp连接计算机,计算机基础知识——linux socket套接字tcp连接分析
  8. 自适应HTML5宽屏物流运输快递货运类网站源码 pbootcms模板
  9. 点击home键_iPhone小技巧:无Home键iPhone11如何强制重启?
  10. “Null 是价值十亿美元的错误!”
  11. 关于《中国图象图形学报》论文录用经历
  12. 《Go圣经》章三:基本数据
  13. python各种源码下载
  14. STM32单片机bootloader扫盲
  15. 人脸识别、活体检测、人脸识别面临的挑战
  16. 知识付费资源变现小程序源码+可开流量主/带教程
  17. 计算机系统存储器 分类,存储器的分类
  18. metro样式开机启动菜单_如何在Windows 8中获取Metro风格的开始菜单和开始按钮
  19. 微信扫码提示在浏览器中打开的遮罩代码
  20. OA课程--word2013实用技巧大全-目录

热门文章

  1. Django中ORM对数据库的增删改查操作
  2. 「Algospot」量化QUANTIZE
  3. 【codeforces 534B】Covered Path
  4. 第二百三十二节,Bootstrap排版样式
  5. iOS常用---NSString,NSMutabuleString
  6. XML参考 :XmlReader 详解、实例(3)-- 读取XML节点和属性名称
  7. 读取topic数据存储到文件内
  8. 账户余额“蒸发”暴露网银安全哪些漏洞?
  9. 带你看JDK源码之HashMap
  10. Dubbo集成Spring与Zookeeper实例