对象创建时堆内存分配算法
-
指针碰撞
前提要求堆内存的绝对工整的。
所有用过的内存放一边,没用过的放另一边,中间放一个分界点的指示器,当有对象新生时就已经知道大小了,指示器只需要像没用过的内存那边移动与对象等大小的内存区域即可 -
空闲列表
假设堆内存并不工整,那么空闲列表最合适。
JVM维护一个列表 ,记录哪些内存块是可用的,当对象创建时从列表中找到一块足够大的空间划分给新生对象,并将这块内存标记为已用内存。
对象怎么定位
Java程序会通过栈上的reference数据来操作堆上的具体对象。
-
如果使用句柄访问,Java堆中将可能会划分一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的地址信息。
-
如果使用直接指针访问的话,reference中存储的就是对象地址,如果只是访问对象本身的话,就不需要对一次间接访问的开销。
使用直接指针的好处是速度更快,节省了一次指针定位的时间开销。
判断对象是否能被回收的算法
-
引用计数算法 在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的
-
可达性分析算法 通过一些列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程走过的路称为“引用链”,如果某个对象到GC Roots间没有任何引用链相连,则证明此对象不再被使用
如何判断对象是否能被回收
Java堆内存组成部分
从内存回收角度看,基于分代收集理论,Java堆分为新生代(Edun区,两个Survivor区),老年代,元空间。
从内存分配角度看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区 (Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率
什么时候抛出StackOverflowError
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
Java中会存在内存泄漏吗,请简单描述。
内存泄漏是指程序中动态分配的堆内存由于某种原因未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至崩溃。
原因:
- 内存中加载的数据量过大,如一次性从数据库取出过多数据;
- 集合类中有对象的引用,使用完后未清空,使得JVM不能回收;
- 代码中存在死循环或循环产生过多的重复对象实体;
- 启动参数内存值设定过小;
栈帧是什么?包含哪些东西
每个方法被执行的时候,Java虚拟机都 会同步创建一个栈帧,用于存储局部变量表、操作数栈、动态连接、方法出口等信息。
- 局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、 float、long、double)、对象引用和returnAddress 类型。
简述一个方法的执行流程
方法的执行到结束其实就是栈帧的入栈到出栈的过程,方法的局部变量会存到栈帧中的局部变量表里,递归的话会一直压栈压栈,执行完后进行出栈,所以效率较低,因为一直在压栈,栈是有深度的。
方法区会被回收吗
方法区回收价值很低,主要回收废弃的常量和无用的类。
如何判断无用的类:
- 该类所有实例都被回收(Java堆中没有该类的对象)
- 加载该类的ClassLoader已经被回收
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方利用反射访问该类
一个对象包含多少个字节
会占用16个字节。比如
Object obj = new Object();
因为obj引用占用栈的4个字节,new出来的对象占用堆中的8个字节,4+8=12,但是对象要求都是8的倍数,所以对象的字节对齐(Padding)部分会补齐4个字节,也就是占用16个 字节。
再比如:
public class NewObj {
int count;
boolean flag;
Object obj;
}
NewObj obj = new NewObj();
这个对象大小为:空对象8字节+int类型4字节+boolean类型1字节+对象的引用4字节=17字节,需要8的倍数,所以字节对齐需要补充7个字节,也就是这段程序占用24字节。
为什么把堆栈分成两个
- 栈代表了处理逻辑,堆代表了存储数据,分开后逻辑更清晰,面向对象模块化思想。
栈是线程私有,堆是线程共享区,这样分开也节省了空间,比如多个栈中的地址指向同一块堆内存中的对象。 - 栈是运行时的需要,比如方法执行到结束,栈只能向上增长,因此会限制住栈存储内容的能力,而堆中的对象是可以根据需要动态增长的。
栈的起始点是哪
main函数,也是程序的起始点。
为什么基本类型不放在堆里
因为基本类型占用的空间一般都是1-8个字节(所需空间很少),而且因为是基本类型,所以不会出现动态增长的情况(长度是固定的),所以存到栈上是比较合适的。反而存到可动态增长的堆上意义不大。
Java参数传递是值传递还是引用传递
值传递。
基本类型作为参数被传递时肯定是值传递;引用类型作为参数被传递时也是值传递,只不过“值”为对应的引用。假设方法参数是个对象引用,当进入被调用方法的时候,被传递的这个引用的值会被程序解释到堆中的对象,这个时候才对应到真正的对象,若此时进行修改,修改的是引用对应的对象,而不是引用本身,也就是说修改的是堆中的数据,而不是栈中的引用。
为什么参数大于2个要放到对象里
因为除了double和long类型占用局部变量表2个slot外,其他类型都占用1个slot大小,如果参数太多的话会导致这个栈帧变大,因为slot大,放个对象的引用上去的话只会占用1个slot,增加堆的压力减少栈的压力,堆自带GC,所以这点压力可以忽略。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了