2024Java面试心得,个人笔记,java面试核心知识

线程私有内存

  • 程序计数器:记录当前线程待执行的下一条指令位置,上下文切换后恢复执行,由字节码解释器负责更新

  • JVM 栈

  • 描述 Java 方法执行的内存模型:执行新方法时创建栈帧,存储局部变量表、操作数栈等信息

  • 存储单位:变量槽 slot,long, double占 2 个 slot,其他基本数据类型、引用类型占 1 个,故表的总长度在编译期可知

  • 本地方法栈:执行本地 C/C++ 方法


04、JVM 对象

1. 创建对象

分配堆内存:类加载完毕后,其对象所需内存大小是确定的;堆内存由多线程共享,若并发创建对象都通过 CAS 乐观锁争夺内存,则效率低。故线程创建时在堆内存为其分配私有的分配缓冲区(TLAB:Thread Local Allocation Buffer)

  • 内存模型

e46b11972fff92d138fc301cec58169c.png

  • 分配流程

21f378a47c87ad03470886c3ea15bfc5.png

注:当 TLAB 剩余空间不足以分配新对象,但又小于最大浪费空间阈值时,才会加锁创建新的 TLAB

零值初始化对象的堆内存、设置对象头信息、执行构造函数 ()V

2. 对象的内存布局

对象头

  • Mark Word:记录对象的运行时信息,如 hashCode,GC 分代年龄,尾部 2 bit 用于标记锁状态

054d11e248fed2caacfb2e31ace8b284.png

  • Class Pointer:指向所属的类信息

  • 数组长度(可选,对象为数组):4 字节存储其长度

对象数据:各种字段的值,按宽度分类紧邻存储

对齐填充:内存对齐为 1 个字长整数倍,减少 CPU 总线周期

如果您正在学习Spring Boot,推荐一个连载多年还在继续更新的免费教程:http://blog.didispace.com/spring-boot-learning-2x/

验证:openjdk/jol 检查对象内存布局

public class User {

private int age = -1;

private String name = “unknown”;

}

// java -jar ~/Downloads/jol-cli-latest.jar internals -cp . com.jol.User

OFF  SZ               TYPE DESCRIPTION               VALUE

0   8                    (object header: mark)     0x0000000000000001 (non-biasable; age: 0)

8   4                    (object header: class)    0xf8021e85 // User.class 引用地址

12   4                int User.age                  -1         // 基本类型则直接存储值

16   4   java.lang.String User.name                 (object)   // 引用类型,指向运行时常量池中的 String 对象

20   4                    (object alignment gap)               // 有 4 字节的内存填充

Instance size: 24 bytes


05、内存溢出

堆内存-Xms指定堆初始大小,当大量无法被回收的对象所占内存超出-Xmx上限时,将发生内存溢出 OutOfMemoryError

  • 排查:通过 Eclipse MAT 分析 -XX:+HeapDumpOnOutOfMemory生成的 *.hprof 堆转储文件,定位无法被回收的大对象,找出其 GC Root 引用路径

  • 解决:若为内存泄露,则修改代码用null显式赋值、虚引用等方式及时回收大对象;若为内存溢出,大对象都是必须存活的,则调大-Xmx、减少大对象的生命周期、检查数据结构使用是否合理等

// -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

public class HeapOOM {

static class OOMObject {}

public static void main(String[] args) {

List vs = new ArrayList<>();

while (true)

vs.add(new OOMObject());

}

}

分析 GC Root 发现com.ch02.HeapOOM对象间接引用了大量的OOMObject对象,共占用 15.4MB 堆内存,无法回收最终导致 OOM

bb9ef39f5d4504a5d200d866252bd3b4.png

栈内存-Xss指定栈大小,当栈深度超阈值(比如未触发终止条件的递归调用)、本地方法变量表过大等,都可能导致内存溢出 StackOverflowError

方法区-XX:MetaspaceSize指定元空间初始大小,-XX:MaxMetaspaceSize指定最大大小,默认 -1 无限制,若在运行时动态生成大量的类,则可能触发 OOM

运行时常量池strObj.intern()动态地将首次出现的字符串对象放入字符串常量池并返回,JDK7 前会拷贝到永久代,之后则直接引用堆对象

String s1 = “java”; // 类加载时,从字节码常量池中拷贝符号到了运行时常量池,在解析阶段初始化的字符串对象

String s2 = “j”;

String s3 = s2 + “ava”; // 堆上动态分配的字符串对象

println(s3 == s1);          // false

println(s3.intern() == s1); // true // 已在字符串常量池中存在

直接内存-XX:MaxDirectMemorySize指定大小,默认与-Xmx一样大,不被 GC 管理,申请内存超阈值时 OOM


06、垃圾回收与内存分配

GC 可分解为 3 个子问题:which(哪些内存可被回收)、when(什么时候回收)、how(如何回收)

07、GC 条件

1. 引用计数算法(reference counting)

原理:每个对象都维护一个引用计数器rc,当通过赋值、传参等方式引用它时rc++,当引用变量修改指向、离开函数作用域等方式解除引用时rc--,递减到 0 时说明对象无法再被使用,可回收。伪代码:

assign(var, obj):

incr_ref(obj) # self = self # 先增再减,避免引用自身导致内存提前释放

decr_ref(var)

var = obj

incr(obj):

obj.rc++

decr(obj):

obj.rc–

if obj.rc == 0:

remove_ref(obj) # 断开 obj 与其他对象的引用关系

gc(obj)         # 回收 obj 内存

优点:思路简单,对象无用即回收,延迟低,适合内存少的场景

缺点:此算法中对象是孤立的,无法在全局视角检查对象的真实有效性,循环引用的双方对象需引入外部机制来检测和回收,如下图红色圈(图源:what-is-garbage-collection)

如果您正在学习Spring Boot,推荐一个连载多年还在继续更新的免费教程:http://blog.didispace.com/spring-boot-learning-2x/

2083955b18ea1f3950469bdbd5edb64c.png

2. 可达性分析算法(reachability analysis)

原理:从肯定不会被回收的对象(GC Roots)出发,向外搜索全局对象图,不可达的对象即无法再被使用,可回收;常见可作为 GC Root 的对象有:

  • 执行上下文:JVM 栈中参数、局部变量、临时变量等引用的堆对象

  • 全局引用:方法区中类的静态引用、常量引用(如 StringTable 中的字符串对象)所指向的对象

优点:无需对象维护 GC 元信息,开销小;单次扫描即可批量识别、回收对象,吞吐高

缺点:多线程环境下对象间的引用关系随时在变化,为保证 GC Root 标记的准确性,需在不变化的 snapshot 中进行,会产生 Stop The World(以下简称 STW) 卡顿现象

33d8b10e3c3b520d01ec3016dba55eff.png

3. 四种引用类型

| 引用类型 | 类 | 回收时机 |

| — | — | — |

| 强引用 | - | 只要与 GC Root 存在引用链,则不被回收 |

| 软引用 | SoftReference | 只被软引用所引用的对象,当 GC 后内存依然不足,才被回收 |

| 弱引用 | WeakReference | 只被弱引用所引用的对象,无论内存是否足够,都将被回收 |

| 虚引用 | PhantomReference | 被引用的对象无感知,进行正常 GC,仅在回收时通知虚引用(回调) |

示例:限制堆內存 50MB,其中新生代 30MB,老年代 20MB;依次分配 5 次 10MB 的byte[]对象,仅使用软引用来引用,观察 GC 过程

public static void main(String[] args) {

// softRefList --> SoftReference --> 10MB byte[]

List<SoftReference<byte[]>> softRefList = new ArrayList<>();

ReferenceQueue<byte[]> softRefQueue = new ReferenceQueue<>(); // 无效引用队列

for (int i = 0; i < 5; i++) {

SoftReference<byte[]> softRef = new SoftReference<>(new byte[1010241024], softRefQueue);

softRefList.add(softRef);

for (SoftReference<byte[]> ref : softRefList) // dump 所有软引用指向的对象,检查是否已被回收

System.out.print(ref.get() == null ? "gced " : "ok ");

System.out.println();

}

Reference<? extends byte[]> ref = softRefQueue.poll();

while (ref != null) {

softRefList.remove(ref); // 解除对软引用对象本身的引用

ref = softRefQueue.poll();

}

System.out.println("effective soft ref: " + softRefList.size()); // 2

}

// java -verbose:gc -XX:NewSize=30m -Xms50m -Xmx50m -XX:+PrintGCDetails com.ch02.DemoRef

ok

ok ok

// 分配第三个 []byte 时,Eden GC 无效,触发 Full GC 将一个 []byte 晋升到老年区

// 此时三个 byte[] 都只被软引用所引用,被标记为待二次回收(若为弱引用,此时 Eden 已被回收)

[GC (Allocation Failure) --[PSYoungGen: 21893K->21893K(27136K)] 21893K->32141K(47616K), 0.0046324 secs]

[Full GC (Ergonomics) [PSYoungGen: 21893K->10527K(27136K)] [ParOldGen: 10248K->10240K(20480K)] 32141K->20767K(47616K), [Metaspace: 2784K->2784K(1056768K)], 0.004 secs]

ok ok ok

// 再次 GC,前三个 byte[] 全部被回收

[GC (Allocation Failure) --[PSYoungGen: 20767K->20767K(27136K)] 31007K->31007K(47616K), 0.0007963 secs]

[Full GC (Ergonomics) [PSYoungGen: 20767K->20759K(27136K)] [ParOldGen: 10240K->10240K(20480K)] 31007K->30999K(47616K), [Metaspace: 2784K->2784K(1056768K)], 0.003 secs]

[GC (Allocation Failure) --[PSYoungGen: 20759K->20759K(27136K)] 30999K->30999K(47616K), 0.0007111 secs]

[Full GC (Allocation Failure) [PSYoungGen: 20759K->0K(27136K)] [ParOldGen: 10240K->267K(20480K)] 30999K->267K(47616K), [Metaspace: 2784K->2784K(1056768K)], 0.003 secs]

gced gced gced ok

gced gced gced ok ok

4. finalize

原理:若对象不可达,被标记为可回收后,会进行finalize()是否被重写、是否已执行过等条件筛选,若通过则对象会被放入 F-Queue 队列,等待低优先级的后台 Finalizer 线程触发其finallize() 的执行(不保证执行结束),对象可在finalize中建立与 GC Root 对象图上任一节点的引用关系,来逃脱 GC

使用:finalize 机制与 C++ 中的析构函数并不等价,其执行结果并不确定,不推荐使用,可用try-finally替代


08、GC 算法

分代收集理论

两个分代假说:符合大多数程序运行的实际情况

  • 弱分代假说:绝大多数对象是朝生夕灭,生存时间极短

  • 强分代假说:熬过越多次 GC 的对象,越可能被继续使用,越难以回收

对应地,JVM 堆被划分为 2 个不同区域,将对象按年龄分类,兼顾了 GC 耗时与内存利用率

  • 新生代:大量对象将被回收,只关注仍存活的对象,逐步晋升

  • 老年代:大量对象不被回收,只关注要被回收的对象

跨代引用

  • 问题:老年代会引用新生代,新生代 GC 时需遍历老年代中大量的存活对象,分析可达性,时间复杂度高

  • 背景:相互引用的对象倾向于同时存亡,比如跨代引用关系中的新生代必然会逐步晋升,最终消除跨代关系

  • 假说:跨代引用相比同代引用只占极少数,无需全量扫描老年代

  • 实现:新生代维护全局数据结构:记忆集(Remembered Set),将老年代分为多个子块,标记存在跨代引用的子块,等待后续扫描;代价:为保证记忆集的正确性,需在跨代引用建立或断开时保持同步

2fd4fe00a32f8ed02d90436fc7483e9a.png

09、标记清除:Mark-Sweep

  • 原理:标记不可达对象,统一清理回收,反之亦可

  • 缺点:执行效率不稳定,回收耗时取决于活跃对象的数量;内存碎片多,会出现内存充足但无法分配过大的连续内存(数组)

3dc4e88a1b1baa745ca739cfe9afe0ee.png

10、标记复制:Mark-Copy

  • 理论:将堆内存切为两等份 A, B,每次仅使用 A,用完后标记存活对象复制到 B,清空 A 后执行 swap

  • 优点:直接针对半区回收,无内存碎片问题;分配内存只需移动堆顶指针,高效顺序分配

  • 缺点:当 A 区有大量存活对象时,复制开销大;B 区长时间闲置,内存浪费严重

  • 实践:对于存活对象少的新生代,无需按 1:1 分配,而是按 8:1:1 的内存布局,其中 Eden 和 From 区同时使用,只有 To 区会被闲置(担保机制:若 To 区不够容纳 Minor GC 后的存活对象,则晋升到老年区)

bc7cfbf458fda74ba14662fa548f3b03.png

11、标记整理:Mark-Compact

  • 原理:标记存活对象后统一移动到内存空间一侧,再回收边界之外的内存

  • 优点:内存模型简单,无内存碎片,降低内存分配和访问的时间成本,能提高吞吐

  • 缺点:对象移动需 STW 同步更新引用关系,会增加延迟

67662d2863be5f82292069679fe5eeb0.png


12、HotSpot GC 算法细节

13、发起 GC:安全点与安全区域

  • 问题:为保证可达性分析结果的准确性,需挂起用户线程(STW),再从各线程的执行上下文中收集 GC Root,如何通知线程挂起?

  • 安全点:HotSpot 内部有线程中断标记;在各线程的方法调用、循环跳转、异常跳转等会长时间执行的指令处,额外插入检查该标记的test高效指令;若轮询发现标记为真,线程会主动在最近的 SafePoint 处挂起,此时其栈上对象的引用关系不再变化,可收集 GC Root 对象

  • 如果您正在学习Spring Boot,推荐一个连载多年还在继续更新的免费教程:http://blog.didispace.com/spring-boot-learning-2x/

  • 安全区域:引用关系不会变化的指令区域,可安全地收集 GC Root;线程离开此区域时,若 GC Root 收集过程还未结束,则需等待

示意图

4e3a847ebfd0cb0a1777c18e4d318551.png

14、加速 GC:CardTable

问题:非收集区域(老年代)会存在到收集区域(新生代)的跨代引用,如何避免对前者的全量扫描?

卡表:记忆集的字节数组实现;将老年代内存划分为 Card Page(512KB)大小的子内存块,若新建跨代引用,则将对应的 Card 标记为 dirty,GC 时只需扫描老年代中被标记为 dirty 的子内存块

3a4b29e59c377de840ab1ca255e1faa2.png

写屏障:有别于volatile禁用指令重排的内存屏障,GC 中的写屏障是在对象引用更新时执行额外 hook 动作的机制。简单实现:

void oop_field_store(oop* field, oop new_val) { // oop: ordinary object pointer

// pre_write_barrier(field, new_val); // 写前屏障:更新前先执行,使用 oop 旧状态

*field = new_val;

post_write_barrier(field, new_val); // 写后屏障:更新完才执行

}

使用写屏障保证 CardTable 的实时更新(图源:The JVM Write Barrier - Card Marking)

4474d57242913401993731e33c303dd5.png

15、正确 GC:并发可达性分析

参考演讲:Shenandoah: The Garbage Collector That Could by Aleksey Shipilev

问题:GC Roots 的对象源固定,故枚举时 STW 时间短暂且可控。但后续可达性分析的时间复杂度与堆中对象数量成正相关,即堆中对象越多,对象图越复杂,堆变大后 STW 时间不可接受

解决:并发标记。引出新问题:用户线程动态建立、解除引用,标记过程中图结构发生变化,结果不可靠;证明:用三色法描述对象状态

如果您正在学习Spring Boot,推荐一个连载多年还在继续更新的免费教程:http://blog.didispace.com/spring-boot-learning-2x/

  • 白色:未被回收器访问过的对象;分析开始都是白色,分析结束还是白色则不可达

  • 灰色:被回收器访问过,但其上至少还有 1 个引用未被扫描(中间态)

  • 黑色:被回收器访问过,其上引用全部都已被扫描,存在引用链,为存活对象;若其他对象引用了黑色对象,则不必再扫描,肯定也存活;黑色不可能直接引用白色

STW 无并发的正确标记:顶部 3 个对象将被回收

b31f8c606d8c809395f1054a892cb096.png

用户线程并发修改引用,会导致标记结果无效,分 2 种情况:

  • 少回收,对象标记为存活,但用户解除了引用:产生浮动垃圾,可接受,等待下次 GC

e20dfbc616dd75121ec047d89f2024b7.png

  • 误回收,对象标记为可回收,但用户新建了引用:实际存活对象被回收,内存错误

a6dcb613aca4dfca769d507ef5ab2a76.png

论文《Uniprocessor Garbage Collection Techniques - Paul R. Wilson》§3.2 证明了「实际存活的对象被标记为可回收」必须同时满足两个条件(有时间序)

  • 插入一条或多条从黑色到_白色_的新引用

  • 删除所有灰色到_该白色_的直接、间接引用

为正确实现标记,打破其中一个条件即可(类比打破死锁四个条件之一的思想),分别对应两种方案:

  • 增量更新 Increment Update:记录黑到白的引用关系,并发标记结束后,以黑为根,重新扫描;A 直接存活

  • 原始快照 SATB(Snapshot At The Begining):记录灰到白的解引用关系,并发标记结束后,以灰为根,重新扫描;B 为灰色,最后变为黑色,存活。需注意,若没有步骤 3,则 B,C 变为浮动垃圾


16、经典垃圾回收器

搭配使用示意图:

c08e05f58fb707dd547d6af190cb8c66.png

17、Serial, SerialOld

原理:内存不足触发 GC 后会暂停所有用户线程,单线程地在新生代中标记复制,在老年代中标记整理,收集完毕后恢复用户线程

优点:全程 STW 简单高效

缺点:STW 时长与堆对象数量成正相关,且 GC 线程只能用到 1 core 无法加速

场景:单核 CPU 且可用内存少(如百兆级),JDK1.3 之前的唯一选择

58fefebd37483fce879e38f80597029a.png

18、ParNew

原理:多线程并行版的 Serial 实现,能有效减少 STW 时长;线程数默认与核数相同,可配置

另外,如果您正在学习Spring Cloud,推荐一个连载多年还在继续更新的免费教程:https://blog.didispace.com/spring-cloud-learning/

场景:JDK7 之前搭配老年代的 CMS 回收器使用

a083361602e92a596c172a8f1ffa6fd6.png

19、Parallel, Parallel Old

垃圾回收有两个通常不可兼得的目标

  • 低延迟:STW 时长短,响应快;允许高频、短暂 GC,比如调小新生代空间,加快收集延迟(吞吐下降)

  • 高吞吐量:用户线程耗时 /(用户线程耗时 + GC 线程耗时)高,GC 总时间低;允许低频、单次长时间 GC,(延迟增加)

8b92bd9d01d10674a1e1011263badefd.png

原理:与 ParNew 类似都是并行回收,主要增加了 3 个选项(倾向于提高吞吐量)

  • -XX:MaxGCPauseTime:控制最大延迟

  • -XX:GCTimeRatio:控制吞吐(默认 99%)

  • -XX:+UseAdaptiveSizePolicy :启用自适应策略,自动调整 Eden 与 2 个 Survivor 区的内存占比-XX:SurvivorRatio,老年代晋升阈值 -XX:PretenureSizeThreshold

aa50c78856294948095a870f034ac5fe.png

20、CMS

CMS:Concurrent Mark Sweep,即并发标记清除,主要有 4 个阶段

  • 初始标记(initial mark):STW 快速收集 GC Roots

  • 并发标记(concurrent mark):从 GC Roots 出发检测引用链,标记可回收对象;与用户线程并发执行,通过增量更新来避免误回收

  • 重新标记(remark):STW 重新分析被增量更新所收集的 GC Roots

  • 并发清除(concurrent sweep):并发清除可回收对象

74b68bddd6c7e360aab1f9f627a54168.png

优点:两次 STW 时间相比并发标记耗时要短得多,相比前三种收集器,延迟大幅降低

缺点

  • CPU 敏感:若核数较少(< 4core),并发标记将占用大量 CPU 时间,会导致吞吐突降

  • 无法处理浮动垃圾:-XX:CMSInitiatingOccupancyFration(默认 92%)指定触发 CMS GC 的阈值;在并发标记、并发清理的同时,用户线程会产生浮动垃圾(引用可回收对象、产生新对象),若浮动垃圾占比超过-XX:CMSInitiatingOccupancyFration;若 GC 的同时产生过多的浮动垃圾,导致老年代内存不足,会出现 CMS 并发失败,退化为 Serial Old 执行 Full GC,会导致延迟突增

  • 无法避免内存碎片:-XX:CMSFullGCsBeforeCompaction(默认 0)指定每次在 Full GC 前,先整理老年代的内存碎片

  • 另外,如果您正在学习Spring Cloud,推荐一个连载多年还在继续更新的免费教程:https://blog.didispace.com/spring-cloud-learning/

21、G1

特点:基于 region 内存布局实现局部回收;GC 延迟目标可配置;无内存碎片问题

f808a3e8be6a637304849b22f5d2d3fe.png

|

| G1 | 之前回收器 |

| — | — | — |

| 堆内存划分方式 | 多个等大的 region, 各 region 分代角色并不固定,按需在 Eden, Survivor, Old 间切换 | 固定大小、固定数量的分代区域 |

| 回收目标 | 回收价值高的 region 动态组成的回收集合 | 新生代、整个堆内存 |

跨代引用:各 region 除了用卡表标记各卡页是否为 dirty 之外,还用哈希表记录了各卡页正在被哪些 region 引用,通过这种“双向指针”机制,能直接找到 Old 区,避免了全量扫描(G1 自身内存开销大头)

bdf41232ad57ace00ec889a4254aad11.png

G1 GC 有 3 个阶段(参考其 GC 日志)

  • 新生代 GC:Eden 区占比超阈值触发;标记存活对象并复制到 Survivor 区,其内可能有对象会晋升到 Old 区

6fea2e1f6b320fdbdeaed9c1c5f8363d.png

  • 老年代 GC:Old 区占比达到阈值后触发,执行标记整理

  • 初始标记:枚举 GC Roots,已在新生代 GC 时顺带完成

  • 并发标记:并发执行可达性分析,使用 SATB 记录引用变更

  • 重新标记:SATB 分析,避免误回收

  • 筛选回收:将 region 按回收价值和时间成本筛选组成回收集,STW 将存活对象拷贝到空 regions 后清理旧 regions,完成回收

  • 混合 GC

828f97e4ddab7fb51ff6dfaa826e5a95.png

参数控制(文档:HotSpot GC Tuning Guide)

| 参数及默认值 | 描述 |

| — | — |

| ‐XX:+UseG1GC | JDK9 之前手动启用 G1 |

| -XX:MaxGCPauseMillis=200 | 预期的最大 GC 停顿时间;不宜过小,避免每次回收内存少而导致频繁 GC |

| -XX:ParallelGCThreads=N | STW 并行线程数,若可用核数 M < 8 则 N=1,否则默认 N=M*5/8 |

| -XX:ConcGCThreads=N | 并发阶段并发线程数,默认是 ParallelGCThreads 的 1/4 |

| -XX:InitiatingHeapOccupancyPercent=45 | 老年代 region 占比超过 45% 则触发老年代 GC |

| -XX:G1HeapRegionSize=N | 单个 region 大小,1~32MB |

| -XX:G1NewSizePercent=5, -XX:G1MaxNewSizePercent=60 | 新生代 region 最小占整堆的 5%,最大 60%,超出则触发新生代 GC |

| -XX:G1HeapWastePercent=5 | 允许浪费的堆内存占比,可回收内存低于 5% 则不进行混合回收 |

| -XX:G1MixedGCLiveThresholdPercent=85 | 老年代存活对象占比超 85%,回收价值低,暂不回收 |

| -XX:G1MixedGCCountTarget=8 | 单次收集中混合回收次数 |


22、内存分配策略

使用 Serial 收集器 -XX:+UseG1GC 演示

1. 对象优先分配在 Eden 区

新对象在 Eden 区分配,空间不足则触发 Minor GC,存活对象拷贝到 To Survivor,若还是内存不足则通过分配担保机制转移到老年区,依旧不足才 OOM

byte[] buf1 = new byte[6 * MB];

byte[] buf2 = new byte[6 * MB]; // 10MB 的 eden 区剩余 4MB,空间不足,触发 minor GC

// java -verbose:gc -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:+UseSerialGC com.ch03.Allocation

// minor gc 后新生代内存从 6M 降到 0.2M,存活对象移到了老年区,总的堆内存用量依旧是 6MB

[GC (Allocation Failure) [DefNew: 6823K->286K(9216K), 0.002 secs] 6823K->6430K(19456K), 0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

Heap

def new generation   total 9216K, used 6513K

eden space 8192K,  76% used // buf2

from space 1024K,  28% used

to   space 1024K,   0% used

tenured generation   total 10240K, used 6144K

the space 10240K,  60% used // buf1

2. 大对象直接进入老年区

对于 Serial, ParNew,可配置超过阈值 -XX:PretenureSizeThreshold 的大对象(连续内存),直接在老年代中分配,避免触发 minor gc,导致 Eden 和 Survivor 产生大量的内存复制操作

byte[] buf1 = new byte[4 * MB];

// java -verbose:gc -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:+UseSerialGC

// -XX:PretenureSizeThreshold=3145728 com.ch03.Allocation // 3145728 即 3MB

Heap

def new generation   total 9216K, used 843K

eden space 8192K,  10% used

from space 1024K,   0% used

to   space 1024K,   0% used

tenured generation   total 10240K, used 4096K

the space 10240K,  40% used // buf1

3. 长期存活的对象进入老年代

对象头中 4bit 的 age 字段存储了对象当前 GC 分代年龄,当超过阈值-XX:MaxTenuringThreshold(默认 15,也即 age 字段最大值)后,将晋升到老年代,可搭配-XX:+PrintTenuringDistribution观察分代分布

byte[] buf1 = new byte[MB / 16];

byte[] buf2 = new byte[4 * MB];

byte[] buf3 = new byte[4 * MB]; // 触发 minor gc

buf3 = null;

buf3 = new byte[4 * MB];

// java -verbose:gc -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:+UseSerialGC

// -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution com.ch03.Allocation

[GC (Allocation Failure) [DefNew

Desired survivor size 524288 bytes, new threshold 1 (max 1)

- age   1:     359280 bytes,     359280 total

: 4839K->350K(9216K)] 4839K->4446K(19456K), 0.0017247 secs]

// 至此,buf1 熬过了第一次收集,age=1

[GC (Allocation Failure) [DefNew

Desired survivor size 524288 bytes, new threshold 1 (max 1): 4446K->0K(9216K)] 8542K->4438K(19456K)]

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

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

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

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

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

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后

整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

image

image

其实面试这一块早在第一个说的25大面试专题就全都有的。以上提及的这些全部的面试+学习的各种笔记资料,我这差不多来回搞了三个多月,收集整理真的很不容易,其中还有很多自己的一些知识总结。正是因为很麻烦,所以对以上这些学习复习资料感兴趣

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
[外链图片转存中…(img-AwViw817-1712380194307)]
[外链图片转存中…(img-mboYflwp-1712380194308)]
[外链图片转存中…(img-WBo8QNkG-1712380194308)]
[外链图片转存中…(img-lwyketwb-1712380194308)]
[外链图片转存中…(img-PYRZTClX-1712380194309)]
[外链图片转存中…(img-n3i75yZw-1712380194309)]

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

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

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-mQBGpm35-1712380194310)]

最后

整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

[外链图片转存中…(img-vJZTP9GQ-1712380194310)]

[外链图片转存中…(img-Vu3Ud2D5-1712380194310)]

其实面试这一块早在第一个说的25大面试专题就全都有的。以上提及的这些全部的面试+学习的各种笔记资料,我这差不多来回搞了三个多月,收集整理真的很不容易,其中还有很多自己的一些知识总结。正是因为很麻烦,所以对以上这些学习复习资料感兴趣

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值