1、内存模型

  起源:在计算机系统,加入了一层读写速度尽可能接近处理器运算速度的高速缓存来作为内存和处理器之间的缓冲。

  问题:缓存一致性。在多处理器系统中,每个处理器都有自己的高速缓存(抽象为工作内存),但是这些处理器又共享了同一主内存。

  解决:抽象出来了 内存模型 ,即对主内存或工作内存的读写访问,用5个操作来实现不同线程之间的数据交互(通信交互)。(6个操作 read、load、use、assign、store、write)

  引申出:6个操作不是原子性的,所以可能数据不安全,所以多线程的数据变化不确定,所以多线程一般不安全。

  并且:除了加高速缓存,处理器内部的运算单元为了被充分利用,还对输入的代码进行了执行顺序的重排序重排序:执行结果一致,不保证定义的代码的先后顺序执行,并且重排序只针对多个线程,或者前后代码之前没有依赖关系。如果线程之前彼此有依赖关系,或前后代码有依赖关系,就不会出现重排序。

  问题:重排序不会对多线程依赖或前后代码依赖,是靠 内存屏障 来实现的。内存屏障:一条 CPU 指令,确保某些特定的操作的顺序(主要是有依赖关系的写),可以理解为强制刷新处理器的写缓存(多线程中每个线程的多线程)。注明:单 CPU 不需要内存屏障。

  实现内存屏障的一些体现例如 volatile 字段、synchronized 字段、 final 字段以及 等。

  volatile:JVM 提供的最轻量级的同步机制,它的工作内存写操作,会无效其他处理器所持有、指向的同一地址的缓存行,所以其他处理器可以立即见到 volatile 修饰的字段的最新值。(多读少写:频繁写操作会一直强制刷新缓存,影响性能)

  synchronized:重量级锁,通过加锁、解锁,操作具有原子性,使得线程安全。但是不会强制刷新主内存。

  扩展知识

    原子性可见性(例如 volatile 能保证新值立即刷新到主内存,所以该线程的数据对其他线程可见)、有序性(本线程内观察,所有线程可见。一个线程观察另一个线程,所有操作都是无序 --- 指令重排序和主内存和工作内存同步延迟 两个原因,导致无序,所以 volatile 禁止指令重排序,变成了有序)

2、垃圾回收

  起源:Java 程序在运行过程中,时时刻刻在创建对象,如果不对已经不在使用的对象进行回收,那么很快就会出现 OOM 情况。

  解决:JVM 定义了垃圾回收技术来实现对不在使用的对象进行回收。

  技术实现

    1)、确定对象的死亡计数法(仅了解)、可达性分析 --- 从一系列 GC Roots 对象作为起点,搜索能被该 GC Roots 引用到的对象。没有引用到的对象,就可以标记死亡 。多线程情况下可达性分析可能存在漏报和误报。(GC Roots :Java 方法栈帧中的局部变量、已加载的静态变量、JNI handles、已启动未停止的 Java 线程。)

    2)、回收时刻:在安全点的时候进行垃圾回收,间接的减少垃圾回收的暂停时间。垃圾回收需要开启一个线程来回收,最开始的时候是暂停其他线程,仅放垃圾回收的线程运行。(安全点:JNI 本地代码等,对程序的对象之类无影响的线程操作)

    3)、算法标记清除算法 --- 效率低和产生大量不连续的内存碎片、复制算法 --- 代价太高昂,只能利用到1/2的内存、标记整理算法

  JVM 的垃圾回收的堆划分:新生代、老年代。

    新生代:Eden 区和两个一样大小的 Survivor 区。 Eden 区约到,浪费的堆空间越小。当 Eden 区域的内存耗尽,就出发一次 Minor GC,收集新生代的垃圾。存活下来的对象,就送到 Survivor 区。

    老年代:新生代的对象经过15次的赋值或者大对象等,就会把对象放入老年代。

  Java 9 以后的垃圾回收器:G1(横跨新生代和老年代的垃圾回收器)。

  扩展知识

    每个线程可以向 JVM 连续申请一段连续的内存,用来放该线程的多个对象。该操作需要加锁,并且维护两个指针,一个指针指向空余内存的起始位置,一个指针指向该段内存的末尾。接下来的 new 指令,直接通过指针加法来实现,把指向空余内存位置的指针加所请求的字节数。当该段线程使用完后,就再申请一段内存。

3、双亲委派机制

  起源:为了保证程序的安全性,保证不能因为人会的 Java 文件注入,加载出来多个相同的 class 文件,从而使程序发生错误。(如果没有双亲委派机制,子类加载器自己就加载了一个API类,并且父类也加载了系统API类,在引用的时候,就会不知道引用哪个API类而报错。)

  解决:双亲委派机制。

  实现:顶层为启动类加载器,然后下一级为扩展类加载器,再下一级为应用程序加载器,再下一级为自定义类加载器。除了顶层的启动类加载器,其余的类加载器都有自己的父类加载器。不是以父子关系存在,而是以组合关系来复用父加载器的代码。

  当一个类加载器收到类加载请求(自己未加载过),将求情往上委派给父类加载器,一直往上,到顶层启动类加载器。 当父类加载器无法完成这个加载请求,子加载器才会去加载。

4、对象逃逸

  起源:方法内创建的对象,作为返回参数返回出去后,被其他方法引用。或者对象作为参数,传入下级方法等操作。避免了该对象被标记为死亡的(方法结束,对该对象的引用就断开,就可以视该对象死亡,可用垃圾回收器回收)。这就是对象逃逸

  书本语言:新建对象放入堆中,除了创建对象的线程,其他线程也可获得该对象的引用。

  优化:(前提非逃逸的对象)

  锁消除:即时编译器能证明该对象不逃逸,说明其他线程对本线程的对象无引用,那么就不需要对该对象进行加锁、解锁,这就是锁消除。

  栈上分配:逃逸分析证明对象不逃逸, JVM 就可以将对象分配到栈上,new 结束,栈弹出对象就可以。(无需借助垃圾回收器回收该对象,因为弹栈了)

  标量替换:将原来对对象的字段的访问,替换成一个个局部变量的访问(因为对象只在本线程中,所以字段的值也只不会被其他线程可见)。这些字段没有分配实际内存,可以和栈上分配一样,或者直接存放在寄存器中,不需要内存空间。

5、类的加载到卸载流程

  加载 --- 验证 --- 准备 --- 解析 ---初始化 --- 使用 --- 卸载

  加载:将 Java 文件用类加载器加载为 class 文件。

  验证:主要验证魔数、常量池等,验证 class 文件是否符合 JVM 规范。

  准备:将类变量分配内存,设置类变量初始值。例如定义一个类变 int i = 2,则设置初始值 i=0。

  解析:将常量池的符号引用,替换为直接引用(指向目标的指针或者是一个能直接定位到目标的句柄)。

  初始化:实例变量初始换、实例代码块初始化、构造函数初始化。

6、JVM 实现反射

  含义:允许正在运行的 Java 程序观测、甚至修改程序的动态行为。IOC 就是依赖于反射机制。(但是反射机制比较慢,性能开销大 --- 其实只是相对的 --- 原因:变长参数方法导致的 Object 数组、基本类型的自动拆装箱、方法内联。)

  方式:Class.forName、getClass()、.class。

  实现:委派实现、本地实现 --- 每个 Method 实例的第一次反射调用都会生成一个委派实现,所委派的具体实现便是一个本地实现

  委派实现:之所以采用委派实现,便是为了能够在本地实现以及动态实现中切换(某个反射调用的次数在15次之下,采用本地实现。达到16次,便开始动态实现,即动态生成字节码)。

  扩展知识

    Class.getMethod 会遍历该类的公有方法,Class.forName 会调用本地方法。所以操作都 很费时。其实实践中我们会在应用中缓存 Class.forName 和 Class.getMethod 的结果。

    调优方法:方法内联、逃逸分析、不自动拆装箱。

    方法内联:在编译过程中遇到方法调用时,将目标方法的方法纳入编译范围之中,并取代原方法调用的优化手段。(类似株连)

7、JVM 内存溢出

  堆溢出:堆要不断的创建对象,如果避免了垃圾回收来清除这些对象,就会产生JVM内存溢出。一般手段是通过内存映像分析工具对Dump出来的堆转储快照进行分析,分清楚到底是内存泄露还是内存溢出。

  虚拟机栈和本地方法栈溢出:线程请求的栈深度大于虚拟机所允许的最大深度。或者虚拟机在扩展栈时无法申请到足够的内存空间。

  方法区和运行时常量池溢出:一个类要被垃圾回收器回收,判断条件是苛刻的。

  本机直接内存溢出

  当出现内存溢出时,就得定位是哪里溢出,并且做出相关优化。

  JVM优化:调整一些 JVM 例如新生代、老年代等内存区域的大小(有钱能做到扩展服务的事情,那就增加服务器吧!)。另外就是代码的优化,尽量少创建对象,少创建大对象等。

  JVM 优化参考链接:https://pengjiaheng.iteye.com/blog/552456

8、Java 对象的内存布局

  新建对象:Object.clone 方法、反序列化、Unsafe.allocateInstance 方法和 new 语句、反射机制(通过调用构造器来初始化实例变量)。

  分配内存:通过 new 出来的对象,内存涵盖了所有父类中的实例变量,就算父类的私有实例变量,子类无法访问,但是子类的实例还是会为父类的实例变量分配内存。

  压缩指针

  Java 虚拟机中每个 Java 对象都有一个对象头,由标记字段和类型指针(类型指针指向该对象的类)所构成。

  64位的虚拟机中,对象头的标记字段占64位,类型指针占64位。通过压缩指针,将堆中原本的64为的 Java 对象指针压缩为32位。这样一来,对象头的类型指针也会被压缩为32位。标记字段还是64位。这样对象头的大小就从16字节变为12字节。

  原理:内存对齐 --- 每个对象的地址对齐到 8 的倍数。但是如果一个对象用不到8N个字节,空白的那部分空间就浪费了。还有个原因是让字段只出现在同一 CPU 的缓存行中,如果字段不对齐,可能出现跨缓存行的字段。虽然浪费了一定的空间,但是可以减少内存行的读取次数,总的来说对于 JVM 的性能得到了一定的提高。

  扩展知识

    为什么引入基本类型:Integer 类为例子,仅有一个 int 类型的私有字段,占4个字节。但是类型指针就占64位(8字节),每一个 Integer 对象的额外内存开销至少是200%。

JVM 第一次学习总结(2019年4月)的更多相关文章

  1. SPSS 2019年10月24日 今日学习总结

    2019年10月24日今日课上内容1.SPSS掌握基于键值的一对多合并2.掌握重构数据3.掌握汇总功能 内容: 1.基于键值的一对多合并 合并文件 添加变量 合并方法:基于键值的一对多合并 变量 2. ...

  2. 19.go语言基础学习(下)——2019年12月16日

    2019年12月16日16:57:04 5.接口 2019年11月01日15:56:09 5.1 duck typing 1. 2. 接口 3.介绍 Go 语言的接口设计是非侵入式的,接口编写者无须知 ...

  3. 16.go语言基础学习(上)——2019年12月16日

    2019年12月13日10:35:20 1.介绍 2019年10月31日15:09:03 2.基本语法 2.1 定义变量 2019年10月31日16:12:34 1.函数外必须使用var定义变量 va ...

  4. 2019年12月12日英语学习-Will I Or Won't I ?

    这节英语课上的内容没记住多少东西,觉得这个主题太枯燥了,不过整堂课和外教沟通交流还是不错的,因为这节课就我一个学生.给我了充分的机会去张嘴交流互动. 也没记住什么东西,不知道写什么.只记住将要决定做某 ...

  5. java后端学习记录2019

    学习计划 2019年计划 1.学习计算机基础,并加以实践.包括LeetCode刷题.数据库原理(索引和锁.Sql优化等).网络协议(Http.Tcp).操作系统(加深Linux).<Http权威 ...

  6. 36.React基础介绍——2019年12月24日

    2019年12月24日16:47:12 2019年10月25日11:24:29 主要介绍react入门知识. 1.jsx语法介绍 1.1 介绍 jsx语法是一种类似于html标签的语法,它的作用相当于 ...

  7. CoachAI 2019年12月~2020年3月实习总结

    CoachAI 2019年12月~2020年3月实习总结 一句话总结: 方向对口,大有所得. CoachAI公司简介   CoachAI是一家做在线健身的公司,目前在手机端和IOS端都有自己的服务,并 ...

  8. JVM快速学习

    最近开始了全面的JAVA生态环境学习,因此,JVM的学习是必不可少的一个环节.和.NET的CLR一样,一起的JAVA应用均跑在JVM虚拟机上,不过相对我们只能干看看的CLR,JVM有很大的灵活性,可以 ...

  9. 2019 年 3 月 iOS程序员面试心得总结,请大家多多指教!

    序言: 今年2月中下旬因为个人原因,换了一份工作,3月初期间面试了有3,4家,基本都是D轮或者刚刚上市的公司,也有上榜的BAT,也从他们的面试笔试中看到了自己的一些不足,于是就想写出来和大家分享一下, ...

随机推荐

  1. jQuery的学习笔记

    JQuery学习笔记 Chapter one初识jQuery 1.2测试jQuery 在jQuery库中,$是jQuery的别名,如:$()相当于jQuery() 注意:在使用JQuery进行开发的时 ...

  2. Oracle12c中多宿主容器数据库(CDBs)和可插拔数据库(PDBs)新特性之运行脚本

    对开发者和DBA们来说,对shell脚本批量任务的影响成了多宿主选项带来的最大改变之一.因为多宿主环境通过服务来连接到可插拔数据库,因此,依靠CRON和OS认证成了换成多宿主环境后的一个最大问题.本文 ...

  3. Java基础:Java虚拟机(JVM)

    当我们第一次学习Java时这些原理上的东西就会被提到,但是很少有真正去学习.今天开始从头过一遍Java,打算从JVM开始. 1. JVM是什么 2. JRE和JDK 3. JVM结构 3.1. 程序计 ...

  4. 2014金山笔试_编写一个数组类 MyVector

    //编写一个数组类 MyVector,数组内容可以动态扩充,实现构造,析构,赋值操作符重载,插入,删除,获取元素个数,获取数组容量(不可以使用STL等的容器类,不能使用 //不连续的存储空间) #in ...

  5. 团队项目第二阶段个人进展——Day6

    一.昨天工作总结 冲刺第六天,学习了leancloud的一些数据处理知识,并看了如何在微信小程序中使用 二.遇到的问题 无 三.今日工作规划 通过动手完成一个demo来学习后端数据的请求和响应

  6. js基础--获取浏览器当前页面的滚动条高度的兼容写法

    欢迎访问我的个人博客:http://www.xiaolongwu.cn 前言 在开发中,兼容性问题是最常见的,今天就来介绍一下关于获取滚动条高度的兼容性写法,宽度同理,我在这里就不一一解释了 各浏览器 ...

  7. 使用Github来管理的代码片段

    代码片段介绍 xcode4引入了一个新feature: code snippets,在整个界面的右下角,可以通过快捷键:cmd + ctrl + opt + 2 调出来.code snippets是一 ...

  8. SSM-MyBatis-01:IDEA的安装,永久注册和简单的MyBatis用例

    一,IDEA的安装和永久注册 1.安装: 那到安装包,下一步,选路径,上面可以选操作系统64/32位,下面是程序的默认打开方式,可以不必勾选,也可以全选 路径一定不包含中文,重点 2.永久注册: 将此 ...

  9. 基于Emit实现的C#版本的BeanCopier

    在java的技术栈当中,著名的Cglib库里面有一个BeanCopier,这个类的功能就是可以完成两个对象的属性复制工作(哪怕属于两个不同的类). 今天本人通过.net内置的System.Reflec ...

  10. 第九章——运行tensorflow(Up and Running with TensorFlow)

    本章简单介绍了TensorFlow的安装以及使用.一些细节需要在后续的应用中慢慢把握. TensorFlow并不仅仅局限于神经网络和机器学习,它甚至可以用于量子物理仿真. TensorFlow的优势: ...