(1)、局部变量表
局部变量表存放了编译器可知的基本数据类型和对象引用(reference 类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和 returnAddress 类型(指向了一条字节码指令的地址)。
局部变量表的大小在编译器就可以确定其大小了,因此在程序执行期间局部变量表的大小是不会改变的。
(2)、操作数栈
一个后进先出(Last-In-First-Out)的操作数栈,也可以称之为表达式栈(Expression Stack)。
操作数栈和局部变量表在访问方式上存在着较大差异,操作数栈并非采用访问索引的方式来进行数据访问的,而是通过标准的入栈和出栈操作来完成一次数据访问。
当然操作数栈所需的容量大小在编译期就可以被完全确定下来,并保存在方法的Code属性中。
可以通过 -Xss
这个虚拟机参数来指定每个线程的 Java 虚拟机栈内存大小:
java -Xss512M HelloTest
该区域可能抛出以下异常:
-
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出
StackOverflowError
异常。 -
如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出
OutOfMemoryError
异常。
3、本地方法栈
本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。
4、Java 堆
Java 堆是垃圾收集器管理的主要区域。
从内存回收的角度来看,由于现在收集器基本都采用分代收集算法。所以,Java 堆可细分为新生代和老年代;再细分一点的话新生代可细分为 Eden
区、From Survivor
区、To Survivor
区。
从内存分配的角度来看,线程共享的 Java 堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。
无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存。
可以通过 -Xms
和 -Xmx
两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。
java -Xms512M -Xmx512M HelloTest
5、方法区
用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
和堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常。
对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。
HotSpot 虚拟机把它当成永久代来进行垃圾回收。但是很难确定永久代的大小,因为它受到很多因素影响,并且每次 Full GC 之后永久代的大小都会改变,所以经常会抛出 OutOfMemoryError 异常。为了更容易管理方法区,从 JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中。
6、运行时常量池
运行时常量池是方法区的一部分。
Class 文件中的常量池(编译器生成的各种字面量和符号引用)会在类加载后被放入这个区域。
除了在编译期生成的常量,运行期间也可将新的常量放入池中,例如 String 类的 intern()。
7、直接内存
在 JDK 1.4 中新加入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存(Native 堆),然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。
这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
1、对象的创建
(1)、类加载检查
虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用, 并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。 如果没有,那必须先执行相应的类加载过程。
(2)、分配内存
在类加载检查通过后,接下来虚拟机将为新生对象分配内存。 对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择那种分配方式由 Java 堆是否规整决定, 而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
(3)、内存分配并发问题
对象的创建在虚拟机中是非常频繁的,即使是仅仅修改一个指针所指向的位置,在并发情况下也并不是线程安全的。解决这个问题通常有两种方案:
-
CAS+失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是, 每次不加锁而是假设没有冲突而去完成某项操作, 如果因为冲突失败就重试,直到成功为止。 虚拟机采用CAS配上失败重试的方式保证更新操作的原子性。
-
TLAB: 每一个线程预先在Java堆中分配一块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。 哪个线程要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才采用上述的CAS进行内存分配。
(4)、初始化零值
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头), 这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用, 程序能访问到这些字段的数据类型所对应的零值。
(5)、设置对象头
初始化零值完成之后,虚拟机要对对象进行必要的设置, 例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
(6)、执行< init >方法
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了, 但从 Java 程序的视角来看,对象创建才刚开始,< init >
方法还没有执行,所有的字段都还为零。 所以,一般来说(由字节码中是否跟随 invokespecial
指令所决定),执行 new 指令之后会接着执行 < init >
方法, 把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。
2、对象的内存布局
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
既已说到spring cloud alibaba,那对于整个微服务架构,如果想要进一步地向上提升自己,到底应该掌握哪些核心技能呢?
就个人而言,对于整个微服务架构,像RPC、Dubbo、Spring Boot、Spring Cloud Alibaba、Docker、kubernetes、Spring Cloud Netflix、Service Mesh等这些都是最最核心的知识,架构师必经之路!下图,是自绘的微服务架构路线体系大纲,如果有还不知道自己该掌握些啥技术的朋友,可根据小编手绘的大纲进行一个参考。
如果觉得图片不够清晰,也可来找小编分享原件的xmind文档!
且除此份微服务体系大纲外,我也有整理与其每个专题核心知识点对应的最强学习笔记:
-
出神入化——SpringCloudAlibaba.pdf
-
SpringCloud微服务架构笔记(一).pdf
-
SpringCloud微服务架构笔记(二).pdf
-
SpringCloud微服务架构笔记(三).pdf
-
SpringCloud微服务架构笔记(四).pdf
-
Dubbo框架RPC实现原理.pdf
-
Dubbo最新全面深度解读.pdf
-
Spring Boot学习教程.pdf
-
SpringBoo核心宝典.pdf
-
第一本Docker书-完整版.pdf
-
使用SpringCloud和Docker实战微服务.pdf
-
K8S(kubernetes)学习指南.pdf
另外,如果不知道从何下手开始学习呢,小编这边也有对每个微服务的核心知识点手绘了其对应的知识架构体系大纲,不过全是导出的xmind文件,全部的源文件也都在此!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
87EvoE-1713748803229)]
另外,如果不知道从何下手开始学习呢,小编这边也有对每个微服务的核心知识点手绘了其对应的知识架构体系大纲,不过全是导出的xmind文件,全部的源文件也都在此!
[外链图片转存中…(img-obg5r7Jg-1713748803229)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!