最近面试,被问到关于jvm的知识,但我却一脸懵逼,故在这里总结一些关于jvm的知识
本文参考:https://blog.csdn.net/qq_33384065/article/details/80282023
&& https://www.cnblogs.com/dolphin0520/p/3613043.html
有篇文章写得很好可以去学习一下:https://www.cnblogs.com/heyanan/p/6125030.html
JVM内存
先从内存说起,jvm内存分为
-
非线程共享的:
vm栈(VM Stack)
本地方法区(native methods area)
程序计数器(Program Counter Register) -
线程共享的:
堆(heap)
方法区(methods area)
Java栈
Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池(运行时常量池的概念在方法区部分会谈到)的引用(Reference to runtime constant pool)、**方法返回地址(Return Address)**和一些额外的附加信息。
局部变量表,顾名思义,想必不用解释大家应该明白它的作用了吧。就是用来存储方法中的局部变量(包括在方法中声明的非静态变量以及函数形参)。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。局部变量表的大小在编译器就可以确定其大小了,因此在程序执行期间局部变量表的大小是不会改变的。
操作数栈,想必学过数据结构中的栈的朋友想必对表达式求值问题不会陌生,栈最典型的一个应用就是用来对表达式求值。想想一个线程执行方法的过程中,实际上就是不断执行语句的过程,而归根到底就是进行计算的过程。因此可以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。
指向运行时常量池的引用,因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。
方法返回地址,当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。
由于每个线程正在执行的方法可能不同,因此每个线程都会有一个自己的Java栈,互不干扰。
简单来说:
1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中
2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问
本地方法区(native methods area)
这里先说一下什么叫本地方法:简单地讲,一个Native Method就是一个java调用非java代码的接口。比如我用Java声明了一个接口,但是却用C/C++去实现,那么它就是一个本地方法。详细可以看这里的:https://blog.csdn.net/itmrchen/article/details/53065883
本地方法区,也就是 本地方法栈(Native Method Stack) ,跟上面的Java栈相似,只不过它是为Native Method方法服务的。
程序计数器(Program Counter Register)
程序计数器是一块较小的内存区域,作用可以看做是当前线程执行的字节码的位置指示器。分支、循环、跳转、异常处理和线程恢复等基础功能都需要依赖这个计算器来完成,指示执行哪条指令的。
在JVM规范中规定,如果线程执行的是非native方法,则程序计数器中保存的是当前需要执行的指令的地址;如果线程执行的是native方法,则程序计数器中的值是undefined。
由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的
Java堆
Heap(堆)是JVM的内存数据区。Heap 的管理很复杂,是被所有线程共享的内存区域,在JVM启动时候创建,专门用来保存对象的实例。在Heap 中分配一定的内存来保存对象实例,实际上也只是保存对象实例的属性值,属性的类型和对象本身的类型标记等,并不保存对象的方法(以帧栈的形式保存在Stack中),在Heap 中分配一定的内存保存对象实例。而对象实例在Heap 中分配好以后,需要在Stack中保存一个4字节的Heap 内存地址,用来定位该对象实例在Heap 中的位置,便于找到该对象实例,是垃圾回收的主要场所。java堆处于物理不连续的内存空间中,只要逻辑上连续即可。
在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存
方法区
在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。
在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。
在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。
Java中几种常量池的区分
全局字符串池(string pool也有叫做string literal pool)
class文件常量池(class constant pool)
运行时常量池(runtime constant pool):是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table)
总结
1.全局常量池在每个VM中只有一份,存放的是字符串常量的引用值。
2.class常量池是在编译的时候每个class都有的,在编译阶段,存放的是常量的符号引用。
3.运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致
垃圾回收
JVM的内存结构包括五大区域:程序计数器、虚拟机栈、本地方法栈、堆区、方法区。其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生、随线程而灭,所以垃圾回收的主要对象是堆。
回收方法有以下几种:
- 标记-清除
- 标记-复制
- 标记-整理
- 分代回收
详情看这里:https://blog.csdn.net/dd864140130/article/details/50084471