JVM学习笔记

JVM基本结构

jvm基本结构

PC寄存器

  • 每个线程拥有一个PC寄存器
  • 线程创建时创建PC寄存器
  • 指向下一条指令的地址
  • 执行本地方法时,PC的值是undefind

方法区:用来保存类的一些元信息的(例外,jdk1.7String的常量信息已经移到了堆)

  • 类的常量池
  • 字段方法信息
  • 方法字节码
  • 通常和永久区(Perm关联在一起)
    永久区并不是永久不变的,只是相对的稳定

  • 和程序开发密切相关
  • 应用程序对象都保存在java堆中
  • 所有线程共享java堆
  • 对于GC来说,堆也是分代的
  • GC的主要工作区间

桟:

  • 线程私有的
  • 由一系列帧组成(因此java桟也叫做帧桟)
  • 帧保存一个方法的局部变量,操作数桟,常量池指针
  • 每一次方法调用创建一个帧,并压桟


    图片.png

    reference this 类比于python中的成员方法必须传self

操作数桟

图片.png

桟上分配

  • 小对象(一般几十个bytes),在没有逃逸(这个对象在其他线程也会使用到)的情况下,可以直接分配在桟上。
  • 直接分配在桟上,可以自动回收,减轻GC压力
  • 大对象或者逃逸对象无法再桟上分配。

堆、桟、方法区的交互

图片.png

java的内存模型

  • 每个线程中有一个工作内存和主存独立
  • 工作内存存放主存中变量的拷贝
  • 可见性:一个线程修改了某个变量,其他线程立即知道
  • 有序性:
    在本线程内,操作都是有序的
    在线程外观察,操作都是无序的(指令重排或主内存同步延时)

编译和解释运行的概念

  • 解释执行:
    解释执行以解释方式运行字节码
    解释执行的意思是:读一句执行一句
  • 编译运行(JIT):
    将字节码编译成机器码
    直接执行机器码
    运行时编译
    编译后性能有数量级的提升

GC的概念

  • 全称Garbage Collection
  • 1960 List语言首先使用GC
  • java中,GC管理堆空间和永久区

GC算法

引用计数法

  • 概念:通过引用计算来回收垃圾
  • 使用者:COM,Python,ActionScript3
  • 原理:
    引用计数器的实现很简单,对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就 加1,当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,则对象A就不可能再被使用。
  • 缺点:引用和去引用伴随着加法和减法,影响性能
  • 很难处理循环引用(垃圾对象的循环引用,也就是那三个点不会被回收)


    图片.png

标记清除法

  • 原理:
    将垃圾回收分为两个阶段:标记阶段和清除阶段。在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象,因此,未标记对象就是未引用的垃圾对象,然后在清除阶段清除所有未标记的对象。
  • 适用场合:适用于存活对象较多的场合,如老年代。

标记压缩法

  • 原理
    它在标记清除法的基础上做了一些优化。和标记清除法一样,标记压缩算法也是从根节点开始,对所有的对象做一次标记。但之后,它并不是简单的清理未标记的对象,而是将所有的的存活对象压缩到内存的一端,之后清理边界外的所有空间。
  • 优势:
    能够整理内存碎片,避免分配大对象时,空间不够导致FullGC
  • 适用场合:适用于存活对象较多的场合,如老年代。

复制算法

  • 原理:
    将原有的内存分为两块,每次只是用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收
  • 优点:与标记清除法相比,复制算法是一种相对高效的回收算法
  • 缺点:浪费空间
  • 适用场合:适用于存活对象较少的场合,如新生代。

复制算法的优化:整合标记清除思想

image.png
  • 当垃圾回收进行的时候,大对象进入担保空间(因为复制空间一般不大)
  • 老年对象(好几次回收都没有被回收掉的对象)进入老年代

可触及性

可触及的:

从根节点可以触及到这个对象

可复活的:

一旦所有引用被释放,就是可复活状态,因为在finalize()中可能复活该对象

不可触及

  • 在finalize()之后,可能会进入不可触及状态
  • 不可触及状态不可能复活
  • 可以回收


    图片.png

    第一次GC调用finalize()方法,因为覆写了finalize()方法,复活了obj,所以第一次obj不是nul,finalize()方法只会被调用一次,第二次gc,obj就不能复活了

  • 桟中引用的对象
  • 方法区中静态成员或者常量的引用 (全局对象)
  • JNI方法桟中的引用对象

Stop-The-World

  • java中的一种全局停顿现象
  • 全局停顿,所有java代码停止,native可以执行,但不能和jvm交互
  • 多半由于GC引起
  • 原因:类比一边开party,一边打扫卫生,只能让大家停下来才能扫干净。
  • 危害:长时间服务停止,没有响应;遇到HA系统,可能引起主备切换,严重危害环境。

GC参数

串行收集器(GC线程只有一个)

最古老,最稳定,效率高,可能产生较长的停顿
-XX:+UseSerialGC:
新生代、老年代使用串行回收
新生代使用复制算法,老年代使用标记压缩算法

并行收集器(GC线程有多个)

ParNew:

  • -XX:+UseParNewGC:
  • 新生代并行,老年代还是串行
  • -XX:+ParallelGCThreads 限制线程数量

Parallel收集器

  • 类似ParNew
  • 新生代复制算法
  • 老年代标记压缩算法
  • 更加关注吞吐量
  • -XX:+UseParallelGC
    老年代串行
  • -XX:+UseParallelOldGC
    老年代并行
  • XX:MaxGCPauseMills
    最大停顿时间
  • XX:GCTimeRatio
    0-100取值范围
    垃圾收集时间占总时间比
    默认99,即最大允许1%时间做GC
  • 两个参数是矛盾的。因为停顿时间和吞吐量不可能同时调优

CMS收集器

  • Concurrent Mark Sweep 并发标记清除(并发指的是和应用程序一起执行)
  • 标记清除算法
  • 并发阶段会降低吞吐量
  • 老年代收集器(新生代使用ParNew)
  • XX:+UseConcMarkSweepGC
  • 停顿时间较少
    -过程比较复杂
    初始标记:根可以直接关联到的对象,速度快
    并发标记(和用户线程一起):主要标记过程,标记全部对象
    重新标记:由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正
    并发清理(和用户线程一起)基于标记结果,直接清理对象


    CMS收集器
  • 清理不彻底
  • 不能在空间快满的时候清理
  • -XX:CMSInitiatingOccupancyFraction设置触发GC的阈值
  • 如果不幸预留的空间不够,就会引起concurrent mode failure


    CMS不用标记压缩但是也会使用命令进行整理碎片

GC参数整理

GC参数整理

GC参数整理

类装载器

class转载验证流程

加载

  • 装载类的第一个阶段
  • 取得类的二进制流
  • 转为方法区数据结构
  • 在java堆中生成对应的java.Lang.Class对象

链接

  • 验证:
    目的是保证Class流的格式正确
    文件格式验证:
           是否以0xCAFEBABE开头
           版本号是否合理
    元数据验证:
           是否有父类
           是否继承了final类
           非抽象类实现了所有抽象方法
    字节码验证(很复杂):
           运行检查
           桟数据类型和操作码数据是否吻合
           跳转指令指定到合理的位置
    符号引用验证:
           常量池描述类是否存在
           访问的方法或字段是否存在且足够的权限
  • 准备:
    分配内存,并为类设置初始值(方法区中)
           public static int v=1
           在准备阶段,v会被设置为0
           在初始化的<clinit>中才会被设置为1
           对于static final类型,在准备阶段就会被赋值
  • 解析:
    将符号引用替换为直接引用
    符号引用:字符串,引用对象不一定被加载。就是一个类的字符串表现,比如说"java.lang.Object"
    直接引用:指针或地址偏移量,引用对象一定在内存

初始化

  • 执行类的构造器<clinit>
    static 变量 赋值语句
    static{}语句
  • 子类的<clinit>调用前保证父类的<clinit>被调用
  • <clinit>是线程安全的

什么是ClassLoader

  • ClassLoader是一个抽象类
  • ClassLoader的实例将读入java字节码然后将类装载到jvm中
  • CLassLoader可以定制,满足不懂的字节码流获取方式
  • ClassLoader负责类装载过程的加载阶段
  • 一些重要方法


    ClassLoader的重要方法
  • ClassLoader的加载顺序


    图片.png

自底向上查询类是否已经加载,自顶向下尝试加载类。
我们写的类一般都是在App ClassLoader中加载的,如果要查询一个类是否已经被加载,先从App ClassLoader中查找,找不到再网上找。如果要加载一个类,先问一下Bootstrap ClassLoader有没有,没有再往下走。
双亲模式的问题
BootstrapLoader不能查找App ClassLoader中的类
解决方法:Thread.setContextClassLoader()
这是一个上下文加载器,是一个角色,用以顶层ClassLoader无法访问底层ClassLoader的问题,基本思想是在顶层ClassLoader中传入底层ClassLoader实例

java堆的分析

内存溢出(OOM)的原因

  • 在jvm中的内存区间划分
    堆,永久区,线程桟,直接内存
  • 占用大量堆空间,直接溢出
  • 永久区溢出,可以增大Perm区大小,运行Class回收
  • java栈溢出
    这里的桟指的是在创建线程的时候,需要为线程分配桟空间,这个桟空间是向操作系统请求的,如果操作系统无法给出足够的空间,就会抛出OOM。解决办法:减少堆内存,减少线程桟的大小

Mark Word,对象头标记 32位

描述对象的hash,锁信息,垃圾回收的年龄,标记
指向锁记录的指针
指向monitor的指针
GC标志
偏向锁线程ID

偏向锁

  • 大部分情况下是没有竞争的,所以可以通过偏向来提高性能
  • 所谓的偏向,就是偏心,即锁会偏向于当前已经占有的锁的线程
  • 将对象投Mark的标记设置为偏向,并将线程ID写入对象头
  • 只要没有竞争,获得偏向锁的线程,在将来进入同步快,不需要做同步
  • 当其他线程请求相同的锁时,偏向模式结束
  • 在竞争激烈的场合,偏向锁会增加系统负担

轻量级锁 BasicObjectLock

  • 嵌入在线程桟中对象
    包含两个部分,一个是对象头,另一个是指向持有这个锁的对象的指针
  • 普通的锁处理性能不够理想,轻量级锁是一种快速锁定的方法。
  • 如果对象没有被锁定
    将对象头的Mark指针保存到锁对象中
    将对象头设置为指向锁的指针(在线程桟空间中)
  • 如果轻量级锁失败,表示存在竞争,升级为重量级锁(常规锁)
  • 在没有竞争的前提下,减少传统锁使用OS互斥量产生的性能损耗
  • 在竞争激烈时,轻量级锁会多做很多额外的操作,导致性能下降。

自旋锁

  • 当竞争存在时,如果线程可以很快获得锁,那么可以不在OS层挂起线程,让线程做几个空操作(自旋)
  • 如果同步块很长,自旋失败,会降低系统性能
  • 如果同步块很短,自旋成功,节省线程挂起切换的时间,提升系统性能。

获取锁的流程

首先尝试获取偏向锁,如果可用,进入偏向模式,如果不可用,尝试轻量级锁,如果可用,使用轻量级锁,到此结束,如果不可用,尝试自旋锁,如果成功,那就拿到锁了,如果不成功,最后才会膨胀为重量锁(普通锁),在操作系统层面进行挂起。

代码层面锁的优化

减少锁的持有时间

尽量使用同步代码块而不是同步方法

减小锁的粒度

  • 将大对象,拆成小对象,大大增加并行度,降低锁竞争
  • 偏向锁,轻量级锁成功率更高
  • ConcurrentHashMap
    若干个Segment,一个Segment维护一个HashEntry,put操作时,先定为到Segment,锁定一个Segment,执行put
    减小锁粒度后,ConcurrentHashMap允许多个线程同时进入

锁分离

  • 根据功能分离
  • ReadWriteLock
  • 读多写少的情况,可以提高性能

锁粗化

图片.png

锁消除

在即时编译时,如果发现不可能被共享的对象,则可以消除这些所的操作

无锁

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

推荐阅读更多精彩内容