字节跳动算法工程师面试总,JVM学习

public class Test {

public static void main(String[] args) {

System.out.println(“Hello World!”);

}

}

然后使用 javap -v Test.class 命令反编译查看结果。

在这里插入图片描述

每条指令都会对应常量池表中一个地址,常量池表中的地址可能对应着一个类名、方法名、参数类型等信息。

在这里插入图片描述常量池:

就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量信息

运行时常量池:

常量池是 *.class 文件中的,当该类被加载以后,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址

5.5)StringTable

5.6)StringTable特性

  • 常量池中的字符串仅是符号,只有在被用到时才会转化为对象

  • 利用串池的机制,来避免重复创建字符串对象

  • 字符串变量拼接的原理是StringBuilder

  • 字符串常量拼接的原理是编译器优化

  • 可以使用intern方法,主动将串池中还没有的字符串对象放入串池中

以下代码信息包含:

  • 常量池与串池的关系

  • 字符串变量拼接底层实现原理

  • 编译期优化

public class Demo01 {

//1、当程序运行时候常量池中的信息都会被加载到运行时常量池中,这时a b ab 都还是符号,还没有变为java字符串对象

//2、当程序运行到了String s1 = “a”;这一行时jvm就会把a 符号变为"a"字符串对象

//3、再将"a"字符串对象放到stringTable[“a”,“b”](串池)中,stringTable是hashTable结构的不能扩容,且不能重复

//4、下一次再有引用字符串a时候就直接去stringTable中找,找不到才会创建字符串对象

public static void main(String[] args) {

String s1 = “a”;

String s2 = “b”;

String s3 = “ab”;

//相当于new StringBuilder().append(“a”).append(“b”).toString()也相当于new String(“ab”)

//但是这种new是在堆内存中它和方法区中的常量池中的"ab"地址不同,也不会去常量池中寻找

String s4 = s1 + s2;

System.out.println(s3==s4); //false

//这一种的s5是在编译期间已经确定为"ab"了所以,这行代码会在常量池中寻找"ab"

String s5 = “a” + “b”;

System.out.println(s3==s5);//true

}

}

5.7) intern方法 1.8

调用字符串对象的 intern 方法,会将该字符串对象尝试放入到串池中

  • 如果串池中没有该字符串对象,则放入成功

  • 如果有该字符串对象,则放入失败

  • 无论放入是否成功,都会返回串池中的字符串对象

注意

  • jdk1.8此时如果调用 intern 方法成功,堆内存与串池中的字符串对象是同一个对象;如果失败,则不是同一个对象

  • jdk1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,

放入串池, 会把串池中的对象返回

例1:

public class Demo02 {

public static void main(String[] args) {

//此时串池中有了[“a”,“b”]

//堆空间中有了new String(“ab”)对象

String s = new String(“a”) + new String(“b”);

//将字符串new String(“ab”)对象尝试放入到串池中,如果串池有就不会放入,没有就会放入;返回串池中的对象

//此时串池中[“a”,“b”,“ab”]

String s1 = s.intern();

String s2 = “ab”;

System.out.println(s == s1);//true

System.out.println(s2 == s);//true

}

}

例2:

public class Demo02 {

public static void main(String[] args) {

String s2 = “ab”;

//此时串池中有了[“a”,“b”,“ab”]

//堆空间中有了new String(“ab”)对象

String s = new String(“a”) + new String(“b”);

//将字符串new String(“ab”)对象尝试放入到串池中,如果串池有就不会放入,没有就会放入;返回串池中的对象

//此时串池中[“a”,“b”,“ab”]

String s1 = s.intern();

System.out.println(s == s1);//false

System.out.println(s2 == s);//false

}

}

5.8) 面试题

/**

  • 演示字符串相关面试题

*/

public class Demo1_21 {

public static void main(String[] args) {

String s1 = “a”;

String s2 = “b”;

String s3 = “a” + “b”; // ab

String s4 = s1 + s2; // new String(“ab”)

String s5 = “ab”;

String s6 = s4.intern();

// 问

System.out.println(s3 == s4); // false

System.out.println(s3 == s5); // true

System.out.println(s3 == s6); // true

String x2 = new String(“c”) + new String(“d”); // new String(“cd”)

x2.intern();

String x1 = “cd”;

// 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢

System.out.println(x1 == x2);

}

}

5.9)StringTable 的位置

jdk1.6 StringTable 位置是在永久代中,1.8 StringTable 位置是在堆中。

图示

在这里插入图片描述

5.10)StringTable 垃圾回收

-Xmx10m 指定堆内存大小

-XX:+PrintStringTableStatistics 打印字符串常量池信息

-XX:+PrintGCDetails

-verbose:gc 打印 gc 的次数,耗费时间等信息

/**

  • 演示 StringTable 垃圾回收

  • -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc

*/

public class Code_05_StringTableTest {

public static void main(String[] args) {

int i = 0;

try {

for(int j = 0; j < 10000; j++) { // j = 100, j = 10000

//将堆中对象放入到串池中,串池里的值是唯一的

String.valueOf(j).intern();

i++;

}

}catch (Exception e) {

e.printStackTrace();

}finally {

System.out.println(i);

}

}

}

因为StringTable是由HashTable实现的,所以可以适当增加HashTable桶的个数,来减少字符串放入串池所需要的时间

-XX:StringTableSize=桶个数(最少设置为 1009 以上)

考虑是否需要将字符串对象入池

可以通过 intern 方法减少重复入池

6、直接内存


1)定义

Direct Memory

  • 常见于 NIO 操作时,用于数据缓冲区

  • 分配回收成本较高,但读写性能高

  • 不受 JVM 内存回收管理

2)使用直接内存的好处

文件读写流程:

在这里插入图片描述

因为 java 不能直接操作文件管理,需要切换到内核态,使用本地方法进行操作,然后读取磁盘文件,会在系统内存中创建一个缓冲区,将数据读到系统缓冲区, 然后在将系统缓冲区数据,复制到 java 堆内存中。缺点是数据存储了两份,在系统内存中有一份,java 堆中有一份,造成了不必要的复制。

使用了 DirectBuffer 文件读取流程

在这里插入图片描述

直接内存是操作系统和 Java 代码都可以访问的一块区域,无需将代码从系统内存复制到 Java 堆内存,从而提高了效率。

3)直接内存回收原理

直接内存的回收不是通过 JVM 的垃圾回收来释放的,而是通过unsafe.freeMemory 来手动释放。

底层是创建了一个 DirectByteBuffer 对象。

DirectByteBuffer(int cap) { // package-private

super(-1, 0, cap, cap);

boolean pa = VM.isDirectMemoryPageAligned();

int ps = Bits.pageSize();

long size = Math.max(1L, (long)cap + (pa ? ps : 0));

Bits.reserveMemory(size, cap);

long base = 0;

try {

base = unsafe.allocateMemory(size); // 申请内存

} catch (OutOfMemoryError x) {

Bits.unreserveMemory(size, cap);

throw x;

}

unsafe.setMemory(base, size, (byte) 0);

if (pa && (base % ps != 0)) {

// Round up to page boundary

address = base + ps - (base & (ps - 1));

} else {

address = base;

}

cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); // 通过虚引用,来实现直接内存的释放,this为虚引用的实际对象, 第二个参数是一个回调,实现了 runnable 接口,run 方法中通过 unsafe 释放内存。

att = null;

}

这里调用了一个 Cleaner 的 create 方法,且后台线程还会对虚引用的对象监测,如果虚引用的实际对象(这里是 DirectByteBuffer )被回收以后,就会调用 Cleaner 的 clean 方法,来清除直接内存中占用的内存。

直接内存的回收机制总结

  • 使用了 Unsafe 类来完成直接内存的分配回收,回收需要主动调用freeMemory 方法

  • ByteBuffer 的实现内部使用了 Cleaner(虚引用)来检测 ByteBuffer 。一旦ByteBuffer被垃圾回收,那么会由 ReferenceHandler(守护线程) 来调用 Cleaner 的 clean 方法调用 freeMemory来释放内存

注意:

/**

  • -XX:+DisableExplicitGC 显示的

*/

private static void method() throws IOException {

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1GB);

System.out.println(“分配完毕”);

System.in.read();

System.out.println(“开始释放”);

byteBuffer = null;

System.gc(); // 手动 gc 失效

System.in.read();

}

一般用 jvm 调优时,会加上下面的参数:

-XX:+DisableExplicitGC // 禁止显示的 GC,也就是禁止代码中的System.gc()

意思就是禁止我们手动的 GC,比如手动 System.gc() 无效,它是一种 full gc,会回收新生代、老年代,会造成程序执行的时间比较长。所以我们最好通过 unsafe 对象调用 freeMemory 的方式释放内存

三、垃圾回收

=====================================================================

1、如何判断对象可以回收


1)引用计数法

当一个对象被引用时,就当引用对象的值加一,当值为 0 时,就表示该对象不被引用,可以被垃圾收集器回收。

这个引用计数法听起来不错,但是有一个弊端,如下图所示,循环引用时,两个对象的计数都为1,导致两个对象都无法被释放。

在这里插入图片描述

2)可达性分析算法

JVM 中的垃圾回收器通过可达性分析来探索所有存活的对象

扫描堆中的对象,看能否沿着 GC Root 对象为起点的引用链找到该对象,如果找不到,则表示可以回收

可以作为 GC Root 的对象:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。

  • 方法区中类静态属性引用的对象

  • 方法区中常量引用的对象

  • 本地方法栈中 JNI(即一般说的Native方法)引用的对象

public static void main(String[] args) throws IOException {

ArrayList list = new ArrayList<>();

list.add(“a”);

list.add(“b”);

list.add(1);

System.out.println(1);

System.in.read();

list = null;

System.out.println(2);

System.in.read();

System.out.println(“end”);

}

对于以上代码,可以使用如下命令将堆内存信息转储成一个文件(快照),然后使用

Eclipse Memory Analyzer 工具打开进行分析。

第一步:

使用 jps 命令,查看程序的进程

在这里插入图片描述第二步:

在这里插入图片描述

使用 jmap -dump:format=b,live,file=1.bin 16104 命令转储文件

dump:转储文件

format=b:二进制文件

file:文件名

16104:进程的id

第三步:打开 Eclipse Memory Analyzer 对 1.bin 文件进行分析。

在这里插入图片描述分析的 gc root,找到了 ArrayList 对象,然后将栈帧中的 list 置为null,再次转储,那么堆中的 list 对象就会被回收。

3)四种引用

在这里插入图片描述

  • 强引用

只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收

  • 软引用(SoftReference)

仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象

可以配合引用队列来释放软引用自身

  • 弱引用(WeakReference)

仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。

下面的面试题答案都整理成文档笔记。也还整理了一些面试资料&最新2021收集的一些大厂的面试真题(都整理成文档,小部分截图)

在这里插入图片描述

最新整理电子书

在这里插入图片描述

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
10)]

[外链图片转存中…(img-pP3B2EH5-1713572132512)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。

下面的面试题答案都整理成文档笔记。也还整理了一些面试资料&最新2021收集的一些大厂的面试真题(都整理成文档,小部分截图)

[外链图片转存中…(img-dp9M5xZn-1713572132514)]

最新整理电子书

[外链图片转存中…(img-NnkQXzWT-1713572132515)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值