天天看点

回顾《深入理解 Java 虚拟机》之 Java 内存区域

过年会把《深入理解 Java 虚拟机》回看一遍,整理下知识点

C/C++

的内存管理都在编码人员自己的手里进行控制,

delete/free

虽能让人感受到上帝视角的快感,却也加大了对编码人员的考验。

Java

Golang

Nodejs

等现代语言因为有虚拟机这层,所以将内存管理的工作移交给了虚拟机做。

JVM 运行时内存区域

回顾《深入理解 Java 虚拟机》之 Java 内存区域

每个区域的功能如下:

回顾《深入理解 Java 虚拟机》之 Java 内存区域

对象的创建

我们经常使用

new

来创建一个强引用的对象,那么

JVM

层面如何处理对象的创建流程呢?

首先,

JVM

会先根据这个指令的参数,看能否在常量池中定位到一个符号引用,并检查引用代表的类是不是已经被加载、解析、初始化过了,没有的话要执行类加载过程。类加载检查通过后,要为对象分配内存,如果采用了具有 “压缩-整理” 功能的

GC

,那么新生代内存区域是归整的(即用过的在一边,空闲的在另一边),因此分配内存只是简单地将临界指针向空闲区域移动一个对象的大小,这称为

指针碰撞

;如果是类似

标记回收

这种算法的

GC

,那么内存区域是块状的,能否找到合适的空闲内存依靠于

JVM

维护的空闲列表,这种方式称之为

空闲列表法

不同的

GC

的特点请见我之前的文章 Java 常见的垃圾收集器总结。

JVM

创建对象是频繁的过程,如何解决多线程情况下,分配内存时的冲突问题呢?一种方式是加锁强制同步,但是效率太低,另一种是

CAS

失败重试来保证原子性;还有一种是预分配,每个线程先预分配一块内存称为本地线程分配缓冲(

TLAB

),当

TLAB

用完时再选择新的

TLAB

(这时再加锁)。

对象的内存布局

回顾《深入理解 Java 虚拟机》之 Java 内存区域

对象访问

Java 程序需要通过栈上的引用数据来操作具体对象,而至于引用是如何去定位、访问堆中对象的,实际虚拟机实现时有两种方式:

回顾《深入理解 Java 虚拟机》之 Java 内存区域

两种访问方式各有优势,句柄访问方式最大的好处是引用中存放的是稳定的句柄地址,在对象被移动时,只会改变句柄中的实例数据指针,而引用本身不需要被修改。

而指针访问的最大优势是速度快,它节省了一次指针定位的开销,由于对象访问在

Java

程序中非常频繁,这类开销积少成多后也是一项非常可观的成本。

具体的访问方式都是有虚拟机指定的,虚拟机Sun HotSpot使用的是直接指针方式,不过从整个软件开发的范围来看,各种语言和框架使用句柄访问方式的情况十分常见。

举个例子说明

这段代码大家都很熟悉,假设这段代码出现在方法体中,那么

Object obj

部分的语义将会反映到

Java 栈

的本地变量表中,作为一个

reference

类型的数据存在。而

new Object();

部分的语义将在类加载过程后反应到

Java

堆和方法区中,前者形成一块存储

Object

类型所有实例数据值

(Instance Data)

的结构化内存,后者则存储

Object

类的对象类型数据。

参考资料

-《深入理解Java虚拟机》第二章,周志明著