「这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战」
前言
刚学java的时候听过java虚拟机,知道所有的java代码都是跑在java虚拟机中的,好处就是一次编译多次运行,java也就因为这样的机制站稳了脚跟,那么java虚拟机内存结构到底是怎么组成的?看下面这张图
这张图在任何博客中都能找到,能看出来jvm内存结构是由五部分组成:
1、Method Area 方法区
2、Heap 堆
3、JVM Stacks 虚拟机栈
4、PC Register 程序计数器
5、Native Method Stacks 本地方法栈
那么都具体是干嘛的呢,请继续往下看
方法区
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载 的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。虽然《Java虚拟机规范》中把 方法区描述为堆的一个逻辑部分,但是它却有一个别名叫作“非堆”(Non-Heap),目的是与Java堆区 分开来。
—— 深入理解java虚拟机第三版 2.2.5 方法区
- 该区域是所有虚拟机线程共享的区,存储了成员变量,方法数据
- 方法区是在虚拟机启动时就会被创建,并且不同的jvm厂商实现的方式可能会不一样
- 同时也会造成堆内存溢出(1.8叫元空间内存溢出,之前叫永久代内存溢出,由于1.8使用了元空间,一般不会造成内存溢出)
堆
对于Java应用程序来说,Java堆(Java Heap)是虚拟机所管理的内存中最大的一块。Java堆是被所 有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java 世界里“几乎”所有的对象实例都在这里分配内存。
—— 深入理解java虚拟机第三版 2.2.4 java堆
常见的异常:java堆内存溢出(OutOfMemoryError 堆内存溢出异常,就是某个变量内容太大,超出了栈内存可以存储的最大值。通俗点讲就是你在java程序中定义的变量大小超过了java堆内存大小,存不下了就像我们倒水时杯子满了再倒水就会溢出一样。
内存溢出的例子,这个例子向一个list里面一直塞数据,直到堆内存存不下抛出内存溢出的错误为止:
虚拟机栈
每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量、操作数栈、动态连接、方法出口等信息。每方法被调用一直到执行完毕的过程时就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
• 线程运行时需要的内部空间(一个栈对应多个栈帧,栈帧:每个方法运行时需要的内存)
• 每个线程运行时所需要的内存,称为虚拟机栈
• 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
• 每个线程只能有一个活动栈,对应着当前正在执行的那个方法
• 栈内存越大,反而会使线程数越少(假如物理机上内存为500M,每个线程的栈内存分配1M,那么就可以分配500个线程,当把每个线程的栈内存调大至 2M,那么就只能分配 250 个线程),所以不建议采用过大的栈内存
• 当变量或者方法为私有时不需要考虑线程安全,当变量或方法为公共的或者 static 时就需要考虑线程安全
• 因为局部变量是线程私有的,每个方法在不同线程调用时都会在当前的栈内存中产生一个新的变量或者方法;但是如果是公有的或者static 时,多个线程会同时操作一个变量。
扩展
• 问题:方法内的局部变量是否线程安全?
a. 如果方法内局部变量没有逃离当前方法的作用访问,那么它就是线程安全的。
b. 如果局部变量引用了变量,并逃离了方法的作用方法,那么就需要考虑线程安全问题
• 问题:垃圾回收是否涉及栈内存?
垃圾回收不会涉及栈内存,不会回收栈内存的东西,只会回收堆内存中的东西。
程序计数器
字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
作用:
• 线程是私有的
• 记住下一条 虚拟机 指令得地址
• 永远不会内存溢出,JAVA规范中已经确定了该区域内存不存在溢出
本地方法栈
本地方法栈与虚拟机栈的作用非常相似,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地方法(Native) 方法服务。同时jdk源代码中 native 修饰的方法一般都是调用别的语言实现。
特点:
○ 有垃圾回收机制
○ 不是JAVA代码编写的方法(比如 object 中 clone等)
○ 它是线程共享的,堆中对象都需要考虑线程安全的问题。




近期评论