• JAVA虚拟机


    类加载过程

    加载

    1. 取二进制流:通过一个类的全限定名获取定义此类的二进制流。
    2. 转换为运行时的数据结构:将字节流静态存储结构转化为方法去运行时的数据结构
    3. 生成class:在内存中生成一个代表这个类的class对象 ,作为方法区这个类的各种数据的访问入口

    验证

    1. 文件格式验证:字节流进入嫩村的方法区进行存储
    2. 元数据验证
    3. 字节码验证:使用类型检查来完成数据流分析校验
    4. 符号引用验证:

    准备

    正式为类变量分配内存并设置类变量初始值,都将在方法区中进行分配

    解析

    虚拟机将符号引用替换为直接引用的过程

    初始化

    <clinit> <init>

    六种触发初始化的情形如下:

    1. new创建类实例
    2. 访问某个类的或者接口的静态变量,或者对该静态变量赋值
    3. 调用类的静态方法
    4. 反射class.forName("com.xxx.xxx")
    5. 初始化一个类的子类
    6. java虚拟机启动时被标名为启动类的类

    动态调用

    静态分配

    对应方法的重载:依赖静态类型来定位方法执行的版本,属于多分派

    动态分配

    对应方法重写,属于单分派。

    确认方法的步骤:

    1. 找到操作数栈的第一个元素所指向的对象的实际类型,即new 关键字后面类型
    2. 如果在常量池中找到描述符合简单方法名都相符的方法,则进行访问权限的校验通过,则直接引用
    3. 否则按照继承关系自下而上进行第二部操作,重新查找其他子类
    4. 如果始终没有找到则抛出异常

    几个相关名词的定义:

    宗量:方法接受者与方法的参数
    单分派:根据一个总量对目标方法进行选择
    多分派:根据多个宗量对目标方法进行选择

    JAVA语言是一门静态多分派 动态单分派语言

    自旋:自旋是指某线程需要获取锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取锁。

    偏向锁

    引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令。
    当只有一个线程去竞争锁的时候,我们不需要阻塞,也不需要自旋,因为只有一个线程在竞争,我们只要去判断该偏向锁中的ThreadID是否为当前线程即可。如果是就执行同步代码,不是就尝试使用CAS修改ThreadID,修改成功执行同步代码,不成功就将偏向锁升级成轻量锁。

    轻量锁

    获取轻量锁的过程与偏向锁不同,竞争锁的线程首先需要拷贝对象头中的Mark Word到帧栈的锁记录中。拷贝成功后使用CAS操作尝试将对象的Mark Word更新为指向当前线程的指针。如果这个更新动作成功了,那么这个线程就拥有了该对象的锁。如果更新失败,那么意味着有多个线程在竞争。
    当竞争线程尝试占用轻量级锁失败多次之后(使用自旋)轻量级锁就会膨胀为重量级锁,重量级线程指针指向竞争线程,竞争线程也会阻塞,等待轻量级线程释放锁后唤醒他。

    重量锁

    重量级锁的加锁、解锁过程和轻量级锁差不多,区别是:竞争失败后,线程阻塞,释放锁后,唤醒阻塞的线程,不使用自旋锁,不会那么消耗CPU,所以重量级锁适合用在同步块执行时间长的情况下

    synchronized 和 volatile

    1. volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值不确定,需要从主内存中读取;synchronize锁定当前变量,只有当前线程可以当问该变量,其他线程被阻塞住
    2. volatile仅能使用在变量级别 synchronize可以使用在变量、方法、类
    3. volatile仅实现变量的修改可见性,不能保证原子性;而synchronized则都可以保证
    4. volatile不会造成线程的阻塞;synchronized会造成线程的阻塞
    5. volatile变量不会被编译器优化;synchronized标记的变量可以被编译器优化

    synchronize 和 ReentrantLock

    ReentrantLock扩展了synchronized
    ReentrantLock可以对锁的等待事件进行设置,这样就避免了死锁
    ReentrantLock可以获取各种锁的信息
    ReentrantLock可以灵活地实现多路通知

    两者加锁机制不一样:
    ReentrantLock底层调用的是Unsafe的park方法加锁
    synchronized操作对象头中的mark word信息进行加锁

    双亲委派模型

    类加载器分类:每一个类加载器都有一个独立的名命空间

    启动类加载器:是虚拟机自身的一部分,用来加载JAVA_HOME/lib/目录中的类
    扩展类加载器:负责加载java.ext.dirs系统变量指定的路径中所有的类库
    应用程序类加载器:负责加载用户类路径上的指定类库,默认使用的类加载器

    如果一个类加载器收到了类加载的请求,它首先不会去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的加载器都是如此,这样所有的加载请求会被传到顶层的启动类加载器中,只有当父类加载无法完成加载请求时,子加载器才会尝试去加载类

    类的实例化 时父子类中各属性加载顺序

    1. 父类静态变量
    2. 父类静态代码块
    3. 子类的静态变量
    4. 子类静态代码块
    5. 父类非静态变量
    6. 父类构造函数
    7. 子类非静态变量
    8. 子类构造函数
    9. 静态变量 静态代码块 非静态变量 构造函数

    优化GC

    1. 适当调整-XX:ServivorRatio的比例
    2. 选择适合自己业务的垃圾收集器,web服务一般是ParNew+CMS
    3. 调整jvm老年代,新生代以及持久代的比例,测试出一个比较满意的值
    4. 设置-XX:MaxTenuringThreshold 让新生代提前进入老年代,减少在survivor区域的复制
    5. 调整 -XX:CMSInitiatingOccupancyFraction=60,控制minor gc频率
    6. -XX+UseCMSCompactAtFullCollection消除cms碎片

    垃圾回收以及算法

    引用计数法:当有一个地方使用计数值+1,失效时-1,为0时是不可再被引用的对象
    缺点:循环引用时,某些对象将无法被回收掉

    可达性分析算法:通过一系列的称为GCROOTS的对象作为起点,往下搜索(路径为引用链),当对象不与GC任何引用链相连时,则这些对象是不可达的。
    GCROOTS对象包括:

    1. 虚拟机栈中引用的对象
    2. 方法区中静态属性或者常量引用的对象
    3. 本地方法引用的对象

    垃圾收集算法:

    1. 标记清除(造成碎皮空间)
    2. 复制算法 (内存使用率50%)
    3. 标记整理 (比标记清除多一步,将存活对象移动到一端,清除其他的对象)
    4. 分代算法 (新生代:复制算法 老年代:标记清除或者标记整理)

    垃圾收集器:
    ParNew(多线程 高吞吐)

    CMS(初始标记,并发标记(时间长),重新标记,并发清除(时间长)低延迟)

    CMS垃圾收集过程图

    G1(将整个堆划分为一个个小块,1-32M,RememberSet指向块的内存地址) 调整小 -XX:InitiatingHeapOccupancyPercent=45% 增多Minor GC频率,减少Full GC频率

    G1垃圾收集过程图

    参考文献
    ^1 周志明 《深入理解JAVA虚拟机》 第二版
    TIPS: 转载必须注明原文地址 和 引用参考文献部分

  • 相关阅读:
    GCJ 2015-Qualification-A Standing Ovation 难度:0
    CF 103E Buying Sets 最大权闭合子图,匹配 难度:4
    HDU 1560 DNA sequence A* 难度:1
    蓝桥杯练习系统 矩阵翻硬币 大数,牛顿迭代法 难度:2
    Operating System Concepts with java 项目: Shell Unix 和历史特点
    HDU 2181 哈密顿绕行世界问题 dfs 难度:1
    HDU 3533 Escape bfs 难度:1
    HDU 3567 Eight II 打表,康托展开,bfs,g++提交可过c++不可过 难度:3
    POJ 1011 Sticks dfs,剪枝 难度:2
    UVALive 5905 Pool Construction 最小割,s-t割性质 难度:3
  • 原文地址:https://www.cnblogs.com/whalefall541/p/15313935.html
Copyright © 2020-2023  润新知