jvm学习——java内存区域划分——丑九怪
写在前面
对于java语言来说,相对于c、c++最大的区别可能就是在内存方面了,c、c++直接将内存交给了编程者,让其对所用的每字节空间“负全责”,而jvm提供了一整套自己管理自己的机制,虽然方便了编程者,但同时造成了隐患,如果编程者无法从内存角度清楚地认识到整个程序的执行过程,那么就有可能导致很多看起来无厘头的错误,在查错时也让人头疼无比。因此学习jvm是很有必要的
运行时数据区
- jvm在执行java程序的过程中会把他所管理的内存划分为若干个不同的区域,以下将一一说明
1、程序计数器
- 简单来说,可以将它理解为一个指针,指向当前线程所执行字节码的“地址”,通过改变他的值达到程序运行的效果,分支、循环、跳转、异常处理、线程恢复等都通过这个“指针”实现。每个线程都会有自己的程序计数器来控制程序的执行
2、java虚拟机栈
-
当一个线程被建立时,就被分配了对应的虚拟机栈,java虚拟机栈用来描述java方法执行的内存模型,存储的基本的单位是栈帧,栈帧里存储每个方法调用时的具体信息。每个方法的调用和返回,对应栈帧的出栈和入栈操作
(局部变量表、操作数栈、动态链接和方法出口等信息)。 -
java虚拟机栈有两个可能出现的异常,StackOverflowError和OutOfMemoryError
这两个异常可以简单理解为:StackOverflowError是由于线程请求的栈深度大于虚拟机所允许的栈深度(过多的栈帧入栈)。如果Java虚拟机栈的大小可以动态扩展,当申请不到足够内存给栈时就会报OutOfMemoryError异常。(目前主流的HotSpot虚拟机不可以动态扩展,所以也就不会报这异常)
下面的代码演示了StackOverflowError,这是一个方法的递归调用,循环是死循环,一直在递归调用,程序执行之后,很快会出现异常
public static void main(String[] args) {
method();
}
public static void method() {
while (true)
{
method();
}
}

2.1、本地方法栈
- 与java虚拟机栈相似,本地方发栈为java程序中的native方法服务。个别虚拟机不区分java虚拟机栈和本地方发栈。
3、java堆
- 这部分空间是所有线程共享的空间,主要存放对象实例,在虚拟机启动时创建。另外,这部分空间是垃圾回收器所管理的主要区域。java虚拟机规范规定,java堆只要逻辑上是连续空间即可。空间大小可以是固定的,也可以是动态可扩展的。因为其空间大小可能存在限制,所以这里也可能出现OutOfMemoryError,
List<Test1> strList = new ArrayList<>();
while (true)
{
strList.add(new Test1());
}
像这样不停地给创建的list中填充Test1的对象,因为每个对象都被list保存了下来,故不会被垃圾回收机制所回收,当创建的对象足够多,占满了所有的java堆空间,导致java堆开始扩展,当java堆扩展过程中再无可用空间的同时,报出OutOfMemoryError异常

4、方法区
- 方法区是所有线程共享的区域,用于存储已经被虚拟机加载的类信息、常量、静态变量等信息。java规范将方法区从逻辑上划分为java堆的一部分。有些java虚拟机将方法区划分为java堆的一部分,将其规定为“永久代”,避免被GC回收。
- 方法区无法满足内存分配需求时,也会出现OutOfMemoryError异常。尝试使方法区出现异常,比较简单的一种方法就是不停的创建类,并对其进行加载操作,想到使用动态代理就可以复现这个异常。
4.1、运行时常量池
- 作为方法区的一部分。Class文件中的常量池,用于存储编译期生成的各种字面量和符号引用,这一部分内容在类加载之后被存放入方法区的运行时常量池。
- 作为方法区的一部分,运行时常量池也有可能因为无法申请到足够内存而抛出OutOfMemoryError异常。
总结
- 对于上述的jvm内存分布,在我的理解中分为两部分:线程共享区域和线程独占区域。线程共享区域有java堆(存放对象),方法区(存放被加载的类的信息)。线程独占区域(每个线程都有一个的部分,互不干扰)有:程序计数器(指向当前指令的地址),java虚拟机栈(存放当前所执行对的方法),本地方法栈(存放当前所执行对的Native方法)。这些区域随着java的发展,有可能出现改变,但基本大同小异,
本文详细解析JVM运行时数据区,包括程序计数器、虚拟机栈、本地方法栈、Java堆、方法区及运行时常量池,探讨各区域的作用、异常情况及其触发条件。
721

被折叠的 条评论
为什么被折叠?



