JVM虚拟机的前世今

Java程序执行过程

javac编译成.class文件,然后jvm将其加载到方法区,执行引擎将会执行这些字节码,执行时会翻译成操作系统相关函数,jvm作为.class文件的翻译存在,输入字节码,调用操作系统函数。

过程如下:Java文件->编译器->字节码->jvm->机器码。

JVM、JRE、JDK的关系

JVM充当翻译的角色,把class翻译成机器识别的代码。

JRE包含jvm,还提供很多类库(也就是jar包,比如读取或者操作文件,连接网络,使用IO等)JVM标准加上实现的一大堆基础类库,组成java运行时环境。

JDK:负责编译代码,调试代码,打包代码,有时候还需要反编译代码,jdk提供了一些非常nice的工具,比如javac(编译代码),java -jar打包代码,javap反汇编等

JVM的作用是:从软件层面屏蔽不同操作系统在底层硬件和指令的不同。

同时JVM是一个虚拟化的操作系统,类似Linux或者Windos的操作系统,只是它架在操作系统上,接收字节码也就是class,把字节码翻译成操作系统上的机器码进行执行。

JVM的发展

常见的Hotspot虚拟机,Jrocket,J9,TaobaoVM等

JVM整体知识模块

JVM能涉及非常庞大的一块知识体系,比如内存结构、垃圾回收、类加载、性能调优、JVM自身优化技术、执行引擎、类文件结构、监控工具等。

但是所有的知识体系这,都和内存结构有一定的联系:

比如垃圾回收就是内存、类加载加载到的地方也是内存、性能优化也涉及到内存优化、执行引擎与内存密不可分、类文件结构与内存设计有关系、监控工具也会监控内存,所以内存结构处于JVM中核心位置,也是属于我们入门JVM学习的最好的选择。

同时JVM是一个虚拟化的操作系统,所以除了要虚拟化指令之外,最重要的一个事情就是需要虚拟化内存,这个虚拟化内存就是JVM的内存区域。

JVM的内存区域

运行时数据区域

在JVM中,JVM内存主要分为堆、程序计数器、方法区、虚拟机栈和本地方法栈等

按照与现场的关系划分为:

线程私有区域、线程共享区域

直接内存:没有被虚拟化的操作系统上的其它内存(比如操作系统上有8G内存,被JVM虚拟化了3G,那么还剩5G,JVM是借助一些工具使用这5G内存的,这部分内存称为直接内存)

虚拟机栈

栈的数据结构:FILO先进后出

虚拟机栈的作用:在JVM运行过程中存储当前线程运行方法所需的数据、指令、返回地址。

虚拟机栈是基于线程的:在线程的生命周期这,参与计算的数据会频繁的入栈和出栈,栈的生命周期和线程是一样的。

虚拟机栈的大小缺省为1M,可以参数 -Xss 调整大小,eg:-Xss256k

栈帧

在每个方法被调用的时候,都会创建一个栈帧,并入栈,一旦方法完成相应的调用,则出栈

栈帧大体包含四个区域(局部变量表、操作数栈、动态连接、返回地址)

1、局部变量表:主要存放我们Java的八大基础数据类型,对象的引用地址

2、操作数栈:存放java方法执行的操作数的,他就是一个栈,先进后出,操作的元素可以是任意的java数据类型。

3、动态连接:java语言特性多态

4、返回地址:正常返回(调用程序计数器这的地址作为返回),异常返回(通过异常处理表来确定)

同时,虚拟机栈这个内存也不是无限大他有大小限制,默认情况下是1M

程序计数器

较小的内存空间,当前线程执行的字节码的行号指示器:各线程之间独立存储,互不影响。

程序计数器是一块很小的内存空间,记录各个线程执行的字节码的地址,例如,分支、循环、跳转、线程恢复等都依赖于计数器。

栈帧的执行对内存区域的影响

在JVM中,基于解释执行的这种方式是基于栈的引擎吗,这个栈就是操作数栈。

运行时数据区及JVM的整体内存结构

本地方法栈

管理本地方法的调用,服务的对象是native方法

方法区

存放已被虚拟机加载的类的相关信息,包括类信息、静态变量、运行时常量池、字符串常量池。

方法区是JVM对内存的逻辑划分,在jdk1.8之后使用元空间来实现方法区。

常量池:存放编译期间生成的各种字面量和符号引用,字面量(字符串、基本类型常量(final修饰的变量)),符号引用则包括类和方法的权限的名(XX/XX/XX)

符号引用

编译时不知道引用类的实际内存地址,使用符号引用来代替。

而在类装载器装载该类时,可以通过虚拟机获取引用类的实际内存地址,也就是将符号引用替换为引用类的实际内存地址即直接引用地址。

即在编译时用符号引用来代替引用类,在加载时再通过虚拟机获取该引用类的实际地址。

常量池与运行时常量池

当类加载到内存之后,JVM就会将class文件常量池的内容存放到运行时常量池中;在解析阶段,JVM会把符号引用替换为直接引用(对象的索引值)

例如:类中的一个字符串常量在class文件中时,存放在class文件常量池中的;在JVM加载完类之后,JVM会将这个字符串常量放到运行时常量池中,并在解析阶段,指定该字符串对象的索引值,运行时常量池是全局共享的,多个类公用一个运行时常量池,class文件中的常量池多个相同的字符串在运行时常量池只会存在一份。

常量池有很多概念:

包括运行时常量池、class常量池、字符串常量池

元空间

java8为什么使用元空间替代永久代?

官方给出的解释是:

移除永久代是为了融合HotspotJVM 与JRockit VM 而做出的努力,因为JRockit没有永久代,所以不需要配置永久代。

永久代内存经常不够用或发生内存溢出,抛出异常OOM,这是因为jdk1.7版本中,指定PermGen区大小为8M,由于PermGen 中类的元数据信息在每次Full GC的时候都可能被搜集,回收率都偏低,还有为PermGen分配多大的空间很难确定。

常见的垃圾回收就是操作堆的

java对象可分为基本数据类型和普通对象

对于普通对象来说,JVM会首先在堆上创建对象,然后在其他地方使用的其实是他的引用。

对于基本数据类型,有两种情况,当你在方法体中声明了基本数据类型对象,他就会在栈上直接分配其他情况,都是在堆上分配。

堆大小参数

-Xms:堆的最小值

-Xmx:堆的最大值

-Xmn:新生代大小

-XX:NewSize:新生代最小值

-XX:MaxNewSize:新生代最大值

直接内存(堆外内存)

如果使用NIO,这块区域会被频繁使用,在java堆内可以用directByteBuffer对象直接引用并操作。

小结:

1、直接内存主要通过DirectByteBuffer申请内存。

2、其它堆外内存,主要使用了Unsafe或者其它JNI手段直接申请的内存

堆外内存泄漏是非常严重的,他的排除难度高,影响大,甚至造成主机死机

传送门

原文地址:https://www.cnblogs.com/blackCatFish/p/13920710.html