石排网站仿做,《小城镇建设》》杂志社网站,个体户做网站是怎么备案,写资料的网站有哪些内容前面我们通过Java是如何编译、JVM的类加载机制、JVM类加载器与双亲委派机制等内容了解到了如何从我们编写的一个.Java 文件最终加载到JVM里的#xff0c;今天我们就来剖析一下这个Java的‘中介平台’JVM里面到底长成啥样。 JVM的内存区域划分
Java虚拟机#xff08;JVM… 前面我们通过Java是如何编译、JVM的类加载机制、JVM类加载器与双亲委派机制等内容了解到了如何从我们编写的一个.Java 文件最终加载到JVM里的今天我们就来剖析一下这个Java的‘中介平台’JVM里面到底长成啥样。 JVM的内存区域划分
Java虚拟机JVM是Java程序运行的虚拟计算机它负责将Java字节码转换为特定平台上的机器指令。JVM的内存区域划分是JVM规范中定义的它规定了JVM在执行Java程序时所管理的不同内存区域。JVM内存区域的主要划分如下图所示 方法区元空间 用于存储类信息、常量、静态变量等数据的内存区域。它是所有线程共享的用于支持类和接口的组织。在HotSpot JVM中它通常被实现为永久代或元空间。从Java 8开始替代了永久代用于存储类的元数据如类的静态结构。元空间不是JVM堆的一部分而是使用本地内存有助于避免内存溢出错误。
Java堆内存 堆是用于存储对象实例和数组的内存区域是垃圾回收器管理的主要区域也是所有线程共享的。堆通常分为新生代和老年代垃圾收集器定期清理无用对象以回收内存。
线程栈内存 栈是每个线程独有的内存区域用于存储局部变量、操作栈和方法调用信息。栈由栈帧组成每个栈帧包含局部变量表、操作数栈、动态链接信息和方法返回地址。
本地方法栈 本地方法栈用于支持JVM使用本地方法时的内存管理类似于Java栈但是用于管理本地方法调用。
程序计数器 程序计数器是每个线程都有一个的内存区域用于记录当前线程执行的字节码的行号指示器。线程切换时程序计数器也会切换到下一个方法的起始点。
直接内存 直接内存虽然不是JVM运行时数据区的一部分但是Java NIO允许使用直接内存进行高效的I/O操作。直接内存不是由JVM管理但是可以通过Java代码进行分配和释放。 每个内存区域都有其特定的用途和GC行为了解这些区域可以更好地理解Java程序的内存管理和性能调优为后续我们谈论GC相关做好铺垫。
JVM内存模型实例 老样子上一章节我们从理论介绍来JVM内存区域划分及其各个区域应该存放的数据下面还是通过一段简单的代码来一起探究这个过程中数据是怎样流转的
public class App {public static void main(String[] args) {SpringApplication sApp new SpringApplication();// sApp.run(App.class, args);}}
1、存放类的方法区元空间
其实在jdk1.8以后改成元空间更加利于我们的理解了元空间存放元数据的空间对于Java虚拟机谁是他的元数据呢当然是class相关的数据呢所以元空间方法区当然是存放类相关数据的呢。上述代码在JVM中的大概位置如图 2、程序计数器
通过前面Java编译大家可以明白我们编写好的源代码会被编译成各种字节码指令然后字节码指令会被一条一条的执行。所以JVM在加载class文件后需要有个可以执行字节码指令的工具在JVM中这个工具就是字节码执行引擎。 但是字节码执行引擎只是用来执行指令的具体执行到哪里了就需要另外程序计数器来记录。如下图所示 Java虚拟机JVM是支持多线程的它允许多个线程并发执行。在Java中每个线程都有自己的程序计数器Program CounterPC这个计数器是线程私有的。程序计数器用于存储当前线程正在执行的字节码指令的地址确保线程在执行过程中能够正确地跟踪执行状态。
因此每一个线程执行字节码时程序计数器会指向当前正在执行的指令。如果线程被暂停或阻塞程序计数器会保持在当前指令的位置这样当线程再次被调度执行时可以从上次暂停的地方继续执行。 如下图更加准确描述了他们之间的关系 在Java中代码的执行总是由线程来驱动的。即使是简单的main()方法也是由JVM在启动时创建的名为main的线程来执行的。这个线程是Java程序的入口点它负责执行main()方法中的代码。当main线程开始执行main()方法时它的程序计数器会记录当前执行的字节码指令的地址。程序计数器是每个线程私有的内存区域它的作用是确保线程能够跟踪其执行状态包括当前执行到哪一行代码或哪一个字节码指令。
3、Java虚拟机栈
上面介绍的程序计数器是用来记录指令执行的位置的且与每个线程有关但是我们知道其实除了类变属性以外其实每个方法都有自己的局部变量如下是的代码示例
public class App {public static void main(String[] args) {SpringApplication sApp new SpringApplication();//sApp.run(App.class,args);sApp.getRunListeners(args);}
}public class SpringApplication {public String run(Class appClass, String[] args) {System.out.println(my Spring Application );return run args;}public String getRunListeners( String[] args) {System.out.println(My getRunListeners );String myRunListenersVar myRunListenersVar;this.getSpringFactoriesInstances(args);return run args;}public String getSpringFactoriesInstances( String[] args) {System.out.println(My getSpringFactoriesInstances );String mySpringFactoriesInstancesVar mySpringFactoriesInstances;return run args;}}
上述代码SpringApplication类的getRunListeners方法与getSpringFactoriesInstances方法都有各自的局部变量因此Java虚拟机也必须有一块内存空间去存该部分数据。
上述代码运行mian方法会将首相会将App类里的main作为第一个栈帧压倒main线程所在的Java虚拟机栈如下图所示 进入SpringApplication#getRunListeners方法时会将getRunListeners作为第二个栈帧压入main线程所在的Java虚拟机栈同时在getRunListeners我们声明里局部变量myRunListenersVar变量该变量在栈帧getRunListeners里。如下图所示 方法继续向下执行进入getSpringFactoriesInstances方法会将getSpringFactoriesInstances压入main线程的Java虚拟机栈顶如下图所示 因为Java栈结构是先进后出后续便会按照getSpringFactoriesInstances - getRunListeners - main 顺序进行弹栈知道main方法运行结束。所以局部变量只会在方法内部生效同时Java虚拟机栈又是线程内执行所以后续介绍Java并发编程的时候我们说线程安全的时候也会提到局部变量避免并发冲突等。
介绍完上述内容我们再用一张图描述下 4、Java堆内存
在介绍栈的过程中有意识的跳过了上述代码中关于创建SpringApplication这块的代码我们再回头看看这段代码。
public class App {public static void main(String[] args) {SpringApplication sApp new SpringApplication();}
}
上述 new SpringApplication()代码就是创建了一个SpringApplication对象实例同样作为JVM也需要找个地方来存放对象实例数据而这个地方被称作为Java堆内存。
当我们创建一个对象的时候会将这个对象的实例数据放到堆内存中然后把这个实例存放的引用返回给局部变量这样我们就持有了对象实例的地址。
还是画一张图更加清晰一点 5、整体流程
介绍整体流程之前其实还有个区域就是Java为了区分我们写的方法和自己内置调用的方法专门用来处理本地方法的调用区域管理这块JVM称之为本地方法栈。至此整个流程就可以完整画出来了。 1、你的JVM进程会启动就会先加载App类到内存里。然后有一个main线程开始执行你的App中的main()方法。main线程是关联了一个程序计数器的那么他执行到哪一行指令就会记录在这里 2、main线程在执行main()方法的时候会在main线程关联的Java虚拟机栈里压入一个main()方法的栈帧。 3、接着会发现需要创建一个SpringApplication类的实例对象此时会加载SpringApplication.class文件到内存里来 4、创建一个SpringApplication的对象实例分配在Java堆内存里并且在main()方法的栈帧里的局部变量表引入一个sApp”变量让他引用SpringApplication对象在Java堆内存中的地址。 5、main线程开始执行SpringApplication对象中的方法会依次把自己执行到的方法对应的栈帧压入自己的Java虚拟机栈 6、执行完方法之后再把方法对应的栈帧从Java虚拟机栈里弹出来
那么JVM中的各个核心内存区域的功能和对应的我们的Java代码之间的关系就彻底理解
其实到这里JVM相关应该就结束了但是在上面理论介绍的时候我们还提及到了直接内存其实这块放到IO相关模块更加合适上述提及到是为了给大家理解元空间做的实例。
同样最后的最后我们思考一两个问题上述我们给JVM划分了各个内存区域放各种数据随着程序的运行或者数据量变多了内存放不下了怎么办呢或者在分配内存的时候大家地址重复了又该咋整呢