《深入理解Java虚拟机_JVM高级特性与最佳实践 第2版》读书笔记

第2章 内存管理机制

java内存模型

- -
共享,给实例、数组分配内存。新生代和老年代,分代垃圾回收算法
方法区 共享,存一些类信息、常量、静态变量
运行时常量池 编译期生成的各类字面量和符号引用
虚拟机栈 本地方法栈
直接内存 JDK1.4 NIO 用通道和缓冲区直接操作内存,避免在java堆和本地方法堆中来回复制数据

定义:
线程私有:各个线程独立的数据,互不影响。如PC
java虚拟机栈:包含1.局部变量表(存放了编译时可以确定的各种数据类型)

对象的创建

  1. 去常量池查看类信息,有没有加载 解析 初始化
  2. 已经加载过在堆中的,去Eden代去分配内存
    1 如果内存是规整的直接移动指针 (指针碰撞)
    2 如果内存有碎片 查看分配表 找到一块足够的区域分配给他(空闲列表)
  3. 由于创建对象是一个非常频繁的事,可能A的指针还没来的及修改B由来修改指针,为了保证线程安全采用CAS和TLAB
  4. 初始化0值
  5. 设置对象头:虚拟机对对象设置,如哪个类实例,hash码,对象GC,元数据
  6. 执行程序员的INIT方法

对象的访问定位

  1. 句柄池 R-池-地址
  2. 直接指针 R-地址

第3章 垃圾回收

引用判断方法

  1. 引用计数法:循环引用问题
  2. 可达性分析法:栈中对象、方法区静态和常量

4种引用

- -
O o=newO()的 永远不会回收
在内存不够的时候才回收
只要有垃圾回收就回收
引用等于没有

垃圾回收算法

  • 标记-整理:标记然后清除,有碎片
  • 复制:把内存分成一半,每次回收把存活对象直接复制到另一半,然后把自己这半清除,用在新生代,因为统计98%的新生代对象都会被回收。80%Eden代,10% 10%surviver空间,回收时把Eden代的内容复制到10%空间中,如果空间不够使用老年代空间做担保。
  • 标记-整理:让存活对象向一边移动,用在老年代,因为老年代对象的存活率很高。
  • 分代收集法:分成新生代和老年代,分别用复制和标记整理算法。

发起GC:在安全点(主动中断,指令复用的地方如循环)或安全区(一段引用不会发生变化的区域)

垃圾收集器

  • serial (OLD):单线程 简单速度快 ,会stop the world
  • parnew:serial的多线程版本

只有上面两种可以与CMS配合使用

  • parallel(+OLD 1.6):可控吞吐量

CMS(重点)concurrent Mark-sweep

  1. 初始标记: 标记GCROOTS,标记根节点 stop theworld
  2. 并发标记concurrent:从根节点遍历标记 耗时最长
  3. 重新标记:重新标记有变动的
  4. 并发清除:清除
  • 缺点:
    • 很吃CPU资源,尤其CPU只有1-2个核心时,要吃一半
    • 清的过程中还产生垃圾,所以阈值要设68%,如果设置92%,在CMS运行期间 内存无法满足主程序运行需要(垃圾清的太慢了)就是清除失败CMF,只能采用 serial old这样停顿时间就很长了
    • 标记清除有碎片

G1(重点)garbage-first1:

  • 停顿时间可控,回收步骤类似CMS,宏观标记-整理,region之间标记-复制。
  • 把所有堆分成多个region,维护个优先列表,回收价值最大的。
  • region不是独立的,一个region的可能引用另一个region的对象,使用remembered set记录对象是否处于不同region中。

内存分配机制:

  1. 先在新生代分配,如果不够就执行一次minorGC把对象转移到survivor区,如果还不够,就通过担保机制转移到老年代。
  2. 大对象直接进入老年代。
  3. 长期存活的对象进入老年代:移动到survivor后年龄设置为1,每经过一次minorGC+1,默认15岁移动到老年代。or相同年龄所有对象的总和超过survivor的一半也移动,无需等到15。

类加载过程:

加载-连接(验证-准备-解析)-初始化

  1. 加载字节码文件,可以从各个地方加载(本地、网络等)
  2. 验证 字节码文件是否合法
    准备 静态变量==的分配内存初始化值
    解析 将符号引用替换为直接引用(字段、方法、接口)
  3. 初始化 《clinit》由父到子执行程序员定义的静态方法

类加载器:放在JVM外部,可以由程序决定怎么加载字节码文件。即使是同一个类,用不同加载器加载,比较起来也不相等,因为比较只有在同一个加载器下才有意义。

双亲委派模型:

如果一个类加载器收到了加载请求,不会自己尝试去加载,委派给父加载器,一直到顶层,只有顶层反馈不能加载时,子加载器才会尝试去加载。

要求自定义类加载器要有自己的父加载器(启动类RT.JAR-扩展类EXT)

目的:保证java最基础的行为,如Object类是同一个类

并发

线程在操作系统中的实现

  • 轻量级进程和用户线程混合
  • 使用多个轻量管理多个用户线程 或 1个轻量管理1个或多个

好处:可以开很多线程,不受到内核数影响,不易使整个进程阻塞;切换消耗低,不要频繁系统调用。.

线程安全的实现方法:

  1. 互斥同步(阻塞同步)悲观
    信号量,lock临界区unlock。
    java中synchronized、reentrantlock实现同步。阻塞和唤醒一个线程需要操作系统来帮忙,由用户态转换到核心态中的时间有可能比用户执行代码的时间还长,是重量级的操作。
  2. 非阻塞同步
    先操作,如果没有其他线程争用共享数据就操作成功;如果有那就不断地重试(冲突检测)。
    指令集原子操作CAS(UNSAFE类):V原值,A预期值,B新值。如果V=A,那么用B更新V,返回旧V值,否则不执行更新。
  3. 无同步方案
    线程本地存储,threadlocal,线程独享变量

锁优化

  1. 自旋锁和自适应自旋
    互斥同步中阻塞引起线程挂起和恢复是要转入内核态完成,对性能影响很大。而自旋就是让等待的线程不放弃自己的执行时间,执行一个忙循环等待别人释放锁。适应性自旋就是如果上次等待成功可以就让他多等几次,如果失败就少等待几次,避免浪费资源。
  2. 锁消除
    虚拟机检测到变量不会被别的线程访问到,就把他当做栈上私有的,消除锁。
  3. 锁粗化
    同一个对象即使没有竞争也连续加锁解锁(如加锁在循环体内),很浪费性能,虚拟机自动扩大他的加锁范围(自动把锁粗话到循环外)。
  4. 轻量级锁
    CAS操作在对象头标记,代表获得锁
    先使用cas获取锁对象;如果存在两个以上线程争用同一个锁,则升级为重量级锁,阻塞线程。绝大数锁在同步周期内不存在竞争。
  5. 偏向锁
    如果在第一个获取锁的线程在执行过程中不存在竞争,那就不同步了;否则转为轻量锁
发布了8 篇原创文章 · 获赞 0 · 访问量 306

猜你喜欢

转载自blog.csdn.net/zwn888zwn/article/details/101978011