浅谈Java对象的内存分配

文章介绍了Java对象的内存分配过程,包括栈上分配的可能性和逃逸分析的作用。逃逸分析用于判断对象是否可以在栈上分配以减少GC压力。对象通常在新生代Eden区分配,经过MinorGC进行存活对象的移动。达到一定年龄或满足特定条件的对象会进入老年代。文章还提到了垃圾回收的引用计数法和可达性分析算法,以及如何判断无用的类进行方法区的回收。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇我们介绍了Java对象创建的过程,那么这一篇我们介绍下Java对象的内存分配过程。

 上图是内存分配的大致流程,

首先我们看到的是对象是否进行栈上分配,我们知道对象是分配在堆中的,当我们堆中的对象没有被引用而成为垃圾对象时,需要依靠GC来进行回收内存,但当这些未被引用的对象太多时 会给GC带来较大的压力,为了减少这些临时对象在堆上进行分配。JVM进行了一些优化,它通过逃逸分析来判断一个对象是否可以在栈上分配,如果可以那么就在栈上分配,这样随着栈帧的出栈该对象占用的空间就被销毁了,这样就减少了垃圾回收的压力。

接下来我们说说什么是逃逸分析:分析对象的动态作用域,当一个对象在方法中被定义后,它可能被外部方法所引用,那么这种对象就不适合栈上分配。如下图所示,第一种情况user对象就不适合在栈上分配,第二种情况的user对象就适合在栈上分配。

开启逃逸分析  XX+DoEscapeAnalysis 

开启标量替换  XX:+EliminateAllocations (jdk 1.7 之后默认开启)

public User test1() {
 User user = new User();
 user.setId(1);
 user.setName("zhuge");
 return user;
 }

 public void test2() {
     User user = new User();
     user.setId(1);
     user.setName("zhuge");
 }

TIP1 : 堆分为新生代和老年代,新生代中又分Eden区 和Survior  区,它们的比例默认是8:1:1 

-XX:+UseAdaptiveSizePolicy(默认开启) 会导致这个8:1:1比例自动变化

-XX:-UseAdaptiveSizePolicy 自适应

TIP2 :

Minor GC/Young GC:指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。

Major GC/Full GC :一般会回收老年代 ,年轻代,方法区的垃圾,Major GC的速度一般会比Minor GC的慢 10倍以上。
看完上面的两个TIP我们就可以说说对象在堆中的分配了,大多数情况,对象是在新生代的Eden区分配的,当Eden区放满时会触发minor gc ,剩余存活的对象则会被挪到survior区 survior区分为 s1 , s2 区 首先会挪到s1区 ,当再次发生minor gc时 会清空 Eden 和 s1 将存活的对象挪动到 ,s2区,如果 survior区装不下,那么会提前挪动到老年代中。当再次发生minor gc时,Eden 和 s2会被清空,存活的对象会挪动到 s1,如此反复进行。下面画个图方便理解:

 

 minor gc 的大致过程就是按照上图循环往复。需要注意的是,每惊醒一次minor gc 对象的分代年龄就会+1 ,当 达到15(默认)时就会挪向老年代,当然不同的垃圾收集器会有略微的不同,我们可以通过jvm参数来进行控制:

-XX:MaxTenuringThreshold   设置对象进入老年代的年龄阈值

当然除了上面讲述的分配特性还有其它的特性,如大对象直接进入老年代,因为大对象需要大量连续的内存空间,在内存分配复制时十分消耗资源,我们可以通过jvm参数设置大对象的大小

-XX:PretenureSizeThreshold=1000000 (单位是字节)
-XX:+UseSerialGC

需要注意的是 这个参数的设置 只在Serial 和 ParNew 两个垃圾收集器下有效。

接下我们再说一下,对象的动态年龄判断机制,首先介绍下概念,在S区中一批对象的总大小 超过S区内存大小的50%时,

-XX:TargetSurvivorRatio  可以指定超过的百分比

那么大于等于这批对象年龄最大值的对象就直接进入老年代,用公式表达就是 年龄1+年龄2+...+年龄N >= 1/2 S 

年龄N + 年龄N以上 的所有对象进入老年代,这样是为了让那些长期存活的对象今早进入老年代。

上述这些操作一搬会在minor gc 后触发。除了这些还有像老年代空间分配担保机制 等有兴趣的可以自己去了解下。

说完了分配,这里再说下对象内存的回收,堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)。

这里有两个方法用来判断,第一个是 引用计数法

给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失效,计数器就减1;任何时候计数器为0 的对象就是不可能再被使用的 这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决 对象之间相互循环引用的问题
另一个方法则是 可达性分析算法 “GC Roots” 对象作为起点,从这些节点开始向下搜索引用的对象,找到的对象都标记为 非垃圾对象,其余未标记的对象都是垃圾对象。 GC Roots 根节点: 线程栈的本地变量 静态变量、本地方法栈的变量等等

 

接下来再说个小知识点:

如何判断一个类是无用的类
方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?
类需要同时满足下面3个条件才能算是 “无用的类”
该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
加载该类的 ClassLoader 已经被回收。(自定义加载器,如jsp的热加载)
该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值