JVM

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

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

Posted by ChenJY on February 5, 2019 | Viewed times
本站图床基于新浪微博,图片加载异常请强制刷新或直接访问语雀空间查阅文章备份

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

C/C++ 的内存管理都在编码人员自己的手里进行控制,delete/free 虽能让人感受到上帝视角的快感,却也加大了对编码人员的考验。JavaGolangNodejs 等现代语言因为有虚拟机这层,所以将内存管理的工作移交给了虚拟机做。

JVM 运行时内存区域

每个区域的功能如下:

对象的创建

我们经常使用 new 来创建一个强引用的对象,那么 JVM 层面如何处理对象的创建流程呢?

首先,JVM 会先根据这个指令的参数,看能否在常量池中定位到一个符号引用,并检查引用代表的类是不是已经被加载、解析、初始化过了,没有的话要执行类加载过程。类加载检查通过后,要为对象分配内存,如果采用了具有 “压缩-整理” 功能的 GC,那么新生代内存区域是归整的(即用过的在一边,空闲的在另一边),因此分配内存只是简单地将临界指针向空闲区域移动一个对象的大小,这称为 指针碰撞;如果是类似 标记回收 这种算法的 GC,那么内存区域是块状的,能否找到合适的空闲内存依靠于 JVM 维护的空闲列表,这种方式称之为 空闲列表法

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

JVM 创建对象是频繁的过程,如何解决多线程情况下,分配内存时的冲突问题呢?一种方式是加锁强制同步,但是效率太低,另一种是 CAS 失败重试来保证原子性;还有一种是预分配,每个线程先预分配一块内存称为本地线程分配缓冲(TLAB),当 TLAB 用完时再选择新的 TLAB(这时再加锁)。

对象的内存布局

对象访问

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

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

而指针访问的最大优势是速度快,它节省了一次指针定位的开销,由于对象访问在 Java 程序中非常频繁,这类开销积少成多后也是一项非常可观的成本。

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

举个例子说明

       Object obj = new Object();

这段代码大家都很熟悉,假设这段代码出现在方法体中,那么 Object obj 部分的语义将会反映到 Java 栈 的本地变量表中,作为一个 reference 类型的数据存在。而 new Object(); 部分的语义将在类加载过程后反应到 Java 堆和方法区中,前者形成一块存储 Object 类型所有实例数据值(Instance Data)的结构化内存,后者则存储 Object 类的对象类型数据。

参考资料

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

License


Comment