深入理解Java虚拟机JVM高级特性与最佳实践--笔记

时时更新,假装很厉害(其实就是逼自己坚持下去···)我不生产程序 我是程序的搬运工


Part1 内存模型

1.运行时数据区域


运行时数据区域
  • 程序计数器:空间小,线程私有,当前线程所执行的字节码的行号指示器。调用Native方法计数器的值为Undefined。唯一一个没有定义OutOfMemoryError情况的区域。
  • Java虚拟机栈:线程私有,描述java方法执行的内存模型:每个方法执行创建栈帧,存储局部变量、操作数栈、动态链接、方法出口等信息。
  • 本地方法栈:本地方法即Native方法,
    除次外与Java虚拟机栈几乎一一致,有些虚拟机(HotSpot, java8默认虚拟机)直接将两栈合二为一。
  • java堆:线程共享,启动时创建,目的是存放对象实例,方便GC回收。
    • 分代收集算法:分为新生代和老年代,再细分有Eden空间、Form Survivor空间、To Survivor空间等。
  • 方法区:多线程共享,存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。java堆的一个逻辑部分,别名Non-Heap(非堆),目的与java堆分开。在HotSpot JVM中大家称为永久代,其他虚拟机不存在永久代概念,HotSpot也正在转变。
  • 运行时常量池:方法区的一部分,用于存放编译期生成的各种字面量和符合引用。
  • 直接内存:不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。使用场景NIO,Native函数库直接分配堆外内存,然后存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。避免了java堆与Native堆来回复制数据。

Part2对象创建

1.对象创建:

  • new
  • 检查指令参数是否可定位到类的符号引用
  • 检查此类是否被加载、解析和初始化
  • 虚拟机分配内存
    • 若内存绝对规整,则采用指针碰撞(指针向非空闲区域挪动对象需要的大小)否则采用空闲列表(在已用空间的空闲区域中找到足够大的内存),规整与否由GC是否带压缩整理功能决定(Serial、ParNew等采用Mark-Compact算法带Compact,CMS基于Mark-Sweep算法采用后者)
    • 保证原子性:
      1.CAS+失败重试
      2.TLAB(Thread Local Allocation Buffer)本地线程分配缓冲,保证更新操作的原子性。
  • 初始化内存空间为零值,若TLAB则在TLAB分配时执行。
  • 初始化对象实例(类元数据信息、对象哈希码、对象GC分代年龄等)
  • 执行对象init方法(目测就是初始化变量值)

2.对象内存布局:

  • 对象头(Header)
    • Mark Word对象自身运行时数据(哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等)
    • 类型指针,指向它的类元数据的指针。
  • 实例数据(Instance Data)
  • 对齐填充(Padding)
    • 并不是必然存在,HostSpotVM对象地址必须是8字节的整数倍,所以需要对其。

3.对象访问定位:

  • 句柄访问:


    句柄访问
  • 直接指针访问:


    指针访问

Part3 GC

1.对象的存活状态:

  • 引用计数算法:主流的Java虚拟机中都未使用这一算法。原因:当A对象引用B,B对象引用A,AB就计数就一直不为0也就不会被回收。
  • 可达性分析算法: Java、C#、Lisp都用这以算法。思路:从“GC Roots”作为起点,当一个对象没有路径与GC Roots相连则证明这个对象无引用。

2.引用:JDK1.2之后引用分为四种(由强到弱)

  • 强引用String Reference
  • 软引用Soft Reference
  • 弱引用Weak Reference
  • 虚引用Phantom Reference

3.死亡标记:对象死亡至少需要两次标记,一次是可达性分析算法的标记,放入F-Queue队列,然后等待队列调用finalize方法,执行后依旧没有引用则被再次标记,然后回收。finalize只会被调用一次。

4.回收方法区:主要回收内容废用常量(例如String),无用的类。常量判断方式与堆中差不多。无用类的判断有三点:

  • 类的实例都被回收,堆中无任何实例。
  • 该类的ClassLoader被回收。
  • 类的类类型无处引用,即Object.class。
    至于是否被回收还需要看启动参数等等因素。

5.垃圾收集算法:

  • 标记-清除 Mark-Sweep:先标记,再统一回收,最基础的算法,两个问题,一个是效率第,一个是空间零碎。
  • 复制算法Copying:将内存按容量划分大小相等的两块,每次使用其中一块,回收时将存活对象复制到另一半,然后全部回收,再将活着的对象复制回来。代价内存折半。实际在HotSpot中Eden和Survivor比例为8:1,可用内存在整个新生代中为90%,剩下的10%用来收集复制。对象存活率较高时不采用此方式。
  • 标记-整理 Mark-Compact,与标记删除一样,标记整理是指标记后存活对象都向一端移动,然后清理掉边界以外的内存。
  • 分代收集算法:大多数垃圾收集的算法,根据对象的存活周期不同将内存划分为几块,一般为新生代和老年代,新生代收集会有大批对象死去,用复制算法,老年代对象存活率高,用标记-清理或标记-整理。

6.垃圾收集器

  • 复制算法:
    • Serial收集器 需要stop the world,单个线程
    • ParNew收集器 上一个的多线程版本
    • Parallel Scavenge收集器 目标是控制吞吐量(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾回收时间)
  • 标记整理算法:
    • Serial Old收集器 老年代版本Serial收集器,与Parallel Scavenge收集器搭配使用,或是CMS收集器的后备方案
    • Parallel Old收集器 老年代版本Parallel Scavenge收集器 Parallel Scavenge收集器的好搭档
  • 标记清除算法
    • CMS收集器 并发标记并发清除
  • 复制算法+标记整理算法
    • G1 Garbage-First最先进的收集器,不需要与其他收集器配合使用。可预测停顿,可整合空间。(-XX:+UseG1GC)

7.对象分配流程:

  • 优先在Eden区中,空间不够进行gc然后再不够则将其他对象放至老年代。
  • 大对象直接进入老年代。
  • 长期存活对象进入老年代:给对象一个年龄,第一次Minor GC 仍然存活,则将其从Eden中移动到Survivor中,并且年龄加1,当年龄加到一定程度(默认15岁)晋升到老年代。

8.java虚拟机工具:

  • jps:虚拟机进程状况


    jps命令
  • jstat:虚拟机统计信息监控工具


    jstat命令

    (参数2147为pid)


    jstat命令主要参数
  • jinfo:java配置信息工具


    jinfo命令
  • jmap:java内存映像工具


    jmap命令
  • jhat:虚拟机堆转存快照分析工具


    jhat命令

    jhat效果
  • jstack:java堆栈追踪工具


    jstack命令
  • jconsole:java监视与管理控制台


    jconsole可视化工具

Part4实际分析

1.高性能机器部署:
问题:16G内存,java堆大小分配12G,一次GC高达14s,因为大对象比较多,大部分大对像都进入到了老年代,所以GC回收不掉这些大对象,造成十几分钟就十几秒的GC停顿。
解决方案:分配小一点内存,一台机器上部署多个服务。

2.集群间同步导致内存溢出:
问题:高并发读写操作,导致集群间数据疯狂同步,同步失败后还需要同步重试,服务器间通讯,消耗大量内存资源。
解决方案:书里没说,个人觉得这种高并发读写就不应该这种方式实现集群,生产者消费者的模式来减缓同步压力。

3.堆外内存导致的溢出错误:
问题:平台限制内存2G,虚拟机分配内存1.6G,当加大内存的时候内存溢出现象更加严重,jstat时候GC不频繁,个区域稳定。
解决:问题定位,由此可推算,直接内存区大小为0.4G,Direct Memory虽然也能被Full GC回收,但是它不能通知GC回收。大量NIO操作,需要用DM。分配DM空间-XX:MaxDirectMemorySize

4.外部命令导致系统缓慢:
问题:系统调用大量脚本,命令行等,方式来获取系统一些信息。
解决:大量的这种操作需要消耗很多资源,系统需要产生新的进程,建议使用java api的方式获取这些信息。

5.服务器JVM进程崩溃
问题:运行期间服务器频频出现自动关闭的现象,生产hs_err_pid.log,一个服务调用另一个服务,两个服务处理速度差距较大,导致多个线程等待,最终服务崩溃。
解决:使用生产者消费者模式,消息实现队列。

6.不恰当的数据格式导致内存占用过大。

7.由windows虚拟内存导致的长时间停顿。

(Class文件描述的各种信息略掉了。)

Part5虚拟机类加载机制

1.类生命周期:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)七个阶段

2.五种类初始化时机:

  • new、getstatic、putstatic、invokestatic指令时类没有进行初则需要进行初始化。

  • 使用反射调用的时候,类没进行初始化就要先初始化。

  • 初始化一个类的时候,父类没调用就需要触发父类初始化。

  • 虚拟机启动的时候,用户需要指定执行的主类,虚拟机会先初始化这个类。

  • 当使用JDK1.7的动态语言的支持的时候,如果一个java.lang.invoke.MethodHandle实例最后解析结果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,这个方法句柄对应的类没有进行初始化的时候。

3.加载阶段虚拟机动作:

  • 通过类全名获取二进制字节流
  • 将字节流所代表的静态存储结构转化为方法区的运行时的数据结构。
  • 在内存中生成这个类的的java.lang.Class对象,作为方法区这个类的访问入口。

4.验证:

  • 文件格式验证(魔数、版本、类型、引用、编码、md5)

  • 元数据验证(是否由父类,是否继承了不允许被继承的类(final)

  • 字节码验证(数据类型与指令代码序列都能配合工作,跳转指令不会跳到方法外,保证类型转换有效)

  • 符号引用验证(引用能否找到对应的类,能否找到合法的方法和字段,字段访问性是否可被当前类访问)

5.准备(初始化类变量,类变量赋初值)

6.解析

  • 类或接口解析
  • 字段解析
  • 类方法解析
  • 接口方法解析

7.初始化

  • 静态初始化块能赋值之后的静态变量,但无法访问。

8.同一个类加载器加载的类才是相等的,否则instanceof也返回false

9.类加载器:

  • 启动类加载器(Bootstrap ClassLoader):C++实现,加载rt.jar等基本文件
  • 扩展类加载器(Extension ClassLoader)负责加载<java_home>\lib\ext 目录中的依赖。
  • 应用程序类加载器(Application ClassLoader):系统类加载器,加载classpath上的类库

Part6 虚拟机字节码执行引擎

1.栈帧:虚拟机运行时数据区中的虚拟机栈的栈元素。

2.栈帧需要分配多少内存不会受程序运行期变量数据的影响。

3.栈帧结构:

  • 局部变量表:最小单位为槽(Variable Slot)每个槽能存储的类型有八种:(boolean、byte、char、short、int、float、reference、returnAddress)对于64位数据类型以高位对齐的方式分配两个连续的Slot。0位索引为this
  • 操作数栈:LIFO,每个元素可以是任意的java类型,虚拟机都是基于栈的执行。
  • 动态连接
  • 方法返回地址
  • 附加信息

4.方法重载Overload优先级


ab.png

先输出 char,注掉char后输出int注掉int输出long注掉long输出Character 注掉Character输出Serializable注掉Serializable输出object
顺序是先转型,再装箱,再接口,再父类,最后可变长

Part7 内存模型与线程

1.评价服务性能:每秒事务处理数TPS(Transactions Per Second)

2.内存间交互操作

  • lock
  • unlock
  • read
  • load
  • use
  • assign
  • store
  • write

3.八种原则:

  • read和load,store和write必须成对出现R->L S->W
  • 最后一次assign后必须写回内存A->S->W
  • 未assign操作的数据不允许同步到主内存中A->S->W
  • 一个变量在同一时刻只允许一个线程lock,并且可以被lock多次,但必须unlock相同次数
  • 如果对变量lock 会清空内存中此变量的值,在执行load或assign操作初始化变量的值。
  • 没lock不许unlock,不许unlock其他线程lock的变量
  • unlock前必须store,write U->S->W
  1. volatile
  • 确保了数据存取的原子性(32位环境读取64位变量分两次,先读高32位后读低32位)
  • 保证了数据的可见性
  • 禁止了指令重排序
    误区:
    • 自增操作(i++) 不具备原子性,即本操作是三步
      • 从主存储中读取i的值到高速缓存
      • 在高速缓存中对i进行算数运算
      • 将结果从高速缓存写入主存储
    • 不具有原子性的操作volatile无法保证其准确性
    • 当a线程读取i并进行加法运算,此时b线程进行读取i操作,此时a并未将i写入主存储,所以b读到的还是旧的值。
  • 想保证上一操作的原子性则需要在运算方法上加入synchronized关键字,或者使用Lock 对象锁的方式来保证同一时间仅有一个线程可以调用该方法。或是使用原子对象自增AtomicInteger.

5.天然的先行发生的关系:

  • 程序次序规则:按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。
  • 管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作(同一个锁)
  • volatile变量规则:volatile变量的写操作优先发生于这个变量后面的读操作。
  • 线程启动规则:Thread对象的start方法先行发生于此线程的每一个动作。
  • 线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测。
  • 线程终端规则
  • 对象终结原则
  • 传递性

6.线程状态:

  • 新建
  • 运行
  • 无限期等待
  • 限期等待
  • 阻塞
  • 结束
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容