JAVA虚拟机整体的回顾,为提纲类型

JAVA虚拟机是装在操作系统之上的一个应用软件【平台性质】,作用是:将class格式的字节码编译成可执行的机器码。从而,class格式和物理机无关。也就是所谓的java和平台无关。做到跨平台。

从上图可以看出,

JVM核心知识架构

1. 类加载

(1) 类加载

1) 类加载

1.根据全路径名获取二进制的字节流

2.将类的静态存储结构转换成运行时数据结构

3.生成一个Java.lang.class类型的对象。作为内存使用类的一个接口。

2) 类加载器

1.定义

2.3种预定于类加载器

  1. 启动类加载器(根类加载器)。加载路径JAVA_HOME/lib
  2. 扩展类加载器。加载路径JAVA_HOME/lib/ext
  3. 应用类加载器。加载路径ClassPath下的所指定的类库。开发者直接使用。默认的类加载器

3) 双亲委派机制

1.原理

2.优势

  1. 避免类的重复加载
  2. 保证程序安全,避免核心API被修改。【就算自动以核心API,根据双亲委派机制,最终还是会加载启动类加载器加载的类库,而不会加载自定义的方法】

(2) 链接

1) 验证

验证字节码是否满足规范,元数据格式,文件格式等

2) 准备

静态变量分配初始值。如果final修饰,则准备阶段的初始值就是程序中的初始值。如果没有被final修改,则静态变量的初始值给对应数据类型的初始值。比如0.

是给内存对象分配内存的阶段。

3) 解析

将虚拟机中常量池的符号引用(类名、方法名、属性名转换成直接引用

(3) 初始化

执行java中的构造方法,给方法赋值

2. 内存管理机制和垃圾回收器

(1) 内存管理机制

1) 运行时内存区域

线程级别(程序计数器、JAVA虚拟机栈、本地方法栈)和非线程级别(方法区和堆)【线程共享】

1.程序计数器

记录指令码执行的位置,在线程切换回来后能继续执行。

2.JAVA虚拟机栈

线程方法执行的方法调用。一个方法对应一个栈帧。包括局部变量表、操作数栈、动态链接和方法返回地址。

3.本地方法栈

和JAVA虚拟机栈功能相同。区别在于执行本地方法(类库)时使用。

4.方法区

用来存储被虚拟机加载的类的信息。包含类常量、静态方法、即时编译器编译后的代码等。

5.堆

Java虚拟机启动时就创建,目的是存储创建的实例对象。

6.运行时常量池

方法区的一部分。用于存放字面量符号引用

7.直接内存

不是java虚拟机的一部分,运行时候可能会使用到。是计算机的直接内存。NIO技术的时候,通常会被使用到。

2) 内存对象操作

1.内存对象的创建

流程基本上分两步:其一 判断类是否已经被加载到方法区。如果没有加载到方法区,先将类加载到方法区。其二 在新生代的Eden区创建对象【验证、准备、解析和初始化】。

2.内存对象的布局(格式)

对象头【GC代年龄、哈希码等】、实例数据【真实对象数据】和对齐填充数据。

3.内存对象的调用

  1. 直接引用

Java虚拟机栈的局部变量表中的变量引用是对象的地址。就是直接引用。直接通过地址访问对象。

  1. 句柄引用

JAVA虚拟机栈的局部变量表的变量引用是句柄,是间接引用。访问对象需要通过句柄到句柄池中找到句柄对应的地址。然后再访问改地址,就会访问到对象。

3) 常见的内存溢出

1.JAVA堆溢出

堆内存溢出会提示Java heap space。核心是区分内存溢出还是内存泄露。

内存泄露可以根据GCRoot引用链分析,分析在哪个环节,需要对对象的引用及时释放。从而解决泄露的问题。

内存溢出:通过调整JVM堆内存-xmx -xms 来设置。通过分析哪些对象的生命周期可以缩短。目的是及时的释放内存。避免内存溢出。

2.JAVA虚拟机栈和本地方法栈溢出

栈溢出会提示”stack over flow”

提示这个错误,两种可能:其一 栈深度不够方法调用使用(可能性比较小-通常支持2000)。其二 栈申请扩展空间,没有足够的空间提供。【常见的原因是线程数量太多,每个线程都有自己独立的虚拟机栈,线程数多可能造成空间申请不够,从而报错--解决的思路:调小虚拟机堆的大小或者减小栈对象的大小,从而确保更多的线程能存在】

3.方法区和运行时常量池溢出

提示”PermGen space”

方法区存放的是Class的相关信息。比如类名、修饰符、常量池、字段描述、方法描述等。运行时产生大量的类填充方法区,直到溢出。提示”PermGen space”

通常是动态代理等字节码增强技术会导致方法区的内存溢出。

4.直接内存溢出

通常为NIO等技术会造成内存溢出。如果内存溢出,可以核心关注NIO技术实现部分的代码。

(2) 垃圾回收器

1) 对象存活判断

1.引用计数法

对象表里面记录对象被引用的次数。为0代表没有被引用。可以不回收。大于0代表被引用,说明对象存活着。

缺点:不能解决循环引用的问题。

2.可达性分析法

从GCRoot节点向下检索,如果有到达对象的路径,则该结点存活。切路径为引用链。

2) 垃圾回收算法

1.标记-清楚法

首先标记需要清除的对象,然后在统一清除。

2.标记-复制法

将内存分为两部分S1和S2.每次只使用一部分。对使用的那部分内存比如S1进行标记存活的对象。让后将存活的对象移动到另外一个内存区域比如S2,之后对原来内存S1整体进行回收。

3.标记整理法

将内存存活的对象进行标记,然后将存活的对象向一段移动。然后清理到端边界以外的内存。

4.分代收集法

根据对象存活周期的不同将内存划分为几块。分别为新生代老年代

3) 垃圾回收器

新生代的三个垃圾收集器都是使用复制算法。Serial是单线程、parnew 使用多线程、parallel scavenge 并发多线程。

老年代中,serial old 是标记整理法、parallel old是标记复制算法、CMS采用的是标记-清除法(为了更快的速度和吞吐量)

从线程角度来看:单线程有serial和parallel old 是单线程。ParNew 、parallel scavenge 和serial old是多线程。CMS是并发多线程。

1.Serial收集器(标记-复制法)-新生代

算法采用 标记-复制 算法。是一个单线程的收集器。只对新生代进行垃圾回收。它在进行垃圾回收的时候,必须停止所有的线程工作。Stw --stop the word。

2.ParNew收集器(复制算法)-新生代

ParNew 是serial收集器的多线程版本。也针对新生代。算法也是 标记-复制算法。

3.Parallel scavenge 收集器(停止-复制法)-新生代

并行的多线程,和ParNew类似。

4.Serial Old 收集器(标记-整理法)-老年代

老年代的收集器,同样是单线程收集器。使用的算法是标记-整理

5.Parallel Old 收集器(停止-复制法)-老年代

CMS(Concurrent Mark Sweep )收集器(标记-清除法)-老年代

4) 详细熟悉CMS流程

3. 执行引擎

个人理解,执行引擎的功能是方法的执行。具体分为三部分内容。方法的表达【栈帧的结构】。方法的调用【确定方法调用的版本】和方法的执行【字节码转成机器码的方法】

(1) 运行时栈帧结构

1) 局部变量表

函数的参数和定义的局部变量。有基本类型数据和引用类型。可能是直接引用也可能是间接引用。

方法执行过程中,如果方法中大内存变量使用完成后,可以在方法之前赋值为null,这样垃圾回收器可以提前对其进行回收。不用等到方法执行完成才对其回收。可以尽早的释放内存。

2) 操作数栈

操作数栈主要进行计算使用的。在数据放入到局部变量表的时候,可能会首先放入操作数栈,然后在弹出栈放入局部变量表中。在进行计算的时候,也需要根据指令从局部变量表中获得数据,然后压栈到操作数栈,之后弹出,进行计算。这里和数据结构的运算式后序表达使用栈计算的知识点重合。

3) 动态链接

动态链接是指在栈帧中存储当前方法在方法区的运行时常量池中的符号引用。

运行时常量池中的符号引用一部分在类加载的链接阶段(具体为链接结点的解析过程中)替换成直接引用。另外一部分是在每一次运行期间转换成直接引用。这部分称之为动态链接。

4) 方法返回地址

方法不论正常退出还是异常退出,都需要返回到方法调用的位置,保证程序继续向下执行。所以,栈帧通过方法返回地址找到返回方法退出时候返回的位置。

(2) 方法调用

确定方法调用的版本

1) 解析

如果在调用之前就可以确定方法的版本,并且在程序运行期间,方法的版本不会发生变化。那么,在解析阶段就将代表方法的符号引用替换成方法的直接引用。这种方法的调用称之为解析。

2) 分派

分派分为静态分派和动态分派。最典型的两种例子为重载重写

静态分派:根据函数的参数声明来确定方法的版本【重载--相同的方法名,不同的参数类型,根据参数类型来确认方法的版本】定位函数版本的时候不是根据参数的具体类型。而是参数的定义类型来确定的。

动态分派:根据实际的类型来确认方法的版本。根据运行时候实际对象的不同,才确定调用哪个对象中的方法。【重写--最能体现】

(3) 执行引擎

1) 解析执行

JVM得到字节码,通过interpreter解释器将字节码翻译成机器码。然后执行。

Hotspot的解析执行器是基于栈的指令集的解析执行器。

2) 编译执行

JVM提供及时编译技术,将一段字节码编译成机器码,避免解析执行。从而提升效率。

比较可知。对于循环的代码块或者多次执行的代码块每次解析执行效率低于一次编译,后续直接执行机器码。而编译执行的目的就是为了解决多次执行代码的编译。

4. JAVA内存模型和线程

(1) JAVA内存模型

JAVA的内存模式和JVM的运行时内存区域是从不同角度来分析JAVA的内存的。JAVA内存模型包含线程共有的主内存和线程私有的工作内存。

之所以这么设计,主要是解决计算机运算速度和硬件速度不匹配的问题。为了解决这个问题,在线程计算之前,首先将硬件磁盘中的数据加载到主内存,作为多线程处理所共享。线程处理的时候从主内存获取数据,备份到各个线程私有的工作内存。然后进行计算,或者需要传递给执行引擎进行执行。所以,如果忽略磁盘数据到主内存数据的话。数据将在主内存、工作内存和引擎中相互传递。这里面有多线程的问题,也有数据线程同步的问题。也有多线程在JVM中的执行的问题。

1.主内存

线程共享的内存区域。主要是堆和方法区【堆-容易理解,线程共享的数据大多数对象、方法区-线程共享的可能是运行时常量池中的字面常量等】。

2.工作内存

主要是指线程私有的内存。主要是JAVA虚拟栈和拷贝主内存变量的寄存器。

3.内存间的交互操作

Lock、read、load、use、assign、store、write、unlock

上面8个命令,代表了对主内存变量锁定lock、从主内存读取到工作内存read、到将读取到工作内存的数据赋值给工作内存中的变量load、再到将工作内存的变量传递给执行引擎use、将执行引擎中变量赋值给工作内存变量assign、将工作区变量读取到主内存store、将工作内存中读取到主内存的变量赋值给主内存变量write、对主内存变量进行解锁unlock

4.Volatile 的特性

可见性:其中一个线程对volatile修饰的变量,对其它线程立即可见。【实现的方法:修改之后,立即同步到主内存,其次,如果工作内存需要使用该变量,不使用原有的内存数据,立即从主内存同步】

在条件苛刻的情况下【volatile修饰的变量 运算结构不依赖当前值】,也可以保证操作的原子性。

Volatile修饰的变量可以阻止“指令重排序”的优化

5.线程的原子性、可见性、有序性

原子性:一个操作,要么执行、要么没有执行。

可见性:一个线程对变量的修改完成后、其它线程立即能发现改变。

有序性:如果在本线程观察,所有操作是有序的、如果在其它线程观察,所有操作是无序(乱序)的。

对共享代码必须按照代码的逻辑顺序执行,避免因为代码重排优化而导致错误。

6.先行发生原则

代码先行原则体现的是两个操作之间的偏序关系。假设操作A先于操作B执行。那么,操作B运行之前能观察到操作A的影响。

(2) 线程

1) 线程的实现

1.使用内核线程的实现

内核线程是由操作系统支持的线程。这种线程的切换是通过操纵调度器scheduler对线程进行调度。

用户一般不会直接使用内核线程,而是使用内核线程的高级接口。--轻量级线程(LWP)。每个线程都是一个独立的调度单元。所以,单个线程的阻塞不会影响整个线程的进行。

但是,挂起或者启动操作需要CPU在用户模式和内核模式之间切换,所以,代价比较高。

2.使用用户线程的实现

完全建立在用户空间的线程库上的线程。线程的建立,同步、调度都可以完全在用户模式下完成。不需要内核的帮助。

2) 线程的调度

线程的调度本质是给线程分配处理器权限的过程。

1.协同式线程调度

线程执行时间由线程自己控制。自己工作完成之后,在主动通知系统,切换到其它线程执行。

优点:实现简单,没有线程同步问题。

缺点:如果线程阻塞,不能主动退出,一直占用处理器。

2.抢占式线调度

对于多个线程,由系统来分配处理时间。线程的切换不由线程本身控制(thread.yield 可以主动让出自己对处理器的使用权限,但是获取权限还是需要系统给分配)。JAVA使用的线程调度方式就是抢占式。

3) 用户模式和内核模式

1.内核模式和用户模式定义

内核模式下,代码具有对硬件的所有控制权,所有的CPU执行指令。内核模式下,错误是灾难性的。

用户模式下:用户对硬件和内存是没有直接访问权限的。对内存和硬件的访问是通过系统接口来进行访问的、用户模式下,错误是可以恢复的。

2.为什么要有内核模式和用户模式

设置用户模式主要的目的是控制应用程序对系统资源无限制的访问。因为,通过系统接口对系统硬件和内存的访问,可以进行验证。

3.用户模式切换到内核模式,再回到用户模式的流程

通常应用软件是在用户模式下进行的。假设用户模式下需要访问硬件或者内存。需要调用系统接口进行访问,首先会参数进行验证。其次,CPU切换到内核模式下进行处理。完成后再切换到用户模式,继续执行应用程序软件。

(3) 线程安全

1) JAVA线程安全的5个种类

从线程安全的安全程度角度从强到弱的角度来分析。共享数据分为5类。

1.不可变

不可变的常量或者对象。因为不可变,多个线程最多只能读取,不能修改其值,所以,线程安全。

2.绝对线程安全

不管运行情况如何,调用者都不需要额外的同步手段就能达到线程安全。

3.相对线程安全

通常来说是线程安全的【方法都加了synchronize】。单对于一些特定的调用,需要调用端额外的调用手段。比如vector

4.线程兼容

本身不是线程安全的【方法没有同步方案】。需要调用端同步来保证安全性。

5.线程对立

无论是否在调用端采取错误,都不可能在多线程中并发使用代码。

2) 实现线程安全的方法

实现线程安全的方法。

1.互斥同步

互斥同步也称之为阻塞同步。互斥是因,同步是果。互斥是方法,同步是目的。

互斥:多个线程并发访问共享数据,保证一个时刻只有一个线程能访问共享数据。

互斥同步是基于悲观锁的角度来处理的一个思路。认为必须加锁保证互斥,才能解决多线程的竞争。

互斥同步的两种方案:synchronize【在字节码中,添加两个关键字】和ReentrantLock。

2.非阻塞同步

基于检测冲突的乐观策略。认为不会发生冲突。直接对数据进行访问。如果发生冲突,再进行补救措施【不断重试,直到成功】。

这种操作用硬件指令来完成。如下:

l 测试并设置

l 获取并增加

l 交换

l 比较并交换

l 加载链接/条件存储

3.无同步方案

没有数据竞争,不需要同步方案。

(4) 锁优化

1.自旋和自适应自旋

线程状态的切换需要在用户态和核心态之间切换。代价比较大。因此,可以尝试等待一段时间后,如果仍然未申请到需要的资源,在切换用户状态。其中等待一段时间,进行忙运行,称之为自旋。如果等待的时间根据运行自行调整就是自适应自旋。

2.锁取消

根据判断,如果永远不会发生竞争,则取消锁机制。

3.锁粗化

如果一段代码多次进行加锁和取锁操作。可以考虑将锁的范围扩大到整个循环之外。--核心思想是 加锁和取锁消耗比较大,所以,加锁取锁操作尽量不要出现在循环中。可以放在循环外部。

JVM 整体回顾(一)的更多相关文章

  1. JVM基础回顾记录(二):垃圾收集

    垃圾收集流程&HotSpot对该流程的实现方式 上一篇介绍了jvm的内存模型,本篇将介绍虚拟机中最为复杂的一部分:垃圾收集,本篇会从垃圾回收前的准备工作到后面的收集阶段的方式以及HotSpot ...

  2. JVM基础回顾记录(一):JVM的内存模型

    JVM的内存模型&垃圾收集算法 JVM内存模型 JAVA程序执行的基本流程(基于HotSpot): 图1 1.程序计数器 程序计数器是一块较小的内存空间,是当前线程执行字节码的行号指示器,字节 ...

  3. (一)JVM快速回顾总结

    1,JVM内存结构(JVM体系概述,java8后的JVM) 2,GC的作用域(方法区和线程堆) 3,常见的垃圾回收算法 (1)引用计数 循环引用问题 (1) 那些对象可以作为GC_Root 虚拟机栈( ...

  4. JVM 整体流程介绍

    一. JVM自身的物理结构 从图中可以看出 JVM 的主要组成部分 ClassLoader(类加载器),Runtime Data Area(运行时数据区,内存分区),Execution Engine( ...

  5. Struts2的整体回顾(Action, 拦截器, 值栈, OGNL表示式, ModelDriven)

    ValueStack里有map(request, session, attr, parameters)和对象栈. Map调用的方法: ActionContext.getContext().put(k, ...

  6. HTML5+CSS3整体回顾

    http://blog.poetries.top/2016/10/19/HTML5+CSS3%E5%9F%BA%E7%A1%80%E5%9B%9E%E9%A1%BE%20/ 这篇文章主要总结H5的一些 ...

  7. JVM垃圾回收重要理论剖析【纯理论】

    JVM学习到这里,终于到学习最兴奋的地方了---垃圾回收,在学习它之前还得对JVM垃圾回收相关理论知识进行了解,然后再通过实践来加深对理论的理解,下面直接开始了解相关的理论: JVM运行时内存数据区域 ...

  8. JVM学习笔记——内存结构篇

    JVM学习笔记--内存结构篇 在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的内存结构部分 我们会分为以下几部分进行介绍: JVM整体介绍 程序计数器 虚拟机栈 本地方法栈 堆 方法 ...

  9. 成都亚马逊AWSome Day回顾

    6月25日我和公司同仁一起参加了亚马逊在成都的第一场AWSome Day活动.整个活动时间异常紧促,短短一天包含了7堂session,讲师的狂轰乱炸使得我们同学们普遍觉得比上班累多了.好了,废话不多说 ...

随机推荐

  1. jvm优化案例

    案例1 survivor区太小,每次Minor GC存活的对象进入老年代,导致老年代可用空间不足,经常发生FULL GC,导致系统变慢 案例问题描述 有一个数据计算系统,从mysql和其他数据源提取数 ...

  2. 实验1:Mininet源码安装和可视化拓扑工具

    一.实验目的 掌握 Mininet 的源码安装方法和 miniedit 可视化拓扑生成工具. 二.实验任务 使用源码安装 Mininet 的 2.3.0d6 版本,并使用可视化拓扑工具生成一个最简拓扑 ...

  3. python 给IDLE添加行号

    [LineNumbers] enable=1 enable_editor=1 enable_shell=1 visible=1 [LineNumbers_cfgBindings] linenumber ...

  4. Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(一)

    标题 Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(一) 技术 Spring Boot 2.Spring Security 5.JWT 运行环境 ...

  5. .NET Standard 系列

    .NET Standard 是一套正式的 .NET API 规范,有望在所有 .NET 实现中推出. 推出 .NET Standard 的背后动机是要提高 .NET 生态系统中的一致性. ECMA 3 ...

  6. 列举python的可变类型和不可变类型

    可变的# unhashable type: 'list'# unhashable type: 'dict'# unhashable type: 'set'# 不可变# hashable type:st ...

  7. 基于Intel x86 Android的RAD游戏开发

    zip文件还包含编译的"MonkeyGame-debug".可以在模拟器中运行的二进制文件.在"game.build"文件夹中有一个HTML5 build.在C ...

  8. 洛谷 P3413 【萌数】

    敲完这篇题解,我就,我就,我就,嗯,好,就这样吧... 思路分析: 首先我们要知道一个回文串的性质--假如说一个[l-1,r+1]的串是回文的,那么[l,r]一定也是回文的. 所以我们只要记录前一个数 ...

  9. volatile型变量语义讲解一 :对所有线程的可见性

    volatile型变量语义讲解一 :对所有线程的可见性 一.volatile变量语义一的概念 当一个变量被定义成volatile之后,具备两个特性: 特性一:保证此变量对所有线程的可见性.这里的&qu ...

  10. 写给前端同学的C++入门教程(一):概述和环境搭建

    说明:本人是前端er,因为最近对 UE4(一个游戏开发引擎)产生了兴趣,而这个引擎源开发游戏时需要用到 C++ ,所以就开始入坑 C++ 了.现将自己学习 C++ 的笔记整理并分享出来,以便一些想入门 ...