java程序内存主要分为了2个部分,包括 stack segment(栈内存区)、heap segment(堆内存区)。
在分析Java程序内存分配情况时,我们从下面这个经常被使用的例子开始吧。
下面程序将打印什么呢?
Java代码
String s1 = new String("abc");
String s2 = new String("abc");
System.out.print(s1.equals(s2)); // 1
System.out.print(s1 == s2); // 2
可能大家心中已经有答案了,第1条语句打印的是true,第二条语句打印的false,没错,正如大家所想,打印结果就是这样。那么,大家是否知道为什么第2条语句打印的是false呢?让我们来看看这个程序中的数据在内存中分配的情况吧。
首先,我们分析第一条语句String s1=new String("abc");
其实这条语句是下面两条语句的缩写,
String s1; //1
s1=new String(“abc”); //2
1. 在栈内存中定义一个名为s1的对String类的对像引用变量
2. 在堆内存开辟了一块空间用于存放字符串“abc”,将1定义的引用变量s1指向该空间。
同理,第二条语句String s2=new String("abc");也做了同样的事情,定义引用变量,指向新开辟出来的空间。说到这里大家可能心中已经有答案了,为什么s1!=s2呢?因为它们指向的是两块不同的堆内存空间。
其实,在java中new过程分配内存情况都和上面类似,先在栈内存中定义引用,然后在堆内存中开辟空间,存放数据,最后让引用指向该空间,如果一个引用没有指向任何空间,那么在使用这个引用变量时,程序将抛出著名的NullPointerException,其实从这个异常名字我们也能清晰知道产生该异常的原因,不是吗?
熟悉了上面这个例子,基本上大家对Java程序内存分配情况,已经入门了,让我们进入下一步吧。
我们定义下面的一个类:
Java代码
public class Cat {
private double weight;
private int age;
public Cat(double _weight, int _age) {
this.weight = _weight;
this.age = _age;
}
//get set 方法...
}
public class Cat {
private double weight;
private int age;
public Cat(double _weight, int _age) {
this.weight = _weight;
this.age = _age;
}
//get set 方法...
}
我们可能在其他程序中new出一只猫来,像这样:
Java代码
public class Demo{
public static void main(String[] args){
Cat kitty=new Cat(3.0,2);
}
}
public class Demo{
public static void main(String[] args){
Cat kitty=new Cat(3.0,2);
}
}
那么这个new过程是怎么进行的呢?
按照上面对String类型new的分析,大家可能想,这个简单,先在栈内存中定义引用变量kitty,然后在堆内存中分配一块空间,将kitty猫的属性存放进去,最后让引用变量指向这个空间,完了。
对,大家这样分析大体是正确的,但大家好像忽略了一个重要的细节,kitty猫的属性是怎样存进堆内存空间的呢?
我们来看看Cat这个类的构造函数吧,它定义了两个形参,也就是局部变量,在我们试图通过new向它传实参值时,在栈内存中定义了两个引用变量,_weight,_age并赋值,即两个引用变量所在栈内存中内容分别为3.0,2,同时在堆中开辟了一块新空间,空间中有两个内存区域,而内存中内容为系统初始化内容。然后通过构造函数将栈内存中值赋给堆内存中值,就这样堆内存中新空间就被赋值了,接下来就像大家所想的那样将引用kitty指向这块新空间。大家可能会想,那么栈中分配出来的那两块内存怎么处理呢?
其实,做完上面的事情后,局部变量将从栈中弹出,这个过程是自发的,它有别于JVM垃圾收集,JVM垃圾收集主要是用来释放堆内存空间,某块堆内存在没被引用,或引用它的变量再也不会被使用时将被回收。
噢,好长的分析啊,不知道大家看得怎么样了,我们继续吧。
假设在Demo类中有一个方法,像这样:
Java代码
public void change(Cat _cat){
_cat.setAge(5);
}
public void change(Cat _cat){
_cat.setAge(5);
}
那么把刚才new出来的 kitty传入该方法中,像这样:
Java代码
Demo demo=new Demo();
Cat kitty=new Cat(3.0,2);
demo.change(kitty);
System.out.print(kitty.getAge());
Demo demo=new Demo();
Cat kitty=new Cat(3.0,2);
demo.change(kitty);
System.out.print(kitty.getAge());
程序将打印kitty的age,那么该age会变化吗?
可能大家受到C语言中一个著名问题,也就是swap(交换)问题(见下面)的影响,得出了不会变的结论,也就是将打印2,但事实并非如此,该程序将打印5,那么到底发生了什么呢?让我们从程序内存变化情况,来解释这个现象吧。
首先,按前面我们的分析,new出kitty猫时,在栈内存中定义了一个引用,指向堆中分配的内存,堆内存存放kitty猫的信息。
然后,我们开始调用change方法了,在我们把kitty传给该方法时,在栈内存中就定义了局部变量_cat,该引用变量将被指向kitty所指向的堆内存区域,然后_cat对它指向的堆内存区域内容进行了更改,这不就是kitty所指堆内存区域被更改了吗?因为它们指向同一块堆内存。就这样,调用kitty.getAge()时,将得到5这样的结果。最后,分配出来的_cat将从栈中弹出。
分析到这里,我们再回头看,swap(交换)问题
我们都知道在C语言里有个方法像这样:
Java代码
void swap(int a,int b){
int temp=a;
int a=b;
int b=temp;
}
void swap(int a,int b){
int temp=a;
int a=b;
int b=temp;
}
然后在主函数中使用它,像这样:
Java代码
int a=2;
int b=5;
swap(a,b);
int a=2;
int b=5;
swap(a,b);
我们都知道这是无法交换a,b的值的,可是大家是否还记得解决方法呢,怎么解决这个问题呢?在C语言中我们用指针,像这样:
Java代码
void swap(int *a,int *a){
int temp=*a;
*a=*b;
*b=temp;
}
main(){
int a=2;
int b=5;
swap(&a,&b);
}
void swap(int *a,int *a){
int temp=*a;
*a=*b;
*b=temp;
}
main(){
int a=2;
int b=5;
swap(&a,&b);
}
看到这里,大家可能想起来了,这里使用了引用。
说到这里,大家是不是豁然开朗了呢,在Java栈内存中存放的就是引用啊,所以Java虽然没有显式的指针,但处处是指针。
好了,休息一下,我们再继续
随着一步步深入,大家可能对Java程序内存分配情况有了大致的了解,那么让我们一起来谈谈更高级的主题吧。
在文章开头我们使用String类例子,大家是不是觉得有点别扭呢?是啊,可能大家定义String都是像下面这样直接定义的,但这样有什么不同呢?
Java代码
String s1="abc";
String s2="abc";
System.out.print(s1.equals(s2)); // 1
System.out.print(s1==s2); // 2
显然,上面程序第1条打印语句,输出的是true,那么第二条呢?
如果大家运行该程序,第2条打印的也是true,为什么呢?大家是不是有点迷糊了呢,好,让我们来分析一下这个程序内存分配情况吧。
在这里我们先介绍一个高级概念:常量池
JVM虚拟机为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集和,包括直接常量(string,integer和 floating point常量)和对其他类型,字段和方法的符号引用。对于String常量,它的值是在常量池中的。而JVM中的常量池在内存当中是以表的形式存在的, 对于String类型,有一张固定长度的CONSTANT_String_info表用来存储文字字符串值,注意:该表只存储文字字符串值,不存储符号引用。说到这里,对常量池中的字符串值的存储位置应该有一个比较明了的理解了。
在介绍完JVM常量池的概念后,接着我们谈开始前面提到的“abc”的值的内存分布的位置。对“abc”的值,实际上是在class文件被JVM装载到内存当中就已经为“abc”这个字符串在常量池的CONSTANT_String_info表中分配了空间来存储“abc”这个值。既然“abc”这个字符串常量存储在常量池中,常量池是属于类型信息的一部分,类型信息也就是每一个被转载的类型,这个类型反映到JVM内存模型中是对应存在于JVM内存模型的方法区中,也就是这个类型信息中的常量池是存在于在方法区中,方法区可以在一个堆中自由分配。
这也就说明了为什么s1==s2,因为它们俩都是指向常量池中“abc”串的引用,而像文章开头里提到的new出来的String在新分配的堆内存中内容“abc”,只是常量池中“abc”串的拷贝。所以,请大家不要用new方法来初始化String类型,直接赋值就可以了。
好,先说到这里了,希望通过这篇文章大家对Java程序内存有所了解。
分享到:
相关推荐
本篇文章将详细探讨jProfiler7在Java内存分析上的核心功能、使用方法以及在Linux环境中的配置和应用。 1. **内存分析概述** - 内存分析是识别和解决Java应用程序中的内存泄漏、过度对象创建和内存消耗过高问题的...
Java内存分析是Java开发中非常重要的一个环节,它关乎到应用程序的性能和稳定性。"JAVA内存分析 - V1.0.0.zip" 提供了一套工具来帮助开发者深入理解并优化Java应用程序的内存使用情况。这个压缩包包含了两个关键文件...
java内存解析(讲的很清楚的视频)java内存解析(讲的很清楚的视频)java内存解析(讲的很清楚的视频)java内存解析(讲的很清楚的视频)java内存解析(讲的很清楚的视频)java内存解析(讲的很清楚的视频)java内存...
Java内存分析工具是Java开发过程中不可或缺的调试利器,主要用于检测和解决内存泄漏问题。内存泄漏是程序运行过程中,无法释放不再使用的内存空间,随着时间推移,会导致系统资源耗尽,性能急剧下降,甚至可能导致...
Java内存分析是一个重要的主题,尤其是在开发复杂且性能要求高的应用程序时。内存泄露是Java程序员需要密切关注的问题,因为它们可能导致系统性能下降,甚至可能导致系统崩溃。本文将深入探讨Java内存分析和内存泄露...
**Mac OS上的Java内存分析工具MAT** 在Java应用程序开发中,内存管理是至关重要的,特别是对于大型、长期运行的应用程序,内存泄漏可能导致性能下降甚至系统崩溃。为了有效地诊断和解决这些问题,开发人员需要借助...
Java内存分析工具ji Java内存分析工具ji是Java程序员不可或缺的工具之一,它可以帮助开发者快速地排查和解决Java内存泄漏问题。在本文中,我们将详细介绍Java内存泄漏的定义、原因、排查方法、解决方案,并提供一个...
Java内存分析是优化应用程序性能的关键环节,特别是在处理大型或资源密集型应用时。"dumpAnalyzer"是一个专门用于此目的的工具,它提供了图形化的界面,帮助开发者深入理解系统的内存状况,找出潜在的性能瓶颈。本文...
IBM HeapAnalyzer是一款强大的Java内存分析工具,主要用于诊断和解决Java应用程序中的内存泄漏问题。这款工具通过对Java堆内存的深入分析,帮助开发者定位那些占用过多内存的对象,从而优化应用性能。在Java开发过程...
Java内存分析工具是Java开发过程中不可或缺的利器,主要用于检测和解决程序中的内存泄漏问题,以及进行性能监控。本文将深入探讨几种常用的Java内存分析工具,它们的特点、使用方法以及如何利用这些工具有效地优化...
介绍了几种jdk自带的内存分析工具的简单使用,和一些java内置的命令
MAT 是一个开源的java内存分析工具,能够快速的分析dump文件,可以直观的看到各个对象在内存占用的量大小,以及类实例的数量,对象之间的引用关系,找出对象的GC Roots相关的信息,此外还能生成内存泄露报表,疑似...
Java内存分析是优化应用程序性能和解决内存泄漏问题的关键步骤,尤其在使用Eclipse IDE时,MAT(Memory Analyzer Tool)是一个非常强大的工具。MAT基于Eclipse平台,为Java开发者提供了深入的内存剖析功能,帮助我们...
Java内存分配全面解析 Java程序在执行过程中,其内存分配主要涉及到JVM(Java Virtual Machine,Java虚拟机)的不同区域。这些区域包括寄存器、栈、堆、常量池、代码段和数据段。理解这些内存区域的工作原理对于...
MAT(Memory Analyzer Tool)是Eclipse基金会开发的一款强大的Java内存分析工具,专为解决Java应用程序的内存泄漏和性能优化问题而设计。它提供了丰富的功能,包括堆dump分析、对象引用链追踪、内存消耗报告等,使得...
Java内存分析技术在教学中的应用 Java内存分析技术在教学中的应用是Java程序设计课程教学中的一件利器。本文介绍了Java内存分析技术及其在教学中的典型应用。Java程序运行在Java虚拟机(JVM)中,与其他一些语言...
Java内存泄漏分析是一个关键的系统优化任务,尤其是在大型企业级应用中,长期运行的系统可能会因为内存泄漏导致性能下降甚至服务中断。"JAVA内存泄漏分析工具"正是一款用于解决此类问题的专业工具,它能帮助开发者...
jProfiler7 java内存分析 里面包含jProfiler7安装版本,及key. 顺带有两个文档,一个en,一个cn. 还有几个html入门介绍及使用,包括案例怎么分析内存泄漏的整个分析解决过程。
Java内存分析是一个关键的优化步骤,特别是在开发大型的、高性能的应用程序时。MAT(Memory Analyzer Tool)是由IBM开发的一款强大的Java堆内存分析器,它的全名是Eclipse Memory Analyzer。这款工具是开源的,完全...