JVM基础知识以及优化
JVM
概念
java Virtual machine ----java虚拟机:java程序的运行时环境
区分java内存结构与java内存模型(JMM)
常见的JVM
JVM其实是一套规范,开发JVM需要准守这个规范
常见的开发JVM的实例有:oracle公司的hotspot
结构
JVM组成部分:类加载器、JVM内存、执行引擎
JVM内存结构
1程序计数器(寄存器)
1.java源代码------>编译后二进制的字节码(JVM指令(对所有平台是一致的))---->通过java虚拟机中的组件:解释器处理成为机器码------>这样机器码就能被cpu执行
2.与此同时,解释器是从程序计数器中读取指令的内存地址的(即程序计数器来记住下一条指令的内存地址),然后解释器从二进制字节码中找到对应的jvm指令
3.特点:
线程是私有的(java程序支持多线程运行,cpu的调度器组件给每个线程分配时间片,然后交替的执行每个线程,每个线程都有自己的独属于的程序计数器)
程序计数器不会存在内存溢出问题
2虚拟机栈
每个线程在运行时需要内存空间来存储,这个 内存空间叫做虚拟机栈
垃圾回收只是对堆内存进行垃圾回收,不会对虚拟机栈进行垃圾回收
虚拟机栈内存溢出
1.什么情况导致栈内存溢出 java.lang.stackoverflowerror
栈帧过多超过了栈内存(例如方法的递归调用,但未设置合理的或者没有结束条件)
栈帧过大超出栈内存
虚拟机栈与线程有关,线程运行诊断
cpu占用过高:
1.
[root]# top
利用top命令查看所有的进程
[root]# ps H -co pid,tid,%cpu | grep 32655
ps H 命令来诊断所有运行的线程的占用cpu情况
-co 可以指定只关注几列(进程id,线程id,cpu占用情况)
| grep 进程id 来进行筛选出所有该进程下的线程
2.
jdk提供的命令:
jstack 32655
查看该进程id 的所有的线程
线程执行没有结果(有可能产生了死锁):
jstack 32655
3本地方法栈
JVM在调用本地方法时候,需要给本地方法提供内存空间,不是由java代码编写的方法的叫做本地方法,与底层操作系统来调用功能
4堆
通过new进行创建对象,需要使用堆内存
线程共享,需要考虑线程安安全的问题
堆内存溢出
该对象一直在使用无法进行垃圾回收内存,则会产生内存溢出
堆内存诊断
进行垃圾回收之后,堆内存占用还是很高,如何排查?
利用jconsole进行执行GC垃圾回收,Jmap进行查看新生代和旧生代的占用
**`jvisualvm` 命令可视化的方法展现jvm**
使用里面的堆dump -----生成当前堆内存的快照----查看其中的对象占用内存情况
GC垃圾回收机制
判断对象是否回收
1.引用计数法
对象被引用的次数加一或者不再引用则减一,弊端循环引用
2.可达性分析算法(java虚拟机使用的方法)
①首先对所有的对象进行扫描
②如果是被根对象进行引用的对象(即沿着GC root对象为起点的引用链能够找到该对象),那么不能当成垃圾,反之即可
根对象(GC Root):肯定不能当做垃圾的对象叫做根对象
如何分析是不是根对象?利用java提供的工具 MAT(分析快照)、JMAP(产生快照)
3.五种引用(强软弱、虚引用、终结器引用)
垃圾回收算法
1.标记-清除(清除:就是将该内存地址的起始地址放到可分配的列表中,等待下次被分配占用,不需要清除里面的东西)
2.标记-整理(整理:避免内存碎片问题,会排序内存连续空间,对象需要移动)
3.复制(先标记空闲对象的内存,将没标记的转到 to 内存中,然后回收from里的,再交换from 与 to 的指向)
分代垃圾回收算法(java虚拟机中使用的,垃圾回收算法的协同工作)
在java虚拟机中不会单独使用一种垃圾回收算法,会多种算法共同应用工作,具体的实现就是虚拟机中的分代垃圾回收
垃圾回收器
分类:
1.串行
2.吞吐量优先
3.响应时间优先
举例:
CMS垃圾回收器
G1垃圾回收器:
垃圾回收调优
5方法区
线程共享,需要考虑线程安安全的问题
方法区存储的信息,跟类的结构有关的信息:类的成员变量,成员方法和构造器方法的代码,类的构造器,运行时常量池。
在jvm运行时开辟方法区内存
方法区在逻辑上是堆的组成一部分,但是在实际的厂商在创建jvm时,不一定都是属于堆的一部分的,也有使用操作系统的本地内存的。例如在oracle厂商中hotspot虚拟机在1.6版本是利用 永久代 进行实现的
方法区内存溢出
1.元空间(1.8版本 之后的名称)的小,但是类加载器创建的类的二进制字节码比较多,那就造成内存溢出
运行时常量池
二进制字节码文件(类基本信息,常量池,类方法定义,包含了虚拟机指令)
将class文件反编译 javap -v xxx.class
常量池:就是常量表,虚拟机的jvm指令可以根据这张表进行找到要执行的;类名,方法名,参数类型,字面量等信息
运行时常量池:常量池就是在class文件中的一个表,当这个类被加载到虚拟机中,他的常量池信息需要放在内存中,也就是运行时常量池,把前面的 #xx 对应表序号转化成真正的内存地址。
stringtable(串池)
直接内存(属于系统内存,不是jvm内存空间)
buffer在系统内存创建一个directmemory缓冲区
类加载与类的字节码结构
java源文件通过编译器进行编译成类文件
类文件:就是java字节码文件(包含字节码指令)
字节码文件通过类加载器进行类加载(其中有很多的阶段),然后加载到虚拟机中就可以执行其中的字节码指令了
执行的时候需要执行引擎的解释器进行解释执行,但是在解释的过程中会对其中的热点代码进行运行期的编译处理
类文件结构
字节码文件通过类加载器进行类加载(其中有很多的阶段),然后加载到虚拟机中就可以执行其中的字节码指令了
执行的时候需要执行引擎的解释器进行解释执行,但是在解释的过程中会对其中的热点代码进行运行期的编译处理
类文件
java源文件通过编译器进行编译成类文件
类文件:就是java字节码文件(包含字节码指令)
结构
类文件的结构是根据JVM的规范来的,各个虚拟机开发厂商在实现虚拟机的时候需要准守这个结构规范:
1.魔数
四个字节,所有文件都是一个自己的特定类型
四个字节来表示自己是class文件,而不是其他文件
ca fe ba be
2.版本
四个字节,表示类的版本20202034 中的34表示java 8
字节码指令
javac -parameters -d . Helloworld.java
编译java源文件,然后npp的二进制查看插件:
如果自己进行分析类文件结构过于麻烦,使用提供的javap 反编译class文件:
javap -v HelloWorld.class
文件的自己信息(修改时间,md5,编译来源)
全类名
版本信息
修饰符级别
常量池
方法信息(构造方法,本地变量,main方法,局部变量)
图解运行流程
编译期处理
java编译器 将 .java源代码文件 进行编译为 .class文件的过程叫做 编译期
类加载阶段
类加载阶段可以分为三个阶段:加载、链接、初始化
加载阶段(与链接交替执行)
class文件要运行的话,需要类加载器进行将文件(字节码文件)加载到jvm的方法区中。内部采用c++ 的 instanceKlass 数据结构来描述java类
链接阶段(与加载交替执行)
链接可以分为三个步骤:验证、准备 、解析
验证步骤:
比如修改了class字节码的格式等等一些信息的话,在链接的过程中验证不能通过
准备步骤:
解析步骤:
HSDB工具监控连接的进程id:
java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB
初始化阶段
就是执行类的初始化方法,()v方法
类加载器
(类加载中有层级关系):