JVM内存区域划分

内存区域划分图:

  

 

 


方法区:
存储类信息、常量、静态变量、JIT即时编译器编译后的代码。所有线程共享。

当JVM中的类加载器将一个类的.class文件加载到JVM内存时,是把这个.class信息加载到了方法区中,而.class内部记录的是字节码指令,线程要根据这样的字节码指令才能去进行具体工作,所以可以把这些字节码指令理解为线程运行的元数据。方法区中的对象只有一份,比如A.class在方法区中只有一份,也就是说一个类对象在一个JVM中只有一个,这个类的静态变量也就只有一份,常量也是只有一个就足够了。在JDK1.8之后,方法区被改为为元数据区,MetaSpace。

堆内存:
存放创建的对象实例,所有对象的具体内容都放在这里。所有线程共享。

堆内存是所有线程共享的,一个JVM中只有一个堆内存。堆内存存放的是实际对象的内容,就是说代码中所有new出来的对象都是放在堆内存里面的。而这些对象的引用要么就是放在某个线程的栈帧的局部变量表里,要么就是方法区里的某个静态变量或常量。

虚拟机栈:
栈结构,里面存放的是一个一个栈帧。线程私有。

虚拟机栈是线程私有的,它的生命周期都是与线程一致的,如果JVM中跑了多个线程,那么将会有多个虚拟机栈。虚拟机栈就是一个栈结构,在这个栈里面进进出出的是栈帧,一个栈帧对应一个方法。当一个线程执行一个方法时,该方法对应的栈帧便压入该线程的栈;当一个线程退出一个方法时,该方法对应的栈帧便弹出该线程的栈。而一个栈帧里面记录的是它对应的方法的运行时数据,比如:局部变量、操作数栈、动态链接、方法返回地址等,注意这里的局部变量就是一个地址,而这个地址指向的是堆内存里面的具体对象内容存放的地址。在JVM启动时,可以通过Xss参数来指定一个线程的栈内存大小。

本地方法栈:
同虚拟机栈一样,只不过针对的是native方法。线程私有。

程序计数器:
存放当前线程的执行到的字节码的行号。线程私有。

JVM是一个运行在操作系统上的一个进程,其内部会跑很多线程,而每一个线程要去执行字节码,所以就必须有一个地方记录当前它执行到的字节码的具体位置,那么程序计数器就是记录了每个线程执行的字节码的具体位置,这样当线程调度出来时才能做到:继续执行。每一个线程都必须有自己的程序计数器。

结合程序语句对应起来讲讲:
说了那么多,还是不懂,它们都是怎么起作用的呢?接下来我们结合我们平时都在写的代码看看对应在JVM中发生了什么事情:

 1 Public class Application{
 2 
 3 public static void main(String[] args){
 4 test();
 5 }
 6 
 7 public static void test(){
 8 Person p = new Person();
 9 }
10 }

 


每个线程在JVM中都有一个自己的栈。那么当有一个线程A调用main()方法之后,main()方法对应的栈帧便会压入线程A的栈中:

 

 

 

当线程A执行到test()处时,又会把test()方法对应的栈帧压入线程A的栈中:

 

 

 

当执行Person p = new Person(),时,会创建一个Person对象p,p对象的实际内容放在了堆内存中,然后p对象的引用(也就说p对象在堆内存中的地址)记录在了test()方法对应栈帧的局部变量表中:

 

 

 

当test()方法执行完毕返回后,对应的栈帧便从线程A的栈中弹出,弹出的栈帧会被清除掉:

 

 

 

同理,线程B也有可能会调用到test()方法,test()方法会有新的对应的栈帧压入线程B的栈中,然后同样创建一个Person对象p在堆内存中,引用放在线程B栈中的test()方法对应的栈帧的局部变量表中。

可以总结出:

线程与JVM中的栈对应,一个线程一个栈帧,而且栈与线程的生命周期一致;

方法的一次执行与JVM中栈中的一个栈帧对应,一次方法的执行周期与一个栈帧的入栈出栈周期一致;

为什么方法的调用可以对应栈帧的入栈和出栈?

比如A方法调用了B方法,此时一个线程1过来了,那么必然是先进入A方法,后进入B方法,而且必然是先离开B方法,再离开A方法,所以符合“先进后出”的模式,所以自然就对应了栈结构。

相关内存区域的JVM参数总结:
-Xmx:决定了堆内存最大大小

-Xms:决定了堆内存初始大小

-Xss:决定了每个线程的栈内存大小

-XX:PermSize: 决定了方法区大小

-XX:MaxPermSize: 决定了方法区最大大小

posted @ 2023-02-24 15:47  木木林2022  阅读(44)  评论(0)    收藏  举报