JVM原理与深度调优(一)

什么是jvm

jvm是java虚拟机 运行在用户态、通过应用程序实现java代码跨平台、与平台无关、实际上是”一次编译,到处执行”

1.从微观来说编译出来的是字节码!去到哪个平台都能用,只要有那个平台的JDK就可以运行!字码好比是一个人,平台好比为国家,JDK好比这个国家的语言!只要这个人(字节码)有了这个国家的语言(JDK)就可以在这个国家(平台)生活下去。
2.JDK 是整个Java的核心,包括了Java运行环境(Java Runtime Envirnment),一堆Java工具和Java基础的类库(rt.jar)。
3.Java虚拟机(JVM)一种用于计算机设备的规范,可用不同的方式(软件或硬件)加以实现。编译虚拟机的指令集与编译微处理器的指令集非常类似。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。
4.java编译出来的是一种“java字节码”,由虚拟机去解释执行。 而c和c++则编译成了二进制,直接交由操作系统执行。
5.所谓的一次编译、到处执行,即只需在一个地方编译,在其他各个平台下都可以执行。
6.与平台无关指的是JAVA只运行在自己的JVM上,不需要依赖任何其他的底层类,所以和操作系统没有任何联系,平台是说运行的系统

内存结构图

《JVM原理与深度调优(一)》

 

 class文件 

class文件径打破了C或者C++等语言所遵循的传统,使用这些传统语言写的程序通常首先被编译,然后被连接成单独的、专门支持特定硬件平台和操作系统的二进制文件。通常情况下,一个平台上的二进制可执行文件不能在其他平台上工作。而Java class文件是可以运行在任何支持Java虚拟机的硬件平台和操作系统上的二进制文件。

 

执行过程

 执行过程简介

当编译和连接一个C++程序时,所获得的可执行
二进制文件只能在指定的硬件平台和操作系统上运行,因为这个二进制文件包含了对目标处理器的
机器语言。而Java
编译器把Java源文件的指令翻译成
字节码,这种字节码就是Java
虚拟机的“机器语言”。 与普通程序不同的是,Java程序(class文件)并不是本地的可执行程序。当运行Java程序时,首先运行
JVM(Java虚拟机),然后再把Java class加载到JVM里头运行,负责加载Java class的这部分就叫做Class Loader。

JVM中的ClassLoader

JVM本身包含了一个ClassLoader称为Bootstrap ClassLoader,和JVM一样,BootstrapClassLoader是用
本地代码实现的,它负责加载核心JavaClass(即所有java.*开头的类)。 另外JVM还会提供两个ClassLoader,它们都是用
Java语言编写的,由BootstrapClassLoader加载;其中Extension ClassLoader负责加载扩展的Javaclass(例如所有javax.*开头的类和存放在JRE的ext目录下的类)ApplicationClassLoader负责加载应用程序自身的类。 当运行一个程序的时候,JVM启动,运行bootstrapclassloader,该ClassLoader加载java核心API(ExtClassLoader和AppClassLoader也在此时被加载),然后调用ExtClassLoader加载扩展API,最后AppClassLoader加载CLASSPATH目录下定义的Class,这就是一个程序最基本的加载流程。  

第一个Class文件、通过javac编译成字节码、字节码之后有个ClassLoader叫类加载器,因为java.class文件到JVM内部运行起来需要有个装载过程、从物理的文件到内存的结构、比如加载、连接、初始化。

linux应用程序有个进程地址空间,对进程地址空间的解释:

linux采用虚拟内存管理技术,每一个进程都有一个3G大小的独立的进程地址空间,这个地址空间就是用户空间。每个进程的用户空间都是完全独立、互不相干的。进程访问内核空间的方式:系统调用和中断。
    创建进程等进程相关操作都需要分配内存给进程。这时进程申请和获得的不是物理地址,仅仅是虚拟地址。 
实际的物理内存只有当进程真的去访问新获取的虚拟地址时,才会由“请页机制”产生“缺页”异常,从而进入分配实际页框的程序。该异常是虚拟内存机制赖以存在的基本保证,它会告诉内核去为进程分配物理页,并建立对应的页表,这之后虚拟地址才实实在在的映射到了物理地址上。

Linux操作系统采用虚拟内存技术,所有进程之间以虚拟方式共享内存。进程地址空间由每个进程中的线性地址区组成,而且更为重要的特点是内核允许进程使用该空间中的地址。通常情况况下,每个进程都有唯一的地址空间,而且进程地址空间之间彼此互不相干。但是进程之间也可以选择共享地址空间,这样的进程就叫做线程。

基本上所有linux应用程序都会遵循这个规泛、有栈、有堆、对于JVM来说、也是遵循这个规则、只不过在这个规则上做了一些改进

通过类加载器把Class文件装载进内存空间、装进来以后只是你的字节码,然后你需要去运行、怎么去运行呢 ?图中类加载器子系统下面都是运行区
内存空间里有:
1.方法区:被装载的class的信息存储在Methodarea的内存中。当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件内容并把它传输到虚拟机中。
2.Heap(堆):一个Java虚拟实例中只存在一个堆空间。
3.JavaStack(java的栈):虚拟机只会直接对栈执行两种操作:以帧为单位的压栈或出栈,java栈有个核心的数据、先进后出
4.Nativemethodstack(本地方法栈):通过字面意思、基本是调用系统本地的一些方法、一般在底层封装好了、直接调用
5.地址、在这里边是一个指针的概念、比如从变量到对象怎么做引用、就是地址
6.计数器:主要做字节码解析的时候要记住它的位置、可以理解为一个标记
7.执行引擎:数据、字节码做一些业务处理、最终达到想要的结果
8.本地方法接口:基本是底层系统、比如IO网络、调用操作系统本身
9.本地方法库:为了兼容、实现跨平台有不同的库 、兼容平台性
额外数据信息指的是本地方法接口和本地方法库

 

JMM

java的内存模型

大家可能听过一个词、叫线程安全、在写高并发的时候就会有线程安全问题、java里边为什么会出现线程安全问题呢、因为有JMM的存在、它会把内存分为两个区域(一个主内存、一个是工作内存)工作内存是每个java栈所私有的
因为要运行速度快、需要把主内存的数据放到本地内存中、然后进行计算、计算完以后再把数据回显回去

 
《JVM原理与深度调优(一)》

JMM有两个区域、主内存和栈内存、
java线程可能不止一个、可能有多个栈、现在需要三个线程同时做个运算、主内存初始值x=0 需要把x=0都要装载在自己的内存里边去、相当于有一个
副本、现在初始值和三个栈都是x=0
现在需要做运算
x=x+1
x=x-1
x=0
我们的期望值是x=0,如果是单个线程跑没问题 、取回x=0、运算x=+1、回显进来主内存就是1 、栈1是1,运算x=-1、回显进来主内存就是0、栈1是0

如果多个线程同时执行、结果是不可预期的、正因为有这种结构的存在、当执行x=+1、栈1是x=1  、栈2来不及执行、栈1就已经把x=1写到主内存了 、栈2跟栈3拿过去之后初始值就不是0、可能就是1了 、这样程序就写乱了 

所以在java中就出现了很多锁、来确保线程安全 

 

 运行时数据区

PC寄存器—-线程私有

PC寄存器也叫程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的信号指示器。
每一条JVM线程都有自己的PC寄存器
在任意时刻,一条JVM线程只会执行一个方法的代码。该方法称为该线程的当前方法(Current Method)
如果该方法是java方法,那PC寄存器保存JVM正在执行的字节码指令的地址
如果该方法是native,那PC寄存器的值是undefined。
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

Java虚拟机栈 —-线程私有

与PC寄存器一样,java虚拟机栈(Java Virtual Machine Stack)也是线程私有的。每一个JVM线程都有自己的java虚拟机栈,这个栈与线程同时创建,它的生命周期与线程相同。
虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
JVM stack 可以被实现成固定大小,也可以根据计算动态扩展。
如果采用固定大小的JVM stack设计,那么每一条线程的JVM Stack容量应该在线程创建时独立地选定。JVM实现应该提供调节JVM Stack初始容量的手段。
如果采用动态扩展和收缩的JVM Stack方式,应该提供调节最大、最小容量的手段。
JVM Stack 异常情况:
StackOverflowError:当线程请求分配的栈容量超过JVM允许的最大容量时抛出
OutOfMemoryError:如果JVM Stack可以动态扩展,但是在尝试扩展时无法申请到足够的内存去完成扩展,或者在建立新的线程时没有足够的内存去创建对应的虚拟机栈时抛出。

 本地方法栈—-线程私有

Java虚拟机可能会使用到传统的栈来支持native方法(使用Java语言以外的其它语言编写的方法)的执行,这个栈就是本地方法栈(Native Method Stack)

如果JVM不支持native方法,也不依赖与传统方法栈的话,可以无需支持本地方法栈。

如果支持本地方法栈,则这个栈一般会在线程创建的时候按线程分配。

异常情况:

StackOverflowError:如果线程请求分配的栈容量超过本地方法栈允许的最大容量时抛出

OutOfMemoryError:如果本地方法栈可以动态扩展,并且扩展的动作已经尝试过,但是目前无法申请到足够的内存去完成扩展,或者在建立新的线程时没有足够的内存去创建对应的本地方法栈,那Java虚拟机将会抛出一个OutOfMemoryError异常。

Jave堆—-线程公用

平时所说的java调优就是它
在JVM中,堆(heap)是可供各条线程共享的运行时内存区域,也是供所有类实例和数据对象分配内存的区域。
Java堆载虚拟机启动的时候就被创建,堆中储存了各种对象,这些对象被自动管理内存系统(Automatic Storage Management System,也即是常说的“Garbage Collector(垃圾回收器)”)所管理。这些对象无需、也无法显示地被销毁。
Java堆的容量可以是固定大小,也可以随着需求动态扩展,并在不需要过多空间时自动收缩。
Java堆所使用的内存不需要保证是物理连续的,只要逻辑上是连续的即可。
JVM实现应当提供给程序员调节Java 堆初始容量的手段,对于可动态扩展和收缩的堆来说,则应当提供调节其最大和最小容量的手段。
Java 堆异常:
OutOfMemoryError:如果实际所需的堆超过了自动内存管理系统能提供的最大容量时抛出。

方法区—-线程公用

方法区是可供各条线程共享的运行时内存区域。存储了每一个类的结构信息,例如运行时常量池(Runtime Constant Pool)、字段和方法数据、构造函数和普通方法的字节码内容、还包括一些在类、实例、接口初始化时用到的特殊方法

方法区在虚拟机启动的时候创建。

方法区的容量可以是固定大小的,也可以随着程序执行的需求动态扩展,并在不需要过多空间时自动收缩。

方法区在实际内存空间中可以是不连续的。

Java虚拟机实现应当提供给程序员或者最终用户调节方法区初始容量的手段,对于可以动态扩展和收缩方法区来说,则应当提供调节其最大、最小容量的手段。

Java 方法区异常:

OutOfMemoryError: 如果方法区的内存空间不能满足内存分配请求,那Java虚拟机将抛出一个OutOfMemoryError异常。

 

    原文作者:w787815
    原文地址: https://www.cnblogs.com/w787815/p/6714348.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞