深入理解Java虚拟机(笔记)
内存分配:
为对象分配内存有两种方式,第一种是“指针碰撞”,也就是把内存分为两边,一边是已使用区域,另一边是未分配区域,分界线用指针记录,当要分配内存时,只需把指针向未分配区域移动需要的空间即可,通常compact算法的垃圾回收会使用“指针碰撞”,如Serial、ParNew;另一种是空闲列表记录,也就是分配是可以不连续的,中间很多间隔可用的未分配内存,这个时候需要一个列表来对内存进行记录,分配内存时候就在列表找到最合适的,通常这种分配方式对应的垃圾回收器如CMS这种基于Mark-Sweep算法;
由于分配内存也是多线程,存在内存使用资源的竞争,因此要保证线程安全,解决这个问题有两种方案,第一种是利用CAS加上失败重试方法保持原子性;第二种是用到ThreadLocal思想,就是为每一个线程分配一个线程自己的空间,称为Thread Local Allocation Buffer,TLAB,当线程完成逃逸分析后就把对象分配到该区域内,该类方式还附有更快更好的分配和回收内存。最后,当线程分配完后虚拟机就马上为分配到的空间初始化为零值,不包括对象头。
对象头(Header):
对象可以分为三部分,对象头、实例数据、对齐填充;
对象头包括两种信息,第一是运行时数据(Mark Word),第二是类型指针(用作指定该对象是哪个类创建的,但也不一定就这样实现,因为如果reference是通过句柄来实现的,那么对象头就没必要记录类型指针了,因为句柄本身会记录对应类对象的地址);运行时数据包括:哈希吗、GC分代年龄、锁状态标志、线程持有的锁、偏向锁ID、偏向时间戳等等。如果该对象是数组,那么还会对象头还会继续数组大小的数据。
然后就是真正的实例数据了,实例数据包括父类继承下来的和子类拥有的,会优先安排父类的字段排序在前面,同时会根据基本类型的大小,把大小相同的放到一起分配。最后的数据对齐就是简单的填充作用。
对象定位:
线程要通过栈上面的reference数据去访问对象,访问对象的方法通常有两种实现,第一种是句柄,第二种是直接的指针引用;那么什么是句柄呢?句柄其实就是一个专门用来描述对象地址和类型地址的数据结构,因此如果用第二种指针引用访问对象,那么就需要对象自身附带指向类型对象的信息,而使用句柄访问对象就没这个必要了。而且还有一个特征就是句柄一般都放在一起,称为句柄池;最后,句柄还有个好处就是,当对象位置发生变化后只需要修改句柄即可,而不需要动所有战上面的reference。做到了一个类似反向代理的作用
StackOverFlowError和OutOfMemoryError:
先说由于栈分配导致溢出,其实这两种都是可能的,属于一个事件的不同描述,如果用一段代码不断的递归使得线程创建很多栈帧导致了内存溢出,那么在没有一个固定的栈帧数限制下,你是该说是数量太多导致的内存溢出还是总栈帧容量太大导致的呢?其实这类错误的解决很简单,首先总内存固定,方法区和堆内存最大值固定,其他太小忽略不算,所以如果没办法改变代码,就只能把堆内存和方法区内存减少就好了。
方法区内存溢出与String.intern
方法区能溢出可以通过常量池溢出,或者加载很多的类,前者可以用String.intern(),后者可以利用CGLib动态生成类字节码文件(一般是用来做增强的任务),这个同样是放在虚拟机常量池上面的。这里提及下String.intern(),因为该方法在1.7和之前的版本大不一样;区别在于去掉了永久代,在堆上添加了metaspace,所以如果一个字符串常量在堆中已经存储,那么String.intern(),方法只是在常量池中记录下堆中这个字符串的引用,而如果一个常量池中已经记录了某个字符串(可以是引用或者真字符数组),那么String.intern(),则什么都不做。
直接内存与Netty
Java1.4后出现了NIO,一种基于通道(channel)与缓冲区(Buffer)的IO方式,使用navtiv函数库直接分配堆外内存,且通过一个堆内的DirectByteBuffer对象作为这块内存的引用,因为不需要在堆内外来回复制数据,能显著提高性能,其中Netty也利用NIO进行封装的一个很好用的框架,NIO之所以能高速,直接原因是因为不需要在用户态和内核态之间来回复制数据。
垃圾收集:
程序计数器、虚拟机栈、本地方法栈随线程而生随线程而灭;线程每一个栈帧都是基本在编译的时候就确定了大小;如何判断对象是否要回收?引用计数法无法回收环引用,可达性分析法不错,其中根GC Root Set可以是虚拟机栈、本地方法栈、常量池、类静态引用;那么要不要回收还要判断reference:强引用、软引用、弱引用、虚引用,第一个是我们平时用的、第二个是准备要发生OutOfMemory才回收、第三个是到下一次GC回收、第四个有没有没啥区别就是回收的时候能收到一条系统消息;但是一个对象要被回收要被标记两次,这点和finalize()方法有关,需要执行finalize方法的对象会放近F-Queue中,这是它最后一次拯救自己的机会,如果失败;就GG;
为什么不推荐用finalize呢?这个不同与c的析构函数,它什么时候调用是不确定的,不要把关闭资源等放在里面,否则你可能一直关不了资源。
方法区的回收,包括常量池的回收和类的回收,例如前者如果一个字符串再也没有引用也会被回收,而一个类会不会被回收非常苛刻:1、该类没有实例;2、该类的类加载器已被回收;3、Class对象没有被引用没有反射调用;通常限制自定义虚拟机利用CGLib技术创建类都需要虚拟机回收类,不然容易爆炸。
内存分配与回收策略:
一般会分配在Eden区,如果有TLAB就分配在缓存,大对象直接分配年老代,具体取决于垃圾收集器组合;默认在Survivor中“熬过”15(可以通过-XX:MaxTenuringThreshold设定)次minor GC的对象会被移到年老代,当然也不一定,JVM也会动态判定应该放入年老代的年龄,例如如果当Survivor中一半对象的年龄相同,那么大于等于该年龄(<15)的对象都会被回收。
Minor GC:新生代GC,因为新生代大部分都是朝生夕灭的对象,所以Minor GC非常频繁,回收速度也快;
Major/Full GC:老年代GC,一般伴随一次Minor GC;速度很慢到Minor的十倍;
Class类文件结构:
Class文件是以8字节为基础单位的二进制字节流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有任何分隔符;其中超过8位字节以上的数据项采用大端方式存储;
Class文件格式采用一种类似于C语言结构体的伪结构存储数据,这种结构只有两种类型,无符号数和表,后面的解析都要以这两种数据类型为基础;
无符号数属于基本的数据类型u1、u2、u4、u8分别代表1字节、2字节、4字节、8字节的无符号数,无符号数可以描述数字、索引引用、数量值或者utf-8的字符串;
表是具有层次关系的复习结构的数据,整个Class文件本质上就是一张表,习惯性以info结尾;
魔数与class文件的版本:
每个Class文件头四个字节称为魔数,值为0xCAFEBABE,紧接的四个字节为Class版本号,第5、6个数次版本号,第7、8个是主版本号,其中java 1.8主版本号是52;
紧接下来的是常量池入口,以一个u2记录常量池的容量计数值(constant_pool_count);只有常量池是以1为索引开始的,因为0代表引用“不引用任何一个常量池项目”含义;
、、、、、待续
深入理解Java虚拟机(笔记)的更多相关文章
- Java内存区域与内存溢出异常——深入理解Java虚拟机 笔记一
Java内存区域 对比与C和C++,Java程序员不需要时时刻刻在意对象的创建和删除过程造成的内存溢出.内存泄露等问题,Java虚拟机很好地帮助我们解决了内存管理的问题,但深入理解Java内存区域,有 ...
- 深入理解java虚拟机笔记Chapter12
(本节笔记的线程收录在线程/并发相关的笔记中,未在此处提及) Java内存模型 Java 内存模型主要由以下三部分构成:1 个主内存.n 个线程.n 个工作内存(与线程一一对应) 主内存与工作内存 J ...
- 深入理解Java虚拟机笔记
1. Java虚拟机所管理的内存 2. 对象创建过程 3. GC收集 4. HotSpot算法的实现 5. 垃圾收集器 6. 对象分配内存与回收细节 7. 类文件结构 8. 虚拟机类加载机制 9.类加 ...
- 深入理解java虚拟机笔记Chapter7
虚拟机类的加载机制 概述 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类的加载机制. 类加载的时机 J ...
- 深入理解java虚拟机笔记之一
Java的技术体系主要有支撑java程序运行的虚拟机,提供各开发领域接口支持Java API,java编程语言及许多第三方java框架( 如Spring,Structs等)构成. 可以把Java程序设 ...
- 深入理解Java虚拟机笔记——虚拟机类加载机制
目录 概述 动态加载和动态连接 类加载的时机 类的生命周期 被动引用 例子一(调用子类继承父类的字段) 例子二(数组) 例子三(静态常量) 类加载的过程 加载 验证 准备 解析 符号引用 直接引用 初 ...
- 【转载】深入理解Java虚拟机笔记---运行时栈帧结构
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈(Virtual Machine Stack)的栈元素.栈帧存储了方法的局部变量表,操作 ...
- 深入理解java虚拟机笔记Chapter8
运行时栈帧结构 栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素.栈帧存储了方法 ...
- 深入理解java虚拟机笔记Chapter2
java虚拟机运行时数据区 首先获取一个直观的认识: 程序计数器 线程私有.各条线程之间计数器互不影响,独立存储. 当前线程所执行的字节码行号指示器.字节码解释器工作时通过改变这个计数器值选取下一条需 ...
- 类文件结构——深入理解Java虚拟机 笔记三
在之前的笔记中记录过,Java程序变成可执行文件的步骤是:源代码-->经过编译变成class文件-->经过JVM虚拟机变成可执行的二进制文件.因此,为了对JVM执行程序的过程有一个好的了解 ...
随机推荐
- Android Studio3.1.2升级问题:Configuration 'compile' is obsolete and has been replaced with 'implementation'.
每次升级Android Studio时,一般情况下Gradle版本的也会相应的升级,我之前Android Studio 3.0.1.Gradle 是4.1升级后为:Android Studio 3.1 ...
- angular 2 - 001 ng cli的安装和使用
angular cli 创建项目和组件 ng new my-app --skip-install cd my-app cnpm install ng serve localhost:4200 angu ...
- 【推荐】Hutool 的通用工具类库
摘自3.1.1版本作者发布原话,当时看到有点说不上的情绪,为作者的坚持.热爱点个赞. 已经想不起来是怎样结识 Hutool 的,但 Hutool 伴随几个项目的推进,获得了同事一致好评. 没经过实践和 ...
- c/c++字节序转换(转)
字节序(byte order)关系到多字节整数(short/int16.int/int32,int64)和浮点数的各字节在内存中的存放顺序.字节序分为两种:小端字节序(little endian)和大 ...
- mysql基础SQL练习
许久收藏的练习mysql语句的,现在看来任然有学习价值! 表如下: Student(Sid,Sname,Sage,Ssex) 学生表 Course(Cid,Cname,Tid) 课程表 SC(Sid, ...
- Git回滚代码到某个commit
回退命令: $ git reset --hard HEAD^ 回退到上个版本$ git reset --hard HEAD~3 回退到前3次提交之前,以此类推,回退到n次提交之前 $ git rese ...
- linux内核剖析(十一)进程间通信之-共享内存Shared Memory
共享内存 共享内存是进程间通信中最简单的方式之一. 共享内存是系统出于多个进程之间通讯的考虑,而预留的的一块内存区. 共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程 ...
- TensorFlow与caffe中卷积层feature map大小计算
刚刚接触Tensorflow,由于是做图像处理,因此接触比较多的还是卷及神经网络,其中会涉及到在经过卷积层或者pooling层之后,图像Feature map的大小计算,之前一直以为是与caffe相同 ...
- web多站点跨域访问
有时项目app和m 需要公用一套接口 这个时候就要用到跨域:特别是app接口跨域访问站点时. 跨域配置: 1.iis服务器上需要安装URLwrite2.0 2.web.config 需要添加这个配置: ...
- Kafka基本架构及原理
本文转载自http://www.cnblogs.com/cyfonly/p/5954614.html 一.为什么需要消息系统 1.解耦: 允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的 ...