Java虚拟机之内存

768 阅读4分钟

本文内容来自《深入理解Java虚拟机》,主要是自身学习,用于记录重点,方便回忆,复习。对应《深入理解Java虚拟机》第二章。

Java虚拟机中内存区域

程序计数器(线程私有):可认为是线程执行字节码的行号指示器,通过该计数器的值,可知道线程下一条需要执行的字节码。循环、异常处理、线程恢复都需要依赖该计数器来完成。没个线程都一个独立程序计数器,各线程计数器互不影响独立存储,为“线程私有”。

Java虚拟机栈(线程私有):通程序计数器,为“线程私有”,生命周期与线程相同。每个方法执行都会创建栈帧,用于存储局部变量表、操作数栈等信息。局部变量表存放编译期可知的基本数据类型,对象引用。可能会出现两种异常:StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度(如递归深度过深),OutOfMemoryError(扩展时无法申请到足够内存)。

本地方法栈(线程私有):与虚拟机栈作用类似,区别在于该区域是用于native本地方法。

Java堆(线程共享):虚拟机启动时创建,用于存放对象实例,垃圾收集器管理的主要区域,也称为“GC堆”,可处于物理上不连续的内存空间中,逻辑上是连续的即可。无法扩展内存时,会抛出OutOfMemoryError。

方法区(线程共享):用于存储被虚拟机加载的类信息,常量、静态变量。可能抛出OutOfMemoryError。

常量池:方法区的一部分,用于存放编译时生成的字面量、符号引用。可能会抛出OutOfMemoryError。

直接内存:NIO中引入,通过native函数库分配堆外内存,通过存储在堆中的DirectByteBuffer对象对这块内存进行操作。不受Java堆大小限制,编码Java堆与Native中来回复制数据。

对象的创建

一、内存分配方法

1、假设Java堆中内存是绝对规整,用过的内存放一边,空闲的内存放另一边,中间放一个指针作为分界线的指示器,为对象分配内存就仅仅是把指针向空闲空间那边挪动一段与内存大小相等的距离,这种分配方法称为“指针碰撞”

2、如果内存不规整,虚拟机即需维护一个列表,记录哪块内存可用,分配时从列表中找出一块足够大内存划分给对象,并更新列表,称为“空闲列表”。

二、并发处理方案

A线程读取指针分配内存还未结束,B线程读取旧指针分配内存,即会出现问题。

1、对内存空间分配动作进行同步处理,虚拟机上采用CAS,失败重试保证更新原子性。

2、把内存分配的动作按照线程划分在不同的空间进行。即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲。使用该方式,可通过-XX:+/-UseTLAB设定。

三、对象内存布局

分为3块区域:对象头+实例数据+对齐填充

对象头分类两部分:1、对象运行时数据:哈希码、GC分代年龄、锁状态标志等。32位虚拟机下为32bit,64位虚拟机下为64bit。2、类型指针,指向它的类元数据的指针,通过该指针确定对象是哪个类的实例。

实例数据:从父类中继承的字段、类中定义的字段

对齐填充:对象其实地址必须是8字节的整数倍,如果对象头是8字节的1或2倍数,如果实例数据不是8字节的整数倍,继续填充。

访问对象:

1、通过引用访问对象 A a = new A(); a.getB():变量 --->引用池(句柄池,Java堆中)--->   对象

2、直接指针   new A().getB():                         ----->  对象

参数:

-Xms20m    设置堆初始化内存20m。

-Xmx20m   设置堆最大内存20m。

-Xss            设置栈的大小。

-XX:MaxDireMemorySize   指定最大直接内存。

-XX:+HeapDumpOnOutMemoryError  让虚拟机出现内存益处时Dump出当前内存堆转存快照,便于事后分析。