JVM

简介

Jvm 系列一:Java类的加载机制
Jvm系列二:JVM内存结构 --内存泄漏与内存溢出
Jvm系列三:GC算法 垃圾收集器
Jvm系列四:jvm调优-命令篇
Jvm系列五:java GC分析
Java技术体系主要由 class文件格式 + jvm + javaApi + java框架 构成
不了解虚拟机运行的特性原理,就无法写出适合虚拟机运行和自优化的代码
虚拟机特性和调优方法
。。。

一、 Java类的加载机制

类加载机制是指:
1.将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据模型的方法区中,=>保存类的数据结构
2.然后在堆区创建一个** java.lang.Class对象** => 作为对方法区中这些数据的访问入口。
3.并且向Java程序员提供了访问方法区内的数据结构的接口。

加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,
而且在Java堆中也创建一个 java.lang.Class类的对象,
这样便可以通过该对象访问方法区中的这些数据。

当类被加载之后,系统为之生成一个对应的Class对象
接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中

================
加载.class文件的方式
本地、
网络、
专有数据库提取.class文件、
将Java源文件动态编译为.class文件
从zip,jar等归档文件中加载.class文件

参考链接 :
https://zhuanlan.zhihu.com/p/60684596
https://zhuanlan.zhihu.com/p/44670213
https://zhuanlan.zhihu.com/p/61775388 内存
https://blog.csdn.net/m0_38075425/article/details/81627349 比较详细

二、JVM内存结构

运行时数据区域:了解创建和销毁时机,有的区域随着虚拟机进程启动而存在,有的区域随着用户线程的启动和结束而创建和销毁

JVM内存结构主要有三大块: 堆内存、方法区和栈
堆内存:
a. 堆内存是JVM中最大的一块由年轻代和老年代组成1:2
b. 年轻代内存又被分成三部分(8:1:1): Eden空间、From Survivor空间、To Survivor空间
堆内存模型+GC回收
<<<<<<<<<<<<<>>>>>>>>>>>>>>>
方法区
方法区存储类信息、常量、静态变量等数据,是线程共享等区域->Non-Heap(非堆)

java虚拟机栈和本地方法栈,主要用于方法的执行

<这里应该有张图 JVM和系统调用之间的关系>

共享区

Java堆(Heap)

  1. Java Heap 是Java虚拟机管理内存中最大的一块
  2. Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建
  3. 此内存区域的唯一目的: 存放对象实例 ,几乎所有的对象实例都在这里分配内存
  4. Java堆是垃圾收集器管理的主要区域,也被称为“GC堆” -- 分代收集算法
  5. 如果在堆中没有内存完成实例分配,并且堆也无法再扩展,将会抛出OOM Error

方法区(Method Area)

  1. 方法区与Java堆一样,是各个线程共享的内存区域
  2. 它用于储存已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码数据
  3. 除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集
  4. 方法区有时被称为持久代(PermGen)
    运行时常量池:类信息 + 常量池-》 编译期生成的各种字面量和符号引用
    参考链接:https://www.cnblogs.com/natian-ws/p/10749164.html
    高版本有变化
    方法的执行都伴随着线程的。
    原始类型的本地变量以及引用都存放在线程栈中。而引用关联的对象比如String,都存在在堆中

线程私有

程序计数器(Program Counter Register)

程序计数器(Program Counter Register)是一块较小的内存空间
它的作用可以看做是当前线程所执行的字节码的行号指示器---> 执行哪一行字节码
字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令
分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成

java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现
1. 在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程指令  -->某个线程执行完后
2. 线程切换后能恢复到正确的执行位置,每一条线程都需要一个独立的程序计数器
3. 各条线程之间的计数器互不影响,独立储存
4. 这类内存区域为“线程私有”的内存
5. 线程执行java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;
6. 如果正在执行的是Native方法,这个计数器值则为空(Undefined)
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域

JVM栈(JVM Stacks)

与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有
每一个方法从调用开始到执行完成的过程,就对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。

1. 它的生命周期与线程相同。
2. 虚拟机栈描述的是Java**方法执行**的内存模型
3. 每个方法执行的时候都会同时创建一个栈帧(Stack Frame)用于储存
    局部变量表:
      1. 存放了编译器可知的各种基本数据类型,在栈中分配多少内存空间,在编译期就已经确定了,索引访问
      stackoverflowError 
      outofmemoryError3
      2. 对象引用
      3. returnAddress类型
      4. 系统不会为局部变量赋予初始值(实例变量和类变量都会被赋予初始值)。也就是说不存在类变量那样的准备阶段。
    操作数栈:
      1. Java虚拟机的解释执行引擎被称为"基于栈的执行引擎",其中所指的栈就是指-操作数栈。
    动态链接:
      1. 由于栈帧是用于方法调用和方法执行的数据结构。所以每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用
      2. 在class文件的常量池中有大量的符号引号。这些符号引用有一部分在类加载阶段或者第一次使用的时候就直接转化为直接引用(静态解析)。有的则在每一次运行期间转化为直接引用(动态连接)。
    方法出口等信息
4. 每一个方法被调用直至执行完成的过程,就对应一个栈帧在虚拟机出入栈的过程
本地方法栈(Native Method Stacks)
虚拟机栈为虚拟机执行Java方法(也就是字节码)服务
本地方法栈是为虚拟机使用到Native方法服务。

直接内存

使用Native函数直接直接分配堆外内存 由堆中DirectByteBuffer操纵
避免在堆内存和Native内存来回复制

JAVA 垃圾回收
  1. 垃圾回收算法

标记清除
年老代

复制算法
年轻代

标记压缩

分代收集算法
Eden => from区 => to区 => 年老区
(新生代进行回收,Minor GC,不影响老年代)
(年老满了就要进行FULL GC,也叫Major GC 包括新生代和年老代)
应该尽量减少FULL GC,因为慢 卡
导致的原因:年老代被写满,永久代被写满,显示调用System.gc()

  1. 虚拟机遇到一条New指令时的操作

检查这个指令的参数,能否在常量池中一个类的符号引用
检查这个符合引用代表的类是否已经被加载、解析、初始化
如果没有执行类加载过程
加载之后,分配内存,内存大小在编译时就已确定

  1. 根据异常信息快速判断内存溢出区域

-XX:+HeapDumpOutOfMemoryError
如果不存在内存泄漏:GcRoot引用链 - > 查看内存是否必须活着 - > 查看参数是否调大
集群同步时,导致内存溢出
使用直接内存,导致内存溢出
外部命令,导致内存溢出
不恰当数据结构,导致内存溢出

  1. 数据分析

运行日志、异常堆栈、GC日志、线程快照、堆转储快照(heapdump/prof)

cpu 寄存器 高速缓存 工作内存 主内存
内核 用户

参考链接:
https://zhuanlan.zhihu.com/p/34426768
https://www.zhihu.com/question/293352546/answer/48523571 FUllGC YGC导致的问题

参考书籍:《深入理解Java虚拟机:JVM高级特性与最佳事件》-周志明

你可能感兴趣的:(JVM)