写点什么

Java 入门你值得拥有!天天都是面对对象编程,你真的了解你的对象吗

发布于: 2 小时前

前言

很久没有发过文章,今天来说一下应届生找工作的问题吧,就算你是打摆子都要看完~~


金九银十是社招以及校招的火热时期,但今年很明显没有往年般的火热,面试也是越来越难了。对于应届生来说,如何能够在面试官眼中脱颖而出显得尤为重要。好在,我帮你们找到了一份非常全面的面试资料,针对于应届生以及转行过来的人群都是极具价值的,接下来就一起看看这份笔记。


对象的内存分配

实例分配原则




  1. 尝试栈上分配

  2. 基于逃逸分析和标量替换,将线程私有对象直接分配在栈上

  3. 在函数调用完毕后自动销毁对象,不需要 GC 回收

  4. 栈空间很小,默认 108K,不能分配大对象

  5. 尝试 TLAB

  6. 判断是否使用 TLAB(Thread Local Allocation Buffer)技术

  7. 虚拟机参数 -XX:+UseTLAB,-XX:-UseTLAB,默认开启

  8. 虚拟机参数-XX:TLABWasteTargetPercent 来设置 TLAB 占用 eEden 空间百分比,默认 1%

  9. 虚拟机参数-XX:+PrintTLAB 打印 TLAB 的使用情况

  10. TLAB 本身占用 eEden 区空间,空间很小不能存放大对象,

  11. 每个线程在 Java 堆中预先分配了一小块内存,当有对象创建请求内存分配时,就会在该块内存上进行分配

  12. 使用线程控制安全,不需要在 Eden 区通过同步控制进行内存分配

  13. 尝试老年代分配(堆分配原则)

  14. 如果可以直接进入老年代,直接在老年代分配

  15. 以上都失败时(注意分配对象时很容易触发 GC,堆分配原则)

  16. 内存连续时:使用指针碰撞(Serial、ParNew 等带 Compact 过程的收集器)

  17. 分配在堆的 Eden 区,该区域内存连续

  18. 指针始终指向空闲区的起始位置。

  19. 在新对象分配空间后,指针向后移动了该对象所占空间的大小个单位,从而指向新的空闲区的起始位置

  20. 对象分配过程中使用了 CAS 加失败重试的方式来保证线程安全(CAS 即原子操作)

  21. 如果成功:则进行对象头信息设置

  22. 内存不连续时:使用空闲列表(CMS 这种基于 Mark-Sweep 算法的收集器)

  23. 如果堆空间不是连续的,则 JVM 维护一张关系表,来使内存逻辑上连续从而达到对象分配的目



堆分配原则:




  • 优先在 Eden(伊甸园)区进行分配

  • 可通过-XX:SurvivorRation=8 来确定 Eden 与 Survivor 比例为 8:1

  • 新生代存在 2 个 Survivor 区域(From 和 To),当新生代 10 份时,Survivor 共占 2 份,Eden 占 8 份

  • 新建对象会先在 Eden 中分配

  • 空间足够时直接分配

  • 当 Eden 空间不足时

  • 将 Eden 内的对象进行一次 Minor Gc 回收准备放入进入 From 类型的 Survivor 区

  • From 类型的 Survivor 区

  • 空间足够时,放置 GC 对象时将 GC 对象回收进来

  • 空间不足时,将 GC 对象直接放入老年代中

  • Minor GC 后 Eden 空间仍然不足

  • 新建对象直接进入老年代

  • 长期存活的对象移交老年代(永久代)

  • 在 Eden 的对象经过一次 Minor GC 进入 Survivo 区后,对象的对象头信息年龄字段 Age+1

  • Survivor 区对象每经过一次 Minor GC 对象头信息年龄字段 Age+1

  • 会在 From Survivor 和 ToSurvivor 区进行来回 GC(复制算法)

  • 当对象的年龄达到一定值(默认 15 岁)时就会晋升到老年代

  • -XX:MaxTenuringThreshold=15 设置分代年龄为 15

  • 大对象直接进入老年代(永久代)

  • 大对象为占用堆内大量连续空间的对象(数组类、字符串)

  • -XX:MaxTenuringThreshold=4M 可以设置大于 4M 的对象直接进入老年代

  • 动态年龄判断

  • GC 回收对象时并不一定必须严格要求分代年龄进行晋升老年代

  • 当 Survivor 区的同年龄对象的总和大于 Survivor 空间 1/2 时

  • 年龄大于等于该年龄(相同年龄)的对象都可以直接进入老年代

  • 老年代对象分配使用空间分配担保

  • 新生代所有对象大小小于老年代可用空间大小时,Minor GC 是安全的

  • 相当于新生代所有对象都可以放到老年代里面,因而不会出现溢出等现象

  • 相反,Minor GC 是不安全的

  • 相当于新生代对象只能有一部分可以放入老年代,另一部分会因为空间不足而放入失败

  • 安全措施-XX:HandlePromotionFailure=true,允许担保失败

  • 发生 MinorGC 之前,JVM 会判断之前每次晋升到老年代的平均大小是否大于老年代剩余空间的大小

  • 若小于于并且允许担保失败则进行一次 Minor GC

  • 对象 GC 预测平稳,不会发生大量对象突然进入老年代导致其空间不足而溢出

  • 若小于并且不允许担保失败则进行一次 full GC

  • 即使对象 GC 预测平稳,但是不保证不会激增,所以安全点还是先去 Full GC 下

  • 回收所有区域,给老年代清理出更多空间

  • 若小于即使允许担保失败也进行一次 full GC

  • 即 Minor GC 后的存活对象数量突然暴增,即使允许担保失败但是还是极大可能是不安全的

  • 回收所有区域,给老年代清理出更多空间

对象实例组成

  • 对象头


  • MarkWord(必须)

  • 类型指针:指向对象的类元数据(非必须)

  • 数组长度(数组类型对象才有)

  • 实例数据


  • 对象的字段属性,方法等,存储在堆中

  • 数据填充


  • JVM 要求 java 的对象占的内存大小应该是 8bit 的倍数

  • 实例数据有可能不是 8 的倍数,需要使用 0 进行填充对齐


MarkWord 结构


对象的初始化

由于对象初始化涉及到类加载,这里不多描述

  • 分配到的空间设置为 0

  • 数据填充 0,8 字节对齐

  • 对象头信息设置

  • 调用<init>进行初始化(类的实例化)


给个示例先体会下


public class ClinitObject {
??? static ClinitObject clinitObject;
??? static {??????? b = 2;??????? clinitObject = new ClinitObject();??????? System.out.println(clinitObject.toString());??? }
??? int a = 1;??? static int b;??? final static int c = b;??? final static String d = new String("d");??? String e = "e";??? String f = "f";
??? public ClinitObject() {??????? e = d;??????? a = c;??? }
??? @Override??? public String toString() {??????? return "ClinitObject{" + "\n" +??????????????? "\t" + "a=" + a + "\n" +??????????????? "\t" + "b=" + b + "\n" +??????????????? "\t" + "c=" + c + "\n" +??????????????? "\t" + "d=" + d + "\n" +??????????????? "\t" + "e=" + e + "\n" +??????????????? "\t" + "f=" + f + "\n" +??????????????? '}';??? }
??? public static void main(String[] args) {??????? System.out.println(clinitObject.toString());??? }}
复制代码


控制台


ClinitObject{  a=0  b=2  c=0  d=null  e=null  f=f}ClinitObject{  a=0  b=2  c=2  d=d  e=null  f=f}
复制代码

对象的大小计算

  • 普通对象


  • 4 或 8 字节(MarkWord)+4 或 8 字节(klass Reference)+实例数据长度+ 0 填充(Padding)

  • 数组对象


  • 4 或 8 字节(MarkWord)+4 或 8 字节(klass Reference)+4 字节(ArrayLength)+实例数据长度+0 填充(Padding)

  • 其它说明:


  • 对象头(MarkWord)在 32 位 JVM 中为 4 字节,在 64 位 JVM 中为 8 字节

  • 为了节约空间,使用了指针压缩技术:

  • JDK6 开始对类型指针(Reference)进行压缩,压缩前 8 字节,压缩后 4 字节

  • 参数 -XX:+UseCompressedOops

  • JDK8 开始新增元数据空间 metaSpace,于是新增参数来控制指针压缩:

  • -XX:+UseCompressedClassPointers(指针压缩开关,堆内存>=32G 时,自动关闭)

  • -XX:CompressedClassSpaceSize (Reference 指向的类元数据空间大小,默认 1G,上限 32G)

  • 数据填充(Padding)为保证对象大小为 8 的整数倍的数据填充,使数据对齐

  • 常用数据类型大小



对象的定位

java 源码中调用对象在 JVM 中是通过虚拟机栈中本地变量标的 reference 来指向对象的引用来定位和访问堆中的对象的,访问方式存在主流的 2 种


  • 句柄访问


  • jvm 堆中单独维护一张 reference 与对象实例数据(实例化数据)和对象类型数据(ClassFile 数据)的关系表

  • 通过该关系表来查找到 java 实例对象

  • 直接访问(Sun HotSpot 采用该方式)


  • reference 直接指向了 java 堆中对象的实例数据(实例化数据),该实例对象的类型指针(Reference)指向类型数据(ClassFile 数据)

指针压缩示例

public class CompressedClassPointer {
??? public static void main(String[] args) throws IOException {??????? People people=new People();??????? System.in.read();??? }}
复制代码

启用指针压缩(默认)

JVM 参数


-server -XX:+UseCompressedOops -XX:+UseCompressedClassPointers -XX:CompressedClassSpaceSize=1G
复制代码


堆内存查看


$ jps1144011744 RemoteMavenServer14928 KotlinCompileDaemon15540 Launcher15908 Jps9996 CompressedClassPointer
复制代码


$ jmap.exe -histo 9996
num #instances #bytes class name----------------------------------------------... 233: 1 32 cn.tinyice.demo.object.People
复制代码

关闭指针压缩

JVM 参数


-server -XX:-UseCompressedOops
复制代码

总结

总的来说,面试是有套路的,一面基础,二面架构,三面个人。


最后,小编这里收集整理了一些资料,其中包括面试题(含答案)、书籍、视频等。希望也能帮助想进大厂的朋友,点击这里即可免费获取





用户头像

VX:vip204888 领取资料 2021.07.29 加入

还未添加个人简介

评论

发布
暂无评论
Java入门你值得拥有!天天都是面对对象编程,你真的了解你的对象吗