JVM

JVM是Java Virtual Machine(Java虚拟机)的缩写, 经常聊到 java 都会有几个名词:jdk,jre,jvm。 简单说一说他们的区别

jre  java运行环境,java程序需要运行,就必须要jre

jdk 程序编译调试的工具包,JDK的工具也是Java程序,也需要JRE才能运行

jvm java 虚拟机,也是jre 下面的一部分,一个虚拟的计算机,有完善的硬件架构。Java跨平台的特性就是通过 jvm 来实现

下面是jvm运行时的结构:

挨个的看下

类装载子系统:c++实现,java 所有代码底层都是通过c或者c++实现

字节码执行引擎: 同上,也是c++实现,我们主要关注部分应该是在运行时数据区

        运行时方法区:

                堆:一般存放 new 的对象(或者说各个对象的值),在栈或者方法区中存放对象的地址,指向new的对象

如 String str = new String()    String str 声明存放栈中,但是没有具体的值,只有一个内存地址,指向堆中的 new String()

堆内存溢出:不停的new对象,把老年代放满

:每个线程都有自己的栈内存空间,我们称为栈帧,调用一个方法时,划分一块栈帧

局部变量表:字面意思,比如  a = 1,b=2这样的变量是存放在局部变量表中

操作数栈

如a = 1执行流程:先将常量 1 放入操作数栈   第二部将a放入局部变量表第三步将1取出操作数栈,放入局部变量表

组成  a = 1 各种的操作数

动态链接:  对应方法内存地址,方便调用来查找

方法出口:  方法执行完,要回到的位置

栈内存溢出:不停创建方法

本地方法栈:native 修饰的方法,底层是c或者c++ 就放这些东西

      程序计数器:   每个方法独有的,存方法的执行流程,字节码执行引擎在执行的时候会修改计数器(保证顺序执行)

  方法区(元空间):常量+静态变量+类信息   用的是直接内存(默认21M,会根据full gc自动调整可大可小,理论无限大,生产

设置一般是256M或者512M)

每个线程都有自己独立的栈空间,本地栈空间寄程序计数器,堆和方法区是共享的

记录两个关于元空间设置的jvm参数:

-XX: MaxMetaspaceSize   元空间最大值,默认-1 理论无限大(受硬件大小影响)

-XX: MetaspaceSize   元空间触发 full gc 初始化值,默认是21M 达到了该值元空间会

触发gc回收,如果回收很多空间(无用的多,都回收了),那么jvm会适当把元空间调小, 如果回收很少空间(基本都需要存在

无法回收),jvm会把元空间调大

一般手动设置会把两个设置成一样的值,而且初始化一般给大一些,比如250M或者更大一些

当然这个要根据实际情况决定(程序,硬件等等问题),后续我们再说

创建对象流程

当我们 new 一个对象的时候,会经历一下流程:

类加载检查》分配内存》初始化》设置对象头》执行init方法

      类加载检查  检查类是否被加载过,否  执行相应的类加载过程,并保存   是  直接往下

        分配内存 

类加载通过后,需要给新生对象划分一块内存(类加载完成会确定大小),内存划分有

两种方式:

1. 指针碰撞(默认)

如果java堆内存是规整的,没有使用的在一边,使用了的内存在另一边。然后中间就会有个指针隔开两块内存(分界)

那么要划分内存,只需要将指针从使用了的方向,向没有使用的内存方向位移该对象的大小就可以了

2. 空闲列表

如果java堆内存不规则,使用了的和未使用的相互交错,那么指针碰撞明显不合适那么虚拟机就需要维护一个记录表

记录那些使用过,在未使用的内存找一块大小合适的位置存放new的对象,并更新表记录为使用

分配内存还有可能会出现问题,比如在高并发情况下,可能出现正在给对象A分配内存

指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。

解决:

1. CAS  (compare and swap)

比较与交换,加上失败重试(乐观锁机制)的方式来保证分配内存

2. 本地线程分配缓冲

预先在内存中划分一块内存(TLAB),不与其他线程发生冲突,各创建各自的(1.8 默认方式)

-XX: TLABSize 设置大小

流程大概是 :

线程开始创建TLAB > 分配内存,能放TLAB直接放,放不下edan区 其实内部还有些机制,本章不详解TLAB

详细关于TLAB参考 (参考了下,其他有些文章说法有问题,这章比较全面):一篇文章搞定 TLAB 原理_zhxdick的技术博客_51CTO博客_tlab是什么tlab是什么,TLAB 原理,一篇文章搞定 TLAB 原理,全系列目录:通过JFR与日志深入探索JVM-总览篇什么是TLAB?TLAB(ThreadLocalAllocationBuffer)线程本地分配缓存区,这是一个线程专用的内存分配区域。既然是一个内存分配区域,我们就先要搞清楚Java内存大概是如何分配的。我们一般认为Java中new的对象都是在堆上分配,这个说法不够准确,应该是大部分对象在堆上的TLAB分配,还有一部分在栈上分配或者是堆上直接分配,https://blog.51cto.com/u_11418075/2610347        初始化  赋初值

        设置对象头 

存放内容如上图

mark word , 指针,数组长度

单独提一下指针压缩

指针在32位JVM中的长度是32bit,在64位JVM中长度是64bit。但是64位的在存放时会将指针压缩为32位存放(默认

可关闭压缩),取出到cpu寄存器时再解压成对应位数,称为指针压缩

为什么要开启指针压缩?           

  执行 init 方法    属性赋值,调用构造方法

对象内存分配

直接上图(结合下面的步骤描述来看):

1. 可以看到,new对象时第一步居然是往栈上分配,也就是说new的内容是可以放栈中的,前面提到new的对象大部分都是存放在

堆里面没有使用的对象需要通过gc进行回收,当大量需要回收的对象出现,就会给gc带来压力,影响性能。为了减少临时对象对堆内

存中分配的数量,java通过逃逸分析来确定对象会不会被外部访问,不逃逸的方法直接栈内分配,方法调用完,出栈时直接就销毁了

不需要gc去处理

想要栈上分配对象需要依赖(1.7后默认开启) :逃逸分析  标量替换

    逃逸分析: 

public void test(){

User user = new User();

//调用test方法,user对象在方法外部引用不到,逃逸不出,可分配到栈中

}

public User test(){

User user = new User();

return user;//外部能访问到User对象,那么就逃逸了,不能分配到栈

}

        标量替换: 

                栈空间并不算大,分配对象时可能没有连续的空间来存放,将对象拆开来存放。

如果都不满足,那么对象就会放到堆中去,那么我们看下堆里面是如何存放数据:

先说下堆中的一个划分:

主要三大块: 新生代,老年代,永久代(永久代1.8没有这个概念了,元空间代替了。元空间的本质和永久代类似,都是对JVM

规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存)

新生代分为  eden(伊甸园区) ,s1  s2(survivor区,幸存者区)    默认比例:8:1:1

2. 判断是否是大对象(需要大量连续空间),大对象直接放入老年代

-XX:PretenureSizeThreshold 设置大对象,单位是字节

只在Serial和ParNew两个收集器下有效(后面垃圾回收器再说)。

这样的好处是避免大对象在新生代分配内存操作降低效率

3.  大部分情况(前面两种都不满足),也不放入TLAB, 新生对象直接放eden区 ,当eden区放满了,就会触发Minor GC (Young GC),基本eden 百分之八九十内容都会被回收,没有回收的就放入survivor区 ,对象如果每次minor gc都还存在,那么就会在 s1 s2中来回存放,每次年龄 +1 当年龄超过15(默认,不同垃圾回收器略微有些差别,但都不超过15),还没有回收,放入老年代  当老年代到达回收阈值,触发Major GC(Full GC)

        minor gc: 指发生新生代的的垃圾收集动作,MinorGC非常频繁,回收速度一般也比较快。

  full gc: 一般会回收老年代,年轻代,方法区的垃圾,MajorGC的速度一般会比MinorGC的

慢10倍以上。full gc完了还是放不下,就OOM错误

  ps: 优化垃圾回收其实就是尽量减少full gc

这里面有几个机制需要重点注意下:

1. 大对象直接放入老年代(上面第二点)

2. 长期存活对象放入老年代 (第三点中的年龄)

3.动态年龄判断

minor gc时触发,对象年龄 >=1 ,大小超过了Survivor区域的50%,放入老年代

为了让长期存活对象,尽早进入老年代

4. 老年代分配担保机制

对象内存回收

通俗的说就是gc的时候 虚拟机要怎么判断哪些内容需要回收,哪些还要存活

1. 引用计数法,这种方式的特点是实现简单,而且效率较高

在对象头处维护一个counter,每增加一次对该对象的引用计数器自加,如果对该对象的引用失联,则计数器自减。当counter为0时,表明该对象已经被废弃,不处于存活状态,就可以回收。这种方式一方面无法区分软、虛、弱、强 引用类别。另一方面,会造成死锁,假设两个对象相互引用始终无法释放counter,永远不能GC。

2. 可达性分析(现在都是用这个)

        通过一系列为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明该对象是不可用的。如果对象在进行可行性分析后发现没有与GC Roots相连的引用链,也不会理解死亡。它会暂时被标记上并且进行一次筛选,筛选的条件是是否与必要执行finalize()方法。如果被判定有必要执行finaliza()方法,就会进入F-Queue队列中,并有一个虚拟机自动建立的、低优先级的线程去执行它。稍后GC将对F-Queue中的对象进行第二次小规模标记。如果这时还是没有新的关联出现,那基本上就真的被回收了。

可达性分析算法是通过枚举根节点来实现的,最重要的问题是GC停顿。为了确保一致性(即所有对象之间的关系是确定下来的)而导致GC进行时必须进行停顿。在HotSpot的中,使用OopMap的数据结构存储特定位置上的调试信息,存储栈上那个位置原来是什么东西,这个信息是在JIT编译时跟机器码一起产生的。因为只有编译器知道源代码跟产生的代码的对应关系。 这样,GC在扫描时就可以得知这些信息了。这样做的目的是使HotSpot能够快速准确的完成GC Roots枚举,以期望减少GC停顿所带来的影响。HotSpot没有在所有的指令生成OopMap,所以只是在“特定位置”记录这些信息,这些位置就是安全点。程序执行时并非在所有的位置上都能停顿下来GC,只有在到达安全点时才能暂停。安全点选取基本上是以“是否让程序长时间执行的特征”选定。此外,HotSpot虚拟机在安全点的基础上还增加了安全区域的概念,安全区域是安全点的扩展。在一段安全区域中能够实现安全点不能达成的效果。

补充:

finalize()方法最终判定对象是否存活,即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历再次标记过程。标记的前提是对象在进行可达性分析后发现没有与GCRoots相连接的引用链。

1.第一次标记并进行一次筛选。筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize方法,对象将直接被回收。

2.第二次标记如果这个对象覆盖了finalize方法,finalize方法是对象脱逃死亡命运的最后一次机会,如果对象要在finalize()中成功拯救自己,只要重新与引用链上的任何的一个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,那在第二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本上它就真的被回收了。

注意:一个对象的finalize()方法只会被执行一次,也就是说通过调用finalize方法自我救命的机会就一次。

方法区回收

        主要回收的是无用的类,那么如何判断一个类是无用的类的呢?

1. 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。

2. 加载该类的ClassLoader已经被回收。

3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

吐槽:内容有点多,写得想吐了都,拆成两部分吧,jvm的内容也还没讲到那么多。后面再说关于垃圾回收器和jvm调优问题吧

一个想过得更好的码农---邋遢道人

jvm虚拟机及创建对象流程相关推荐

  1. JVM虚拟机,也就那么回事!(总结+绘图+流程+代码)

    利用闲余时间,看视频,搜资料,参考+总结绘制出了下面的数据流图,流图内容包括: (1)如何针对亿级流量的电商网站做JVM性能调优? (2)什么是MinorGC,什么是FullGC,什么又是STW? ( ...

  2. jvm虚拟机_一文入门jvm虚拟机

    点击上方「10分钟编程」关注我呦 让我们每天「博学」一点点 一文带你理解JVM 1.jdk.jre.jvm的区别与联系 jdk的全称是Java Development kit(java开发工具包),我 ...

  3. 一文读懂JVM虚拟机:JVM虚拟机的内存管理(万字详解)

    JVM虚拟机的内存管理 文章目录 JVM虚拟机的内存管理 JVM与操作系统 Java虚拟机规范和 Java 语言规范的关系 java虚拟机的内存管理 JVM整体架构 一.PC 程序计数器 二.虚拟机栈 ...

  4. 推荐收藏系列:一文理解JVM虚拟机(内存、垃圾回收、性能优化)解决面试中遇到问题(图解版)

    欢迎一起学习 <提升能力,涨薪可待篇> <面试知识,工作可待篇 > <实战演练,拒绝996篇 > 欢迎关注我csnd博客 也欢迎关注公 众 号[Ccww笔记],原创 ...

  5. JVM 虚拟机原理、Java 代码优化、秒杀系统

    JVM虚拟机原理 JVM组成架构 Java是一种跨平台的语言,JVM屏蔽了底层系统的不同,为Java字节码文件构造了一个统一的运行环境. Java 字节码文件 Java 如何实现在不同操作系统.不同硬 ...

  6. JVM虚拟机知识点(保姆级教程)

    jvm虚拟机知识点 温馨提示:本章节阅读预计耗时>30分钟,请沏茶细品.如果描述不准确或者缺漏的地方,还请技术大大们指点迷津! 目录索引 文章目录 jvm虚拟机知识点 @[toc] JVM基础 ...

  7. 学习深入理解JVM虚拟机及JavaGuide后的学习笔记

    JVM虚拟机 一.JVM组成部分: 1.程序计数器 作用,是记住下一条JVM指令的内存地址:1.多线程情况下,程序计数器用于记录当前线程执行的位置,从而线程切换回来的时候能够知道线程上次运行到哪儿了. ...

  8. jvm虚拟机_JVM虚拟机五连问,能过并不是只靠运气

    对于Java程序员来说,在众多Java知识点当中JVM是很重要的一块,每个Java程序都离不开Java虚拟机,Java程序的运行依靠具体的Java虚拟机实例. 在Java虚拟机规范中,分别用子系统.内 ...

  9. BATJ面试必会|Jvm 虚拟机篇

    转载自  BATJ面试必会|Jvm 虚拟机篇 目录 一.运行时数据区域 程序计数器 Java 虚拟机栈 本地方法栈 堆 方法区 运行时常量池 直接内存 二.垃圾收集 判断一个对象是否可被回收 引用类型 ...

  10. jvm虚拟机内存结构_JVM体系结构101:了解您的虚拟机

    jvm虚拟机内存结构 Java虚拟机(JVM)架构和Java字节码101的初学者速成班 Java应用程序无处不在,它们在我们的手机,平板电脑和计算机上. 在许多编程语言中,这意味着多次编译代码以使其在 ...

最新文章

  1. 查缺补漏 | Python控制结构
  2. pointnet与pointnet++
  3. linux多线程简介
  4. 多域名下Mvc的Http缓存冲突的问题
  5. TensorFlow学习笔记(十二)TensorFLow tensorBoard 总结
  6. python从入门到实践django_Django入门——《Python编程从入门到实践》
  7. 统考计算机应用基础多少分及格,网络教育统考难吗?统考多少分及格?
  8. 算法一看就懂之「 堆栈 」
  9. 任务四十七:王牌特工 准备工作(二)
  10. CE教程 第八章 《注入++》
  11. keytool条目_keytool常用命令
  12. 《动手学深度学习》(PyTorch版)代码注释 - 52 【World2Vec_Learning】
  13. 油猴-今日头条去广告脚本
  14. 谷歌扫图识人_谷歌新的一项黑科技:手机“识人” 密码再见
  15. 洛谷P3939填颜色
  16. CAN 总线的常用拓扑
  17. 【公告】博客专家4月发布原创/翻译奖励发放
  18. 树莓派-linux内核编译
  19. IOS中截屏的实现,很简易的方法
  20. JAVA疫情数据项目(JAVA课程设计)

热门文章

  1. 解决Tomcat运行内存不足问题
  2. Pytorch 小知识点汇总三--numpy数组 求均值,方差,标准差
  3. java 求方差_java计算方差、标准差(均方差)实例代码
  4. SYN 包(synchronize)
  5. excel中if如何加android,Excel 如何实现函数IF的嵌套超过七层
  6. 2020ICPC南京 F Fireworks(概率,三分)
  7. vi编辑器 末尾添加_linux下的VI编辑器使用手册
  8. 编译ionic应用时遇到“To run dex in process, the Gradle daemon needs a larger heap.”
  9. 解决electron打包的程序报错 A JavaScript error occurred in the main问题
  10. Vue3 Composition API教程