JVM笔记

2019-08-09 00:00:00 jvm 笔记

title: JVMnote
date: 2019-07-25 15:16:04
tags:

JVM笔记

前言

堆(操作系统):一般由程序员分配释放,若程序员不释放,则在程序结束时由操作系统回收

栈(操作系统):由操作系统自动分配释放,存放函数的参数值,局部变量等

​ JVM的内存是分布在操作系统的堆中的,因为操作系统的栈是操作系统管理的,它随时会被回收,所以如果JVM放在栈中,那Java的一个null对象就很难确定会被谁回收了,那GC的存在意义就没有了,而要堆栈做到自动释放也是JVM需要考虑的,所以放在堆中最合适。

Java代码编译

《JVM笔记》

JVM内存

《JVM笔记》

程序计数器(Program Counter Register):也叫PC寄存器,是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令、分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。,(1),区别于计算机硬件的pc寄存器,两者不略有不同。计算机用pc寄存器来存放“伪指令”或地址,而相对于虚拟机,pc寄存器它表现为一块内存(一个字长,虚拟机要求字长最小为32位),虚拟机的pc寄存器的功能也是存放伪指令,更确切的说存放的是将要执行指令的地址。(2)当虚拟机正在执行的方法是一个本地(native)方法的时候,jvm的pc寄存器存储的值是undefined。(3)程序计数器是线程私有的,它的生命周期与线程相同,每个线程都有一个。(4)此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

Java虚拟机栈(Java Virtual Machine Stack):(1)线程私有的,它的生命周期与线程相同,每个线程都有一个。(2)每个线程创建的同时会创建一个JVM栈,JVM栈中每个栈帧存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double;和reference (32 位以内的数据类型,具体根据JVM位数(64为还是32位)有关,因为一个solt(槽)占用32位的内存空间 )、部分的返回结果,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址;(3)每一个方法从被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。(5)栈运行原理:栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中,A方法又调用了B方法,于是产生栈帧F2也被压入栈,B方法又调用了C方法,于是产生栈帧F3也被压入栈…… 依次执行完毕后,先弹出后进……F3栈帧,再弹出F2栈帧,再弹出F1栈帧。(6)JAVA虚拟机栈的最小单位可以理解为一个个栈帧,一个方法对应一个栈帧,一个栈帧可以执行很多指令,如下图:

《JVM笔记》

(7)对上图中的动态链接解释下,比如当出现main方法需要调用method1()方法的时候,操作指令就会触动这个动态链接就会找打方法区中对于的method1(),然后把method1()方法压入虚拟机栈中,执行method1栈帧的指令;此外如果指令表示的代码是个常量,这也是个动态链接,也会到方法区中的运行时常量池找到类加载时就专门存放变量的运行时常量池的数据。

本地方法栈(Native Method Stack):(1)先解释什么是本地方法:jvm中的本地方法是指方法的修饰符是带有native的但是方法体不是用java代码写的一类方法,这类方法存在的意义当然是填补java代码不方便实现的缺陷而提出的。案例介绍将在 下面22知识点仔细介绍。(2)作用同java虚拟机栈类似,区别是:虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到的Native方法服务。(3)是线程私有的,它的生命周期与线程相同,每个线程都有一个。

Java 堆(Java Heap):(1)是Java虚拟机所管理的内存中最大的一块。(2)不同于上面3个,堆是jvm所有线程共享的。(3)在虚拟机启动的时候创建。(4)唯一目的就是存放对象实例,几乎所有的对象实例以及数组都要在这里分配内存。(5)

Java堆是垃圾收集器管理的主要区域。(6)因此很多时候java堆也被称为“GC堆”(Garbage Collected Heap)。从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆还可以细分为:新生代和老年代;新生代又可以分为:Eden 空间、From Survivor空间、To Survivor空间。(23知识点详细介绍)(7)java堆是计算机物理存储上不连续的、逻辑上是连续的,也是大小可调节的(通过-Xms和-Xmx控制)。(8)如果在堆中没有内存完成实例的分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

方法区(Method Area):(1)在虚拟机启动的时候创建。(2)所有jvm线程共享。(3)除了和堆一样不需要不连续的内存空间和可以固定大小或者可扩展外,还可以选择不实现垃圾收集。(5)用于存放已被虚拟机加载的类信息、常量、静态变量、以及编译后的方法实现的二进制形式的机器指令集等数据。(4)被装载的class的信息存储在Methodarea的内存中。当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件内容并把它传输到虚拟机中。(6)运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

方法区补充:指令集是个非常重要概念,因为程序员写的代码其实在jvm虚拟机中是被转成了一条条指令集执行的,看下图

《JVM笔记》

首先看看上面各部位位于图中的那些位置:左侧的foo代码是指令集,可见就是在方法区,程序计数器就不用说了,局部变量区位于虚拟机栈中,右侧最下方的求值栈(也就是操作数栈)我们从动图中明显可以看出存在栈顶这个关键词因此也是位于java虚拟机栈的。

另外,图中,指令是Java代码经过javac编译后得到的JVM指令,PC寄存器指向下一条该执行的指令地址,局部变量区存储函数运行中产生的局部变量,栈存储计算的中间结果和最后结果。

JVM启动:java根据系统版本找到jvm.cfg,根据jvm.cfg找到对应的jvm.dll启动JVM

类加载子系统执行过程:当一个ClassLoder启动的时候,ClassLoader的生存地点存在与JVM中的堆,然后它会通过JNI接口(它还常用于java与操作系统、硬件交互)去主机硬盘上将A.class装载到jvm的方法区,方法区中的这个字节文件会被虚拟机拿来new A字节码(),然后在堆内存生成了一个A字节码的对象,然后A字节码这个内存文件有两个引用一个指向A的class对象,一个指向加载自己的classLoader

执行引擎子系统:(1)负责执行来自类加载器子系统(class loader subsystem)中被加载类中在方法区包含的指令集,通俗讲就是类加载器子系统把代码逻辑(什么时候该if,什么时候该相加,相减)都以指令的形式加载到了方法区,执行引擎就负责执行这些指令就行了。

《JVM笔记》

(1)程序在JVM主要执行的过程是执行引擎与运行时数据区不断交互的过程,可理解为上面“方法区中的动图”

(2)但是执行引擎拿到的方法区中的指令还是人能够看懂的,这里执行引擎的工作就是要把指令转成JVM执行的语言(也可以理解成操作系统的语言),最后操作系统语言再转成计算机机器码。

(3)解释器:一条一条地读取,解释并且执行字节码指令。因为它一条一条地解释和执行指令,所以它可以很快地解释字节码,但是执行起来会比较慢。这是解释执行的语言的一个缺点。字节码这种“语言”基本来说是解释执行的。
即时(Just-In-Time)编译器:即时编译器被引入用来弥补解释器的缺点。执行引擎首先按照解释执行的方式来执行,然后在合适的时候,即时编译器把整段字节码编译成本地代码。然后,执行引擎就没有必要再去解释执行方法了,它可以直接通过本地代码去执行它。执行本地代码比一条一条进行解释执行的速度快很多。编译后的代码可以执行的很快,因为本地代码是保存在缓存里的。

简单理解jit就是当代码中某些方法复用次数比较高的,并超过一个特定的值就成为了“热点代码”。那么这个这些热点代码就会被编译成本地代码(其实可以理解成缓存)加快访问速度。

Java栈、本地方法栈、程序计数器(PC寄存器)这三个模块是线程私有的有多少线程就有多少个这三个模块

JVM的结构图

《JVM笔记》

JVM运行简易过程

《JVM笔记》

上图左半部分其实不是在JVM中,是.java文件,经过编译成.class文件(比如maven工程需要maven install,打成jar报,jar包里面都是.calss文件);这些步骤都是在eclipse上进行的。然后类加载器(classloader)一直到解释器是属于JVM的

垃圾回收机制

《JVM笔记》

① 新生区

新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两部分:伊甸区(Eden space)和幸存者区(Survivor pace),所有的类都是在伊甸区被new出来的。幸存区有两个:0区(Survivor 0 space)和1区(Survivor 1 space)。当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园进行垃圾回收(Minor GC),将伊甸园中的剩余对象移动到幸存0区。若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区。那如果1去也满了呢?再移动到养老区。若养老区也满了,那么这个时候将产生Major GC(FullGCC),进行养老区的内存清理。若养老区执行Full GC 之后发现依然无法进行对象的保存,就会产生OOM异常“OutOfMemoryError”。

如果出现java.lang.OutOfMemoryError: Java heap space异常,说明Java虚拟机的堆内存不够。原因有二:

a.Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整。

b.代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)。

② 养老区

​ 养老区用于保存从新生区筛选出来的 JAVA 对象,一般池对象都在这个区域活跃。

③ 永久区

​ 永久存储区是一个常驻内存区域,用于存放JDK自身所携带的 Class,Interface 的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭 JVM 才会释放此区域所占用的内存。

如果出现java.lang.OutOfMemoryError: PermGen space,说明是Java虚拟机对永久代Perm内存设置不够。原因有二:

a. 程序启动需要加载大量的第三方jar包。例如:在一个Tomcat下部署了太多的应用。

b. 大量动态反射生成的类不断被加载,最终导致Perm区被占满。

说明:

Jdk1.6及之前:常量池分配在永久代 。

Jdk1.7:有,但已经逐步“去永久代” 。

Jdk1.8及之后:无(java.lang.OutOfMemoryError: PermGen space,这种错误将不会出现在JDK1.8中)。

堆内存大小-Xms -Xmx设置相同,因为-Xmx越大tomcat就有更多的内存可以使用,这就意味着JVM调用垃圾回收机制的频率就会减少(垃圾回收机制被调用是jvm内存不够时自动调用的)可以避免每次垃圾回收完成后JVM重新分配内存。

Student s = new Student("小明",18);
 
s 是指针,存放在栈中。
 
new Student("小明",18) 是对象 ,存放在堆中。
 
Student 类的信息存放在方法区。
 
总结 :
 
对象的实例保存在堆上,对象的元数据(instantKlass)保存在方法区,对象的引用保存在栈上。

1、Java虚拟机的声明周期:**声明周期起点是当一个java应用main函数启动时虚拟机也同时被启动,而只有当在虚拟机实例中的所有非守护进程都结束时,java虚拟机实例才结束生命

2、java的虚拟机种有两种线程,一种叫叫守护线程,一种叫非守护线程(也叫普通线程),main函数就是个非守护线程,虚拟机的gc就是一个守护线程。java的虚拟机中,只要有任何非守护线程还没有结束,java虚拟机的实例都不会退出,所以即使main函数这个非守护线程退出,但是由于在main函数中启动的匿名线程也是非守护线程,它还没有结束,所以jvm没办法退出,虚拟机的gc(垃圾回收机制)就是一个典型的守护线程

3、Java虚拟机与main方法的关系:main函数就是一个Java应用的入口,main函数被执行时,Java虚拟机就启动了。启动了几个main函数就启动了几个Java应用,同时也启动了几个Java的虚拟机

4、GC垃圾回收机制不是创建的变量为空是就被立刻回收,而是超出变量的作用域后就被自动回收

5、什么是本地库接口和本地方法库:(1)本地方法库接口:即操作系统所使用的编程语言的方法集,是归属于操作系统的。(2)本地方法库保存在动态链接库中,即.dll(windows系统)文件中,格式是各个平台专有的

6、双亲委派机制:JVM在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

《JVM笔记》

7、jdk,jre,JVM的关系:JDK(Java Development Kit) 是 Java 语言的软件开发工具包(SDK)。在JDK的安装目录下有一个jre目录,里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib合起来就称为jre

《JVM笔记》

8、平时所说的八大基本类型的在栈中的存放位置是:运行时数据区->虚拟机栈->虚拟机栈的一个栈帧->栈帧中的局部变量表;局部变量表存放的数据除了八大基本类型外,还可以存放一个局部变量表的容量的最小单位变量槽(slot)的大小,通常表示为reference;所以是可以放字符串类型的,但是要以 String a=”xx”;的形式出现,如果是new Object()那就只能实在哎堆中了,栈里面存的是栈执行堆的地址。

9、方法区的内容是一次把一个工程的所有类信息都加载进去再去执行还是边加载边执行呢?

​ 其实单从性能方面也能猜测到是只加载当前使用的类,也就是边加载边执行。例如我们使用tomcat启动一个spring工程,通常启动过程中会加载数据库信息,配置文件中的拦截器信息,service的注解信息,一些验证信息等,其中的类信息就会率先加载到方法区。但如果我们想让程序启动的快一点就会设置懒加载,把一些验证去掉,如一些类信息的加载等真正使用的时候再去加载,这样说明了方法区的内容可以先加载进去,也可以在使用到的时候加载。

相关文章