第一部分-JVM虚拟机内存区域划分和堆中的对象信息

嘚不嘚:花了一段时间把《深入理解JAVA虚拟机》大体看了一下,收获还是很大的。以前我也看过一些关于JVM的东西,但是没有深入的了解,而且学习大多数是通过博客,但是我这次是通过看书学习的,也算比较系统的学习了一把。这本书也是我毕业以来第一次看完的一本书,也有很多地方没有看,例如一些JVM指令的用法,JVM运行过程中的命令等。因为缺少实践的过程,所以对JVM各个地方理解还很浅显,在大体看一遍之后,然后再去写博客的过程中相当于又加深了印象,有些东西能记住,但是有些东西真的记不住,理解也不太好理解,在这里只能尽可能写一些自己的所看所感,希望各位大神多多指教。
第一:从博客上学习知识是不全面的,因为每个人关注的点是不一样的,参考博客上的高手写的东西有助于我们对技术的理解, 如果想从整体上了解某个技术,还是要通过书籍或者视频去学习去整体的学习。
第二:学完某个章节或者某一小节最好自己写一下这部分知识的总结,如果都不写可能你也不知道这部分你看了什么东西,这个是我的学习方法,如果喜欢可以借鉴。看每一章之前最好提出几个问题,带着问题去读书,收获可能会大一些。
我的计划:我打算五月份开始看java并发编程,5月份可能就会忙一些了,可能是我最近感受到了学习的乐趣,一到周末在泡在星巴克看看书,写写文章,感觉也很充实的。

整体概述
  • JVM整体架构图:画了这个架构图,合计怎么才能从整体写写JVM各个部分才能好理解一些,想来想去感觉还是这本的作者的顺序是最好的,哈哈,打算分为五章介绍JVM基本内容,至于一些实战部分看我的理解了,正常会在以后实践中深入了解时在介绍。五章的主要内容:JVM内存区域、GC算法与GC收集器等、class文件结构、类加载器、执行引擎。
    这里写图片描述

  • class文件:这部分主要讲述类的各个部分在编译过程中会转换成什么。.java文件通过javac命令编译成.class文件,在编译期间将所有的java文件编译成class文件。class文件的结构是固定的,也是有严格要求的,在书的第六章介绍了class文件的结构。class文件非常的牛x,每个类都对应着唯一的一个class文件,作用大大的,反射就是通过获得class文件的对象去操作的,类的信息是通过class文件加载内存中的。还有的class文件是通过动态编译和某些工具动态生成的,动态代理就是通过生成class文件来实现类的增强的。

  • 类加载器:这部分主要讲述类的加载过程中,class文件的结构会转化成内存中的那些内容。加载class文件到内存中,服务器启动的时会加载一部分类到内存中、在JVM运行过程中也会加载一些类到内存中。在类的加载过程中一定要保障同一个类由同一个类加载器加载,否则会出现安全问题。在类加载时会将一部分类的信息直接转化为内存结构,另一部分信息需要JVM运行时转化。
  • data center:这部分主要讲述各个部分的作用,GC算法、GC对象的确认和GC收集器。JVM内存主要存储类的信息、对象的信息、JVM运行的指令等。这部分分为线程共享区域和线程独享区域,也是GC的主要区域。
  • 执行引擎:这部分主要讲述JVM执行过程中,内存中各个部分的变化,栈帧的作用、栈帧中各个部分方法调用时的变化和调用者调用方法的选择等。JVM执行java程序主要区域,JVM运行时的方法调用是通过栈帧实现的。执行引擎主要包括两种执行方式:一种是解释器解释执行,一种是编译执行。。
  • 其他部分:本地方法和类库等。
定义概述
  • java的组成

    • java程序设计语言
    • JVM
    • API
    • class文件格式
  • JDK:java程序最小的编译环境

    • JVM
    • Java程序设计语言
    • API
  • JRE:java运行环境
    • JVM虚拟机
    • API
  • 编译相关理解:
    • 静态编译:javac
    • 动态编译:运行时编译
    • 解释器:将.class文件中的代码加载到内存中的代码,解释成JVM能够执行的指令。
    • JIT定义:当JVM发现某块代码运行的频繁,就认为这块代码是热点代码,虚拟机为了提升效率,将这块代码编译成与本地平台相关机器码,并完成各个层次的优化,完成这个任务的编译器称为及时编译器。
线程不共享区域
  1. PC程序技术器
    1. 作用:用来存放当前线程将要执行的指令,同时在线程之间切换时,PC中存储当前线程执行的位置。
    2. 概述:PC是线程私有的,创建一个线程时,会为每个线程分配一个程序计数器,该程序计数器只存储该线程将要执行的指令,当线程执行本地方法时,PC中的指令为空。
    3. OOM:内存中唯一不会发生OOM的区域。
    4. 扩展(了解):线程的调度方式分为协同式线程调度和抢占式线程调度,协同式调度是线程会执行完当前线程要执行的所有任务,然后释放CPU等资源,切换线程,协同式调度的感觉就是线程之间是串行执行的。抢占式调度是指线程执行是存在时间限制的,系统定义为线程设置执行的时间片,当时间片执行完成,而当前线程并没有执行完成,此时需要切换线程,当前线程等待CPU资源,到又一次轮询到该线程执行时,执行PC中的指令就可以继续执行上次没有执行完的线程。
  2. java虚拟机栈
    1. 概述:线程私有的,栈、PC和线程的生命周期都是一致的。虚拟机栈描述的是java方法执行的内存模型,方法调用和结束伴随着栈帧的压栈和出栈,也就是说java中方法的执行就是在栈中完成的。
    2. 栈帧:每个方法的调用都会创建一个栈帧,它包含了局部表量表,操作数栈、动态链接、返回地址等信息。栈帧是JVM运行时的数据区中栈中的元素,栈帧压栈(进入虚拟机栈)代表一个方法被调用,所以栈帧中包含指向调用方法的引用。
      1. 局部变量表:主要存放栈帧所指向方法的参数和方法内部的局部变量。
      2. 操作数栈:存放JVM运行时真实的操作数据。
      3. 对于栈帧的详细内容这些在后面执行引擎时会详解的介绍。
    3. OOM:因为虚拟机栈的深度在类的编译期间已经确定,所以JVM运行时,如果请求栈的深度大与栈的最大深度会出现StackOverFlowError,栈是可以进行动态分配内存的,即JVM在运行时,栈可以动态的申请内存,当栈无法请求到内存时会出现OOM.
  3. 本地方法栈:和栈的作用类型,不过它是用来执行本地方法的,同时也会出现OOM和StackOverflowError的。
线程共享区域

    1. 概述:线程共享区域,JVM内存区域中最大的一块,除了一些特殊的情况,所有的对象都是在内存上分配,目的是存储实例对象。堆是一块内存逻辑上连续而实际上不连续的内存区域,GC的主要场所。
    2. OOM:当实例对象无法分配到内存时,会出现OOM,或者堆区域可以进行动态扩展,如果出现无法扩展时,会出现OOM。
  1. 方法区
    1. 概述:用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,也就是说方法区中存储类的类型信息。
    2. OOM:无法满足内存分配时,会出现OOM
  2. 常量池
    1. 概述:方法区的一部分,存储类的相关信息,例如类的访问修饰符、类的方法和类的属性等信息都存储在常量池。
    2. OOM:无法申请到内存会出OOM。

补充:打算写第二篇的时,发现了一些问题,通常都是在看一遍之后去写文章,看一次就能多学到一点。。

  • 问题
    • 常量池存在多个?
    • 方法区和永久代之间的关系?
    • class文件中的符号引用和字面量到底是什么?,结构是什么?
    • MetaSpace是什么?
  • 回答:
    • 常量池的几种:
      • 常量池:class文件结构中存在常量池,它是类编译之后,类的静态结构,类中的方法、属性等信息都在常量池中。
      • 字符串常量池:是指字符串常量或者指向字符串常量的地址。String str = "abc" String str2 = new String("bcs") 会在常量池创建两个常量(abc、bcs),同时存在引用指向str2对象的堆内存地址。
      • 运行时常量池:常量池中的符号引用在类的加载期,部分引用转换为直接引用(指向堆内存的地址)。
    • 方法区和永久代:永久代就是方法区,永久代是方法区的实现,JDk1.8之后取消了永久代,取而代之是MetaSpace。
    • 字面量和符号引用:在文章的后面推荐class文件结构的博客,大家有兴趣看下,我看完是懂了。
      • 字面量:基本类型的常量,但是并不存在所有基本类型的字面量结构,因为在编译的过程中存在类型转换的情况,字面量的类型有4种。
      • 符号引用:符号引用包括多种,方法、属性、类等都是一种符号引用,在后面文章中会详细介绍。
    • MetaSpace:元空间,将方法区移动到JVM之外,移动到本地内存,将方法区的实现由永久代转变为MetaSpace。元空间的大小理论上取决于32/64位操作系统。
      • 字符串存在永久代中,容易出现性能问题和内存溢出。
      • 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
      • 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
      • Oracle 可能会将HotSpot 与 JRockit 合二为一。
对象信息
  1. 问题:在new对象的的时候要思考的几个问题:
    1. 初始化对象的类型
    2. 需要多大的内存
  2. 对象的创建:
    1. 寻找类型:在创建对象的时,首先去方法区常量池中寻找初始化类的类型,如果没有找到该类型,那么需要JVM的类加载器对类进行加载。
    2. 内存大小:对象的大小在类加载完之后就可以确定,根据对象的大小在内存中选择合适的内存,为对象进行分配内存空间。内存分配方式有两种:指针碰撞和空闲列表。因为JVM采用不同垃圾回收方式,不同回收方式有着不一样的回收算法(也有的算法一样),所以堆的内存就有不同的方式。如果内存是规整的,将内存指针需要向空闲位置移动合适的大小。如果采用空闲列表形式,在空闲列表上选择合适的区域为对象进行内存分配。
    3. 同步问题:因为存在多个线程对对象进行内存分配,所以容易产生冲突,解决方式有两种,第一种,对堆内存进行同步处理,第二种,为每个线程分配一小块内存,被称为本地线程分配缓冲。
    4. 初始化:将对象分配的内存空间的值初始化为0,接下来调用init命令创建真正的类的实例对象。
  3. 对象的内存布局:对象包括对象头、实例数据和对其填充。

  4. 对象的访问位置:在使用对象时,引用只是指向堆内存的位置,在JVM运行时不管要知道对象的实例数据,也要知道对象的类型信息,对象的访问方式有两种:

    1. 句柄:在堆内存中分配出一块内存-句柄池,引用直接指向该内存中的地址,在该内存中包含了指向对象的实例数据和对象类型数据。
      1. 直接引用:引用直接指向堆内存地址,在堆内存中的对象实例数据中,包含对象类型数据。

MetaSpace 博客: https://www.cnblogs.com/paddix/p/5309550.html
class文件博客:
https://blog.csdn.net/wangtaomtk/article/details/52267548
https://blog.csdn.net/wangtaomtk/article/details/52267588

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值