做网站怎么配电脑,北京知名的网站建设公司排名,硬件开发属于什么行业,国际最新十大新闻事件文章目录 1. JVM视角下#xff0c;内存划分2. 类内存分布硬核详解1. 获取堆内存参数2. 扫描堆内存#xff0c;定位实例3. 查看实例所在地址的数据4. 找到实例所指向的类信息的地址5. 查看class信息6. 结论 3. Java的对象创建流程4. 垃圾判别算法4.1 引用计数法4.2 可达性分析… 文章目录 1. JVM视角下内存划分2. 类内存分布硬核详解1. 获取堆内存参数2. 扫描堆内存定位实例3. 查看实例所在地址的数据4. 找到实例所指向的类信息的地址5. 查看class信息6. 结论 3. Java的对象创建流程4. 垃圾判别算法4.1 引用计数法4.2 可达性分析算法 5. 垃圾收集算法5.1 标记-清除算法5.2 标记-复制算法5.3 标记-整理算法 1. JVM视角下内存划分 tip: 额外补充 在以“分代设计”为主导的堆内存其控件划分大致如上图所示。但G1垃圾回收期为分解后续的内存设计并没有都参考分代理论因此jdk8以后G1大规模运用在jdk8之后内存划分有待商榷堆虽然是线程共享的但他可以为线程划分缓冲区——Thread Local Allocation BufferTLAB。TLAB是线程私有的。但无论怎么划分堆都是存储对象实例直接内存属于操作系统本地内存不归JVM管理。因此GC对他无效 2. 类内存分布硬核详解
既然是硬核不来点内存轰炸是对不起硬核两字。
下文主要讲述一个类在创建过程中可能会涉及到的所有类在内存的分布情况。包括JVM层面的instanceKlassJava层面的Test实例Test.class
下文内容比较硬核请读者酌情阅读。另外底层指针分析可能存在纰漏欢迎读者友善指出
让我们开始 demo代码如下
package com.xhf.test;// -XX:UseSerialGC -Xmn10M -XX:-UseCompressedOops
public class TestDemo {public static void main(String[] args) {new Test();while (true) {}}
}package com.xhf.test;public class Test {private static Integer a;private Integer b;private int c;public int d;private void func() {}public void func2() {}
}
1. 获取堆内存参数
打开HSDB扫描堆的整体内存范围 universe
Heap Parameters:
Gen 0: eden [0x0000000080000000,0x00000000803845a8,0x0000000080800000) space capacity 8388608, 43.96257400512695 usedfrom [0x0000000080800000,0x0000000080800000,0x0000000080900000) space capacity 1048576, 0.0 usedto [0x0000000080900000,0x0000000080900000,0x0000000080a00000) space capacity 1048576, 0.0 usedInvocations: 0Gen 1: old [0x0000000080a00000,0x0000000080a00000,0x000000008fe00000) space capacity 255852544, 0.0 usedInvocations: 0其它信息我们可以不用关注只需要知道eden区的范围是0x0000000080000000 0x0000000080800000绝大多数情况下对象的空间有限划分在eden区域。因此我们想要探查Test示例相关内存地址需要扫描eden区域
2. 扫描堆内存定位实例
scanoops 0x0000000080000000 0x0000000080800000 com.xhf.test.Test
hsdb scanoops 0x0000000080000000 0x0000000080800000 com.xhf.test.Test
0x000000008023e2d0 com/xhf/test/Test主程序运行new Test();他的实例对象被划分在0x000000008023e2d0地址
3. 查看实例所在地址的数据
inspect 0x000000008023e2d0
hsdb inspect 0x000000008023e2d0
instance of Oop for com/xhf/test/Test 0x000000008023e2d0 0x000000008023e2d0 (size 32)
_mark: 1
_metadata._klass: InstanceKlass for com/xhf/test/Test
b: null null
c: 0
d: 0在控制台上通过指令查看不到最全面的信息通过Tools-inspector创建可视化窗口可以查看最全面的信息具体如下 通过上述两幅图我们可以返现很多有趣的细节
_mark字段mark其实就是markword对象头的意思。markword能够存储相当丰富的信息比如分代年龄gc次数偏向锁重锁等等信息。_metadata._klass类型指针指向类型com.xhf.test.Test.class。该字段用于表示当前实例是哪个类的实例b, c, d3个字段属于oop但a不属于oopa属于Test.class因为他是静态变量。此外b这个Object被赋值nullcd两个基本int类型赋值为0
4. 找到实例所指向的类信息的地址
我们找到Test oop但没有找到存储Test类信息的数据地址。inspect无法直接看到_metadata._klass指向的地址我们通过内存扫描直接查看内存数据
mem 0x000000008023e2d0 2 查看0x000000008023e2d0地址偏移2个单位8bit
hsdb mem 0x000000008023e2d0 2
0x000000008023e2d0: 0x0000000000000001
0x000000008023e2d8: 0x0000000013ff3400 0x0000000013ff3400就是oop指向的Test类信息所在地址 注意笔者这里并没有说明0x0000000013ff3400是Test.class类对象的地址 5. 查看class信息
如下图所示0x0000000013ff3400才是class真正的信息这也被称为元信息被JVM存储在meta space中 需要注意的是0x0000000013ff3400地址上的内容不是Java意义上的Test.class这个类
笔者为什么会这么说呢原因是JVM内部采用C的instanceKlass描述 Java类并且会将instanceKlass分配到meta space
而instanceKlass有个叫做_java_mirror的字段它指向的才是Java类的Class对象 本例中就是Test.class这个对象
我们监视这个地址inspect 0x000000008023e210
hsdb inspect 0x000000008023e210
instance of Oop for java/lang/Class 0x000000008023e210 0x000000008023e210 (size 168)
a: null null发现_java_mirror指向的对象是java/lang/Class类Test.class并且大小168bit
我们扫描0x000000008023e210往后的168bit21个8bit内存空间
mem 0x000000008023e210 21
hsdb mem 0x000000008023e210 21
0x000000008023e210: 0x0000000000000001
0x000000008023e218: 0x0000000013c03ed0
0x000000008023e220: 0x0000000000000000
0x000000008023e228: 0x0000000000000000
0x000000008023e230: 0x0000000000000000
0x000000008023e238: 0x00000000800dba38
0x000000008023e240: 0x0000000000000000
0x000000008023e248: 0x0000000000000000
0x000000008023e250: 0x0000000000000000
0x000000008023e258: 0x0000000000000000
0x000000008023e260: 0x0000000000000000
0x000000008023e268: 0x0000000000000000
0x000000008023e270: 0x0000000000000000
0x000000008023e278: 0x0000000080239560
0x000000008023e280: 0x0000000000000000
0x000000008023e288: 0x0000000000000000
0x000000008023e290: 0x0000000013ff3400
0x000000008023e298: 0x0000000000000000
0x000000008023e2a0: 0x0000001500000000
0x000000008023e2a8: 0x0000000000000001
0x000000008023e2b0: 0x0000000000000000 发现内存地址为0x000000008023e290时存放的数据是0x0000000013ff3400
而0x0000000013ff3400的内容恰好是instanceKlass所在地址。
6. 结论
基于上述分析我们得出如下结论
Test实例 - Test instanceKlass - Test.class
文字枯燥乏味看图就好理解了 3. Java的对象创建流程
有了第2节的基础第三节的分析自然就简单多了。
具体流程直接上图
这个流程中具体的内存情况如下 tip: 严格来说上图存在一定的问题。 由第2节可知实例的指针指向的是instanceKlass而非class对象。这里这么处理是为了方便画图。 而且instanceKlass拥有class对象的指针实例可以通过instanceKlass找到class对象只是需要两次指针跳跃所以上图绘制方式其实也并无太大问题 4. 垃圾判别算法
4.1 引用计数法
给对象增加计数器当计数器为0表示对象不再被引用。可以当作垃圾被垃圾清除器清理
这种算法的缺陷很明显一方面开销大JVM需要维护所有对象的引用计数器另一方面无法解决循环引用的问题
4.2 可达性分析算法
以GC Root根节点的集合作为起始点。按照对象之间的引用关系向下遍历如果某个对象无法和GC Root关联那么我们认为该对象是不可达的可以当作垃圾被回收 5. 垃圾收集算法
在讲解回收算法前我们需要补充一些分代理论的基础知识
大部分对象都是朝生幕死创建出来很快就被回收如果一个对象经历了多次垃圾回收那么该对象可以被认为是长时间存活的对象
曾经有个组织做过调查98%的对象活不过一轮垃圾回收
考虑到对象存活时间长短存在差异我们可以大致将堆内存划分为两块空间
新生代Young Generation老年代Old Generation
新生代存放寿命短的对象老年代存放长命的对象。这样在做垃圾回收时可以根据不同区域对象存活特点做出不一样的垃圾回收策略以此提高运行效率
5.1 标记-清除算法
标记清楚算法是最基础的垃圾回收算法后续的算法基本都是在此基础上进行改进。
该算法的核心是
标记垃圾可达性分析算法清除垃圾 标记-清除算法执行流程如上图所示
上述算法存在以下两个缺陷
算法效率不稳定如果内存中存在大量需要清除的垃圾JVM需要执行多次的清除操作反之如果垃圾数量较少JVM执行清除操作次数就少空间碎片当JVM执行清除操作后会存在大量内存碎片内存中使用的空间不连续。这极大的降低了内存利用率提高了内存申请的难度
5.2 标记-复制算法
标记-复制算法将内存划分为等大的两个空间一个空间用于存放对象另一个空间用于预留。
当需要进行内存清除时操作异常容易因为两个区间在同一时刻只有一个区间存在使用的对象因此只需要将存放对象的空间中存活的对象复制到预留空间然后清除原有空间的所有内容即可完成垃圾回收 该算法让JVM只需要关注存活的对象如果存活对象少那么复制操作少效率高因此标记-复制算法一般用于Eden区域的垃圾回收。此外该算法成功解决了内存碎片的问题
但显而易见该算法带来了另一个问题
内存利用率低该算法需要额外的空间进行存储比标记清除算法大了1倍的空间
5.3 标记-整理算法
该算法就是在标记-清除的基础上增加了整理的操作。对于清除后的内存空间该算法会通过移动已使用的空间让内存的使用再次连续 该算法解决了内存碎片问题但移动存活对象这个操作引入了新的问题。就比如原先对象A引用了对象B现在B的地址修改了A如何感知到。此外在移动过程中需要暂停用户线程Stop the world)因此需要移动的对象数量要尽可能少以此减少stop the world的时间