java内存分配虚拟机会多次分配堆空间给同一个对象吗

在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。 &&
& 当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。 &&
& 堆内存用来存放由new创建的对象和数组。 &&
& 在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。 &&
& 在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。 &&
& 引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。 &&
java中变量在内存中的分配
1、类变量(static修饰的变量):在程序加载时系统就为它在堆中开辟了内存,堆中的内存地址存放于栈以便于高速访问。静态变量的生命周期--一直持续到整个"系统"关闭
2、实例变量:当你使用java关键字new的时候,系统在堆中开辟并不一定是连续的空间分配给变量(比如说类实例),然后根据零散的堆内存地址,通过哈希算法换算为一长串数字以表征这个变量在堆中的"物理位置"。 实例变量的生命周期--当实例变量的引用丢失后,将被GC(垃圾回收器)列入可回收&名单&中,但并不是马上就释放堆中内存
3、局部变量:局部变量,由声明在某方法,或某代码段里(比如for循环),执行到它的时候在栈中开辟内存,当局部变量一但脱离作用域,内存立即释放
附:java的内存机制
Java 把内存划分成两种:一种是栈内存,另一种是堆内存。
& & & 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java 会自动释放掉为该变量分配的内存空间,该内存空间可以立即被另作它用。
  堆内存用来存放由 new 创建的对象和数组,在堆中分配的内存,由 Java 虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或者对象之后,还可以在栈中定义一个特殊的变量,让栈中的这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或者对象,引用变量就相当于是为数组或者对象起的一个名称。引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用 new 产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。
  这也是 Java 比较占内存的原因,实际上,栈中的变量指向堆内存中的变量,这就是 Java 中的指针!
阅读(...) 评论()篇文章54 人订阅专注Java、Spring / Boot / Cloud,微服务架构,大数据,云计算,知识分享相关文章来自专栏274来自专栏334来自专栏226来自专栏86来自专栏274来自专栏350扫描二维码扫描关注云+社区java虚拟机堆和栈分配的比例一般是多少合适?_百度知道
java虚拟机堆和栈分配的比例一般是多少合适?
背景:tomcat,ssh框架的web工程,分配2G内存给java虚拟机那么,一般分配多少给堆,多少给栈;还有方法区,一般需要多大的内存空间。...
背景:tomcat,ssh框架的web工程,分配2G内存给java虚拟机 那么,一般分配多少给堆,多少给栈;还有方法区,一般需要多大的内存空间。
答题抽奖
首次认真答题后
即可获得3次抽奖机会,100%中奖。
你的回答被采纳后将获得:
系统奖励15(财富值+成长值)+难题奖励10(财富值+成长值)+提问者悬赏30(财富值+成长值)
来自电脑网络类芝麻团
采纳数:1556
获赞数:279
参与团队:
解除游戏限制一键安装极速版封喉虚拟机下载地址:
为你推荐:
其他类似问题
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。系统发生错误
文档【】不存在或已删除!
等待时间: 6禁用System.gc()
System.gc()会直接触发Full GC,同时对老年代和新生代进行回收;一般情况下垃圾回收应是自动进行的,无需手工触发;过于频繁地触发垃圾回收对系统性能没有好处;虚拟机提供了DisableExplicitGC来控制是否手工触发GC;System.gc()的实现如下:
Runtime.getRuntime().gc();
Runtime.gc()是一个native方法,最终实现在jvm.cpp中,如下所示:
如果设置了-XX:-+DisableExpblicitGC,条件判断就无法成立,那么就会禁用显示GC,使System.gc()等价于一个空函数调用;
System.gc()使用并发回收
System.gc()默认使用Full GC回收整个堆,会忽略参数中的UseG1GC和UseConcMarkSweepGC;
-XX:+ExplicitGCInvokesConeurrent:该参数会使System.gc()使用并发的方式进行回收;
并行GC前额外触发的新生的GC
并行回收器在每一次Full GC之前都会伴随一次新生代GC;
示例:下面的代码只是进行了一次简单的Full GC
public class ScavengeBeforeFullGC {
public static void main(String[]args) {
System.gc();
使用参数:-XX:+PrintGCDetails -XX:+UseSerialGC运行程序,
效果:System.gc()触发了一个Full GC操作
[Full GC[Tenured: 0K-&461K(87424K), 0.0101706 secs] 698K-&461K(126720K), [Perm : 2562K-&K)], 0.0103377 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
使用参数:-XX:+PrintGCDetails -XX:+UseParallelOldGC,运行程序
效果:使用并行回收器,触发Full GC之前,进行了一次新生代GC。
[GC [PSYoungGen: 675K-&536K(38912K)] 675K-&536K(125952K), 0.0051475 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[Full GC [PSYoungGen: 536K-&0K(38912K)] [ParOldGen: 0K-&461K(87040K)] 536K-&461K(125952K) [PSPermGen: 2562K-&K)], 0.0208193 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
因此,System.gc()触发了两次GC。这样做的目的是先将新生代进行一次收集,避免将所有回收工作同时交给一次Full GC进行,从而尽可能地缩短一次停顿时间
-XX:-ScavengeBeforeFullGC:该参数会去除发生在Full GC之前的那次新生代GC,默认为
对象何时进入老年代
对象首次创建时,会被放置在新生代的eden区。没有GC的介入,这些对象不会离开eden;
初创的对象在eden区:下面的代码申请了大约5MB内存
public class AllocEden {
public static final int_1K=1024;
public static void main(String[]args) {
for (inti = 0;
i & 5*_1K;i++) {
byte[]b =new
byte[_1K];
使用参数:-Xmx64M -Xms64M -XX:+PrintGCDetails
部分结果:
PSYoungGen total 19456K, used 6436K [0xfea80, 0x0000)
整个过程没有GC发生,一共分配的5MB数据都应该在堆中;
老年对象进入老年代
当对象的年龄达到一定的大小,自然会进入老年代。这种过程,被称为"晋升";
对象的年龄由该对象经历的GC次数决定。每经历一次GC,没被回收,该次数加1。
MaxTenuringThreshold:控制新生代对象的最大年龄;默认上限为15;也就是说,新生代对象最多经历15次GC,即可晋升到老年代;
注意:达到该条件,新生代对象必然晋升,但未达到该对象也有可能晋升!对象的晋升年龄是由虚拟机自行判断的!示例:
public class MaxTenuringThreshold {
public static final int _1M=;
public static final int _1K=1024;
public static void main(String[] args){
Map&Integer,byte[]& map = new HashMap&&();
for (int i = 0; i & 5 * _1K; i++) {
byte[] b = new byte[_1K];
map.put(i,b);
for (int k = 0; k & 17; k++) {
for (int i = 0; i & 270; i++) {
byte[] g = new byte[_1M];
该代码申请了大约5M空间,在第一个for循环中将byte数组进行保存,防止它们在GC时被回收。
后面的循环在新生代不停的分配内存,已触发新生代GC
运行参数:
-Xmx1024M -Xms1024M -XX:+PrintGCDetails -XX:MaxTenuringThreshold=15 -XX:+PrintHeapAtGC
第一次GC开始前,eden使用了99%,这也是触发新生分代GC的原因。该区不能容纳更多对象,后面又要产生新的对象,自然要对eden进行清理,清理的结果是将存活对象移入了from。from区占用了13%。第一次GC后eden被清空;
大对象进入老年代
除了年龄外,对象的体积也会影响对象的晋升;如果对象体积过大,新生代无论eden或者survivor区无法容纳这个对象,就会直接晋升到老年代,如下图:
PretenureSizeThreshold:用来设置对象直接晋升到老年代的阈值,单位是字节。只要对象大于指定值,就会绕过新生代,直接分配到老年代。该参数只对串行回收器和ParNew有效,对ParallelGC无效。默认为0
在TLAB上分配对象(Thread Local Allocation Buffer,线程本地分配缓存)
存在的意义:加速对象分配;由于对象一般会分配在堆上,而堆是全局共享的。所以存在多个线程在堆上申请空间。这些分配的对象都必须进行同步,会降低效率;所以Java使用了TLAB这种线程专属的区间来避免多线程冲突;
该区域占用eden空间;启用时,虚拟机会为每一个Java线程分配一块TLAB空间
示例:启用与关闭TLAB的性能对比
public class UseTLAB {
public static void alloc(){
byte[] b =new byte[2];
public static void main(String[] args) {
long b =System.currentTimeMillis();
for (int i = 0; i & ; i++) {
long e = System.currentTimeMillis();
System.out.println(e-b);
启用TLAB,运行参数为:-XX:+UseTLAB -Xcomp -XX:-BackgroundCompilation -XX:-DoEscapeAnalysis -server
禁用TLAB,运行参数为:-XX:-UseTLAB -Xcomp -XX:-BackgroundCompilation -XX:-DoEscapeAnalysis -server
观察TLAB的使用情况,打开跟踪参数 -XX:+PrintTLAB,运行参数为:
-XX:+UseTLAB -XX:+PrintTLAB -XX:+PrintGC -XX:TLABSize=102400 -XX:-ResizeTLAB -XX:TLABRefillWasteFraction=100 -XX:-DoEscapeAnalysis -server
跟踪TLAB的使用情况,结果如下图:
该日志分为两部分,首先是每一个线程的TLAB的使用情况,其次是以TLAB totals为首的整体TLAB的统计情况
desired_size:TLAB的大小;
slow allocs:从上次新生代GC到现在为止慢分配次数。慢分配是指由于TLAB空闲空间太小,不能满足较大对象的分配,而将该对象直接分配到堆上的次数;
refill waste:refill_waste的值;
alloc:表示当前的TLAB分配比例和使用评估量;
refills:表示该线程的TLAB空间被重新分配到填充的次数;
waste:表示空间的浪费比例;浪费的空间分为:gc,slow,fast;
gc:表示在当前新生代GC发生时,尚空闲的TLAB空间;
slow:当TLAB被废弃时没有被使用的TLAB空间;
fast:同slow作用,不同的是,fast表示这个refill操作是通过JIT编译优化的;
TLAB totals:显示了所有线程的使用情况;
thrds:显示了相关线程总数;
refills:表示所有线程refills的总数;
max:表示refills次数最多的线程的refills次数;
TLAB空间一般不大,大对象无法被分配到这里,而是直接分配到堆上。
当空间快要被装满时,虚拟机有两种选择:
废弃当前的TLAB,这样会浪费为被分配的TLAB空间;将大于TLAB剩余空间的对象直接分配到堆上;
虚拟机的选择:
虚拟机内部有一个refill_waste的值,当请求对象大于该值,会放入堆中,小于该值,会废弃当前TLAB,新建TLAB类分配新对象;TLABRefillWasteFraction:用来调整refill_waste,它表示TLAB中允许产生这种浪费的比例;默认为64;-XX:-ResizeTLAB:禁用ResizeTLAB;-XX:TLABSize手工指定一个TLAB的大小;
对象分配简要流程:
方法finalize()对垃圾回收的影响
该函数允许被子类重载,用于在对象被回收时进行资源的释放;尽量不要使用此函数,原因如下:
可能会导致对象复活;该函数的执行完全由GC线程决定,若不发生GC,则该函数没有机会执行;影响性能;
函数finalize()由FinalizerThread线程处理。每个即将被回收的并且包含finalize()的对象都会在回收前加入FinalizerThread的执行队列,该队列为java.lang.ref.ReferenceQueue引用队列,内部实现为链表结构。列队中每一项为java.lang.ref.Finalizer引用对象,它本质为一个引用。这和虚引用,弱引用如出一辙:
Finalizer内部封装了实际的回收对象,如下图:next,prev为实现链表所需,分别指向队列中的下一个元素和上一个元素,而referent字段则指向实际的对象引用
由于对象在回收前被Finalizer的referent字段进行"强引用",并加入了FinalizerThread的执行队列,这意味着对象又变为可达对象,因此阻止了对象的正常的回收。由于在引用队列中的元素,排列执行finalize()方法,一旦出现性能问题,将导致这些垃圾对象长时间堆积在内存中,导致OOM异常;
FinalizerThread的工作过程和FinalizerThread执行队列中Finalizer的引用关系
示例:finalize()的糟糕回收过程
public class LongFinalize {
public static class LF {
private byte[] content = new byte[512];
protected void finalize() throws Throwable {
System.out.println(Thread.currentThread().getId());
Thread.sleep(1000);
}catch(Exception e){
e.printStackTrace();
public static void main(String[] args) {
long b = System.currentTimeMillis();
for (int i = 0; i & 50000; i++) {
LF f = new LF();
long e =System.currentTimeMillis();
System.out.println(e-b);
运行参数:
-Xmx10m -Xms10m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath="d:/f.dump"
上述代码,使用一个sleep()方法模拟一个耗时操作,主函数则不断产生新的LF对象。结果发生了OOM错误并在D盘下得到了堆的Dump文件。说明:去掉LF类的finalize()方法,即注释本例代码中的重写方法,再次以相同的参数运行这段程序,程序会很快正常结束。注意:一个糟糕的finalize()可能会使对象长时间被Finalizer引用,而不得到释放,因此会进一步增加GC的压力
finalize()在特殊场合的应用
在MySql的JDBC驱动中,com.mysql.jdbc.ConnectionImpl就实现了finalize()方法,实现如下:
protected void finalize() throws Throwable{
cleanup(null);
super.finalize();
说明:当一个JDBC Connection被回收时,需要进行连接的关闭,即cleanup方法。在回收前,开发人员如果正常调用了Connection.close()方法,那么连接就会被显示关闭,cleanup()方法就什么都不做。如果开发人员没有关闭连接,而Connection对象又被回收了,则隐式进行连接的关闭,确保没有数据库连接的泄漏。
在这里,finalize()是一种补偿措施,在开发人员疏忽时,进行补救的一种方式。这种方式的调用时间依然不确定,不能单独作为可靠的资源回收手段;
生存还是死亡 —— Java虚拟机如何判断对象是否需要回收
Java的内存区域中,有哪些区域是垃圾收集器所关注的?怎么判断一个对象是不是需要回收?对象起死回生又是怎么回事?...
Java中对象的内存分配问题
Java对象内存分配
Java中对象都是分配在堆上吗?你错了!
我们在学习使用Java的过程中,一般认为new出来的对象都是被分配在堆上,但是这个结论不是那么的绝对,通过对Java对象分配的过程分析,可以知道有两个地方会导致Java中new出来的对象并不一定分别在...
JVM如何判断一个Java对象是否可以回收
程序小英雄
总所周知,Java将程序员从内存管理中解放出来,使得我们在编写代码的时候不用手动的分配和释放内存,内存管理的任务由JVM承担起来。本文就将讲解J...
Java虚拟机--对象回收
从这篇开始我们开始探讨一些jvm调优的问题。在jvm调优中一个离不开的重点是垃圾回收,当垃圾回收成为系统达到更高并发量的瓶颈时,我们就需要对jvm中如果进行“自动化”垃圾回收技术实施必要的监控和调节。...
程序计数器,虚拟机栈和本地方法栈
首先我们先来看下垃圾回收中不会管理到的内存区域,在Java虚拟机的运行时数据区我们可以看到,程序计数器,虚拟机栈,本地方法栈这三个地方是比较...
java虚拟机GC是这样回收对象的吗?
接下来的几篇博文就讨论如下问题:
1. 哪些内存需要回收(Java堆和方法去区)
2. 什么时候回收;
3. 如何回收。
对象已死吗?Java堆里面几乎存放了所有的对象,垃圾收集器...
虚拟机是如何判断一个对象是否需要回收
我们常说的垃圾回收,主要指的是Java堆和方法区的垃圾回收。一个接口的多个实现类需要的内存可能不一样,而编译期只知道对象的静态类型;一个方法中需要创...
java虚拟机垃圾回收机制:
java虚拟机的垃圾回收,顾名思义就是将不在需要的内存进行自动回收,已节省内存资源,那么问题就是哪些内存是需要回收的,java虚拟机又是怎么回收的呢?我们知道java内存...
没有更多推荐了,

我要回帖

更多关于 java数组在内存中如何分配 的文章

 

随机推荐