《深入理解java虚拟机》读书笔记

1. 运行时数据区域


1.1 程序计数器

a、 线程私有的内存区域
b、可以看作是当前线程所执行的字节码的行号指示器,通过它来取下一条需要执行的指令
c、 Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。 因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储
d、此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域

1.2 java虚拟机栈

a、线程私有的内存区域
b、 虚拟机栈描述的是 Java 方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧 用于存储局部变量表、 操作数栈、 动态链接、 方法出口等信息。 每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程 
c、 这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度(无限递归调用),将抛出 StackOverflowError 异常;如果虚拟机栈可以动态扩展 ,如果扩展时无法申请到足够的内存,就会抛出 OutOfMemoryError 异常

1.3 本地方法栈

a、线程私有的内存区域
b、 本地方法栈( Native Method Stack )与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务
c、与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowErrorOutOfMemoryError异常

1.4 java堆

a、线程共享的内存区域
b、 几乎所有的对象实例都在这里分配内存,是垃圾收集器管理的主要区域
c、主流虚拟机按照可扩展来实现的(通过-Xmx-Xms控制)。 如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出 OutOfMemoryError 异常

1.5 方法去

a、线程共享的内存区域
b、用于存储已被虚拟机加载的类信息、 常量、 静态变量、 即时编译器编译后的代码等数据
c、也会进行垃圾回收, 这区域的内存回收目标主要是针对常量池的回收和对类型的卸载
d、 当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError 异常

1.5.1 运行时常量池

a、运行时常量池是方法区的一部分
b、用于存放编译期生成的各种字面量和符号引用
c、 运行时常量池相对于 Class 文件常量池的另外一个重要特征是具备动态性, Java 语言并不要求常量一定只有编译期才能产生,运行期间也可能
将新的常量放入池中
d、当常量池无法再申请到内存时会抛出OutOfMemoryError异常

1.6 直接内存

a、直接内存不是Java虚拟机规范中定义的内存区域
b、但是java也有使用: JDK 1.4中新加入了NIONew Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。 这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据
c、如果管理员设置 各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现 OutOfMemoryError 异常


2. 对象创建概述


2.1 虚拟机遇到一条new指令

a、检查这个指令的参数是否能在常量池中定位到一个类的符号引用
b、检查这个符号引用代表的类是否已被加载、 解析和初始化过。 如果没有,那必须先执行相应的类加载过程。对象所需内存的大小在类加载完成后便可完全确定
c、分配内存:
指针碰撞:假设Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离
空闲列表:虚拟机维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录
如果对象A与对象B同事分配怎么办?分配动作同步处理或者每个线程在java堆上预先分配一小块内存,满了再同步处理
d、初始化零值
e、设置这个对象是哪个类的实例、元数据、哈希码、GC分代年龄等
f、调用内置对象的init>方法进行初始化

2.2 对象内存布局

a、对象在内存中存储的布局分为3块区域:对象头、实例数据和对齐填充
b、对象头包括两部分信息:
第一部分用于存储对象自身的运行时数据,包括如哈希码GC分代年龄、 锁状态标志、 线程持有的锁、 偏向线程ID、 偏向时间戳等
第二部分数据是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
c、 实例数据部分是对象真正存储的有效信息
d、对齐填充并不是必然存在的,仅仅起着占位符的作用。VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,如果实例数据没有对齐,则通过它进行补齐

2.3 对象的访问定位

栈上的reference引用如何定位堆上的对象?
使用句柄方式: Java 堆中将会划分出一块内存来作为句柄池, reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据(Java堆)与类型数据(方法区)各自的具体地址信息
直接指针访问:就是直接访问


3. 垃圾回收机制


3.1 如何确定对象已死?

a、引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。无法解决对象A与对象B相互引用问题(大部分虚拟机不使用此算法)
b、可达性分析算法:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。GC Roots包括:虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI引用的对象

3.2 引用类型

a、作用:当内存空间还足够时,则能保留在内存之中;如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象
b、强引用:只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。Object obj=new Object()
c、软引用: 在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。SoftReference
d、弱引用:被弱引用关联的对象只能生存到下一次垃圾收集发生之前。WeakReference
e:虚引用: 完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例,唯一目的就是能在这个对象被收集器回收时收到一个系统通知。PhantomReference

3.3 回收标记

a、如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选。筛选的条件是此对象是否有必要执行finalize()方法。 当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为没有必要执行
b、GC将对F-Queue(finalize()方法对象的队列)中的对象进行第二次小规模的标记
c、finalize()只会被调用一次、不建议使用,可用try  catch代替

3.4 回收方法区

a、常量池:回收废弃常量与回收Java堆中的对象类似,没有对象引用即可回收
b、类回收:java堆中不存在实例、加载该类的classloader被回收、该类的class对象没有被引用

3.5 垃圾回收算法

a、标记-清楚算法:见名知意。缺点:效率不高、会产生空间碎片,碎片太多会导致大对象无法分配



b、复制算法:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。 当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。简单高效、但可用内存缩小了一半,可使用8:1:1进行分配

c、 标记 - 整理算法:标记过程仍然与标记-清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

d、分代收集:新声代(复制)与老年代(标记),两个分别使用不同的算法。

3.5 垃圾回收器

a、Serial收集器(新生代,client端):单线程进行垃圾回收工作,Stop The World
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值