JVM

Posted 云行天下0721

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM相关的知识,希望对你有一定的参考价值。

1、Java的内存区域

JVM内存主要指的是,"运行时数据区域" 。 

运行时数据区包括:方法区、堆、程序计数器、本地方法栈、虚拟机方法栈等5个区域。

 

说一下JVM的主要组成部分及其作用?

JVM主要由两个子系统和两个组件组成,两个子系统为Class Loader(类加载系统)和 Execution engine(执行引擎);

两个组件为Runtime data area(运行时数据区) 和Native Interface(本地接口)

运行过程:

首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。

 

1、程序技术器:

为什么程序计数器是线程私有的?

Java虚拟机的多线程是通过线程轮换分配处理器的执行时间来实现的,也就是说在一个时刻只会执行一条线程,当线程切换后处理器并不会知道前一个线程执行到哪一个程序段了。所以需要使用程序技术器来进行记录自己线程执行到哪个位置,当该线程再次分配到CPU时,就能够按顺序继续执行了。当线程执行的是本地方法的时候,程序计数器中保存的值是空(undefined);原因很简单:本地方法是C++/C 写的,由系统调用,根本不会产生字节码文件,因此,程序计数器也就不会做任何记录 ;

 

 

2、Java虚拟机栈:

2.1Java虚拟机栈也是线程私有的,它的生命周期与线程一样,随线程而生,随线程而灭。

2.2当线程请求的栈深度大于虚拟机所分配或者允许的最大深度将会发生栈溢出,StackOverflowError异常。

现在的虚拟机一般会允许动态扩展,只不过JVM也允许固定长度的虚拟机栈 , 当内存不足无法继续扩展时,就会抛出OOM异常。

2.3Java虚拟机栈描述的是Java方法执行的内存模型:每个方法执行的同时会创建一个栈帧,栈帧是虚拟机栈的元素。

 栈帧

栈帧是Java虚拟机栈中的栈元素,每一个被调用运行时都会创建一个栈帧,方法的调用栈帧入栈,方法调用完成后栈帧出栈,CPU只会执行虚拟机栈中的栈顶元素。可结合递归函数调用的情形来理解。

栈帧中主要存储了局部变量表、操作数栈、动态链接和方法返回地址等信息。

局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。

局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)「String是引用类型」,对象引用(reference类型) 和 returnAddress类型(它指向了一条字节码指令的地址)。

变量槽

局部变量表的容量以变量槽为最小单位,每个变量槽都可以存储32位长度的内存空间,例如boolean、byte、char、short、int、float、reference。对于64位长度的数据类型(long,double),虚拟机会以高位对齐方式为其分配两个连续的Slot空间,也就是相当于把一次long和double数据类型读写分割成为两次32位读写。

动态链接

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,

  持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。

  在类加载阶段中的解析阶段会将符号引用转为直接引用,这种转化也称为静态解析。

  另外的一部分将在每一次运行时期转化为直接引用。这部分称为动态连接

方法出口:

当一个方法开始执行后,只有2种方式可以退出这个方法 :

  方法返回指令 : 执行引擎遇到一个方法返回的字节码指令,这时候有可能会有返回值传递给上层的方法调用者,这种退出方式称为正常完成出口。

  异常退出 : 在方法执行过程中遇到了异常,并且没有处理这个异常,就会导致方法退出。

  无论采用任何退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息。

  一般来说,方法正常退出时,调用者的PC计数器的值可以作为返回地址,栈帧中会保存这个计数器值。

  而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息

 

 

Java堆:

java堆是JVM管理的最大的一块内存空间,主要用于存放各中类的实例对象。

Java堆被划分为两种不同的区域,新生代和老年代。

新生代又分为:Eden 、From Survivor 、To Survivor 三块区域,这样划分是为了更好管理Java堆内存。

 

 

其中永久代就是方法区,是指内存中的永久保存区域,

主要存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域. 它和和存放Instance的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的应用会加载很多Class的话,就很可能出现PermGen space错误。

原文链接:https://blog.csdn.net/u013256816/article/details/50764532

 

方法区:存放class 和 static 变量 , 方法区中包含的都是整个程序中永远唯一的元素。

 

堆和栈的区别?

物理地址

堆的物理地址分配对对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,所以有各种算法。比如,标记-消除,复制,标记-压缩,分代(即新生代使用复制算法,老年代使用标记——压缩)

栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。

内存分别

堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定。一般堆大小远远大于栈。

栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。

存放的内容

堆存放的是对象的实例和数组。因此该区更关注的是数据的存储

栈存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。

PS:

静态变量放在方法区
静态的对象还是放在堆。
程序的可见度

堆对于整个应用程序都是共享、可见的。

栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。
原文链接:https://blog.csdn.net/ThinkWon/article/details/104390752

 

内存泄漏:

内存泄漏指的是不在被使用的内存和变量一直被占据在内存中,导致这部分内存空间一直无法被回收也就无法被充分应用。称为内存泄漏。虽然JVM有垃圾收集机制,但是还是很容易发生内存泄漏的状况。当长期对象持有短期对象时,尽管短期对象已经不再使用,但是由于它被长期对象持有就一直不会被回收。这是比较普遍的内存泄漏的情景。

以上是关于JVM的主要内容,如果未能解决你的问题,请参考以下文章

jvm基础--JVM参数配置

jvm基础--JVM内存模型

jvm基础--JVM内存模型

JVM基础:深入学习JVM堆与JVM栈(转)

JVM堆与JVM栈

JVM内存管理和JVM垃圾回收机制