出处:http://blog.csdn.net/qq_16143915/article/details/51195438

一、JAVA内存管理与GC机制

Java在JVM所虚拟出的内存环境中执行,java内存分为栈(stack)和堆(heap)两部分。



  1. 在Java中,JVM中的栈记录了线程的方法调用。每一个线程拥有一个栈。线程创建时创建栈。

    在某个线程的执行过程中。假设有新的方法调用,那么该线程相应的栈就会添加一个存储单元,即帧(frame)。

    在frame中。保存有该方法调用的參数、局部变量、暂时数据和返回地址。

    • 栈中仅仅保存基础数据类型的对象和自己定义对象的引用(不是对象),对象都存放在堆区中。
    • 栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。

    • 当被调用方法执行结束时,该方法相应的帧将被删除,參数和局部变量所占领的空间也随之释放。线程回到原方法,继续执行。当全部的栈都清空时,程序也随之执行结束。


2.

Java的普通对象存活在堆中。与栈不同,堆的空间不会随着方法调用结束而清空。因此,在某个方法中创建的对象,能够在方法调用结束之后。继续存在于堆中。

这带来的一个问题是,假设我们不断的创建新的对象。内存空间将终于消耗殆尽,JVM启动时创建堆。

  • 存储的全部是对象,每一个对象都包括一个与之相应的class的信息。(class的目的是得到操作指令)
  • jvm仅仅有一个堆区(heap)被全部线程共享,堆中不存放基本类型和对象引用。仅仅存放对象本身

3.方法区

方法区是系统分配的一个内存逻辑区域。是用来存储类型信息的(类型信息可理解为类的描写叙述信息)

  • 又叫静态区,跟堆一样,被全部的线程共享。方法区包括全部的class和static变量。

  • 方法区中包括的都是在整个程序中永远唯一的元素,如class,static变量。

4.执行时数据区过程

AppMain.java
public class AppMain //执行时, JVM把AppMain的信息都放入方法区
{
public static void main(String[] args) //main 方法本身放入方法区。 {
//test1是引用。所以放到栈区里, Sample是自己定义对象应该放到堆里面
Sample test1 = new Sample( " 測试1 " );
test1.printName();
}
}
Sample.java
public class Sample //执行时, jvm 把appmain的信息都放入方法区
{
private name; //new Sample实例后,name引用放入**栈**区里name对象放入堆里
public Sample(String name)
{
this .name = name;
}
public void printName() //print方法本身放入方法区里。
{
System.out.println(name);
}
}

系统收到了我们发出的指令,启动了一个Java虚拟机进程,这个进程首先从classpath中找到AppMain.class文件,读取这个文件里的二进制数据,然后把Appmain类的类信息存放到执行时数据区的方法区中。这一过程称为AppMain类的载入过程。

接着。Java虚拟机定位到方法区中AppMain类的Main()方法的字节码,開始执行它的指令。

执行main()方法第一条指令:

Sample test1=new Sample(“測试1”);

JVM执行该任务过程:

  • JVM在方法区查找Sample类信息,若没有Sample信息,则装载Sample类,然后把Sample类型信息放在方法区里。

  • 创建实例:首先在堆区为新的实例分配内存,这个Sample实例持有者指向方法区Sample类型信息的引用。这里所说的引用。实际上指的是Sample类的类型信息在方法区中的内存地址。而这个地址呢,就存放了在Sample实例的数据区里。
  • 位 于“=”前的test1是一个在main()方法中定义的变量,可见。它是一个局部变量,因此,它被会加入到了执行main()方法的主线程的JAVA方法调用栈中。而“=”将把这个test1变量指向堆区中的Sample实例,也就是说,它持有指向Sample实例的引用。


5.在Java语言里堆(heap)和栈(stack)里的差别

  • 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同。Java自己主动管理栈和堆,程序猿不能直接地设置栈或堆。
  • 栈的优势是。存取速度比堆要快。仅次于直接位于CPU中的寄存器。但缺点是。存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据能够共享。详见第6点。

    堆的优势是能够动态地分配内存大小,生存期也不必事先告诉编译器。Java的垃圾收集器会自己主动收走这些不再使用的数据。但缺点是。由于要在执行时动态分配内存,存取速度较慢。


6.Java中的数据类型有两种

  • 一种是基本类型(primitive types), 共同拥有8种,即int, short, long, byte, float, double, boolean, char(注意,并没有string的基本类型)。这样的类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的。称为自己主动变量。

    值得注意的是,自己主动变量存的是字面值,既不是类的实例,也不是类的引用,这里并没有类的存在。

    如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据。由于大小可知。生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。

    另外,栈有一个非常重要的特殊性,就是存在栈中的数据能够共享。

    假设我们同一时候定义

    int a = 3;

    int b = 3;

    译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用。然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3。在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同一时候均指向3的情况。

    特别注意的是,这样的字面值的引用与类对象的引用不同。假定两个类对象的引用同一时候指向一个对象。假设一个对象引用变量改动了这个对象的内部状态。那么还有一个对象引用变量也即刻反映出这个变化。相反,通过字面值的引用来改动其值,不会导致还有一个指向此字面值的引用的值也跟着改变的情况。如上例,我们定义完a与 b的值后,再令a=4;那么,b不会等于4,还是等于3。在编译器内部。遇到a=4;时,它就会又一次搜索栈中是否有4的字面值,假设没有,又一次开辟地址存放4的值;假设已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

  • String是一个特殊的包装类数据。即能够用String str = new String(“abc”);的形式来创建。也能够用String str = “abc”;的形式来创建(作为对照,在JDK 5.0之前,你从未见过Integer i = 3;的表达式。由于类与字面值是不能通用的,除了String。而在JDK 5.0中,这样的表达式是能够的!由于编译器在后台进行Integer i = new Integer(3)的转换)。前者是规范的类的创建过程。即在Java中。一切都是对象,而对象是类的实例,全部通过new()的形式来创建。

    Java 中的有些类,如DateFormat类,能够通过该类的getInstance()方法来返回一个新创建的类,似乎违反了此原则。

    事实上不然。

    该类运用了单例模式来返回类的实例,仅仅只是这个实例是在该类内部通过new()来创建的,而getInstance()向外部隐藏了此细节。

    那为什么在String str = “abc”;中,并没有通过new()来创建实例,是不是违反了上述原则?事实上没有。

  • 关于String str = “abc”的内部工作。

    Java内部将此语句转化为下面几个步骤:

      (1)先定义一个名为str的对String类的对象引用变量:String str;

      (2)在栈中查找有没有存放值为”abc”的地址。假设没有,则开辟一个存放字面值为”abc”的地址,接着创建一个新的String类的对象o,并将o 的字符串值指向这个地址。并且在栈中这个地址旁边记下这个引用的对象o。假设已经有了值为”abc”的地址,则查找对象o,并返回o的地址。

      (3)将str指向对象o的地址。

      值得注意的是,一般String类中字符串值都是直接存值的。但像String str = “abc”;这样的场合下。其字符串值却是保存了一个指向存在栈中数据的引用!

    为了更好地说明这个问题,我们能够通过下面的几个代码进行验证。

      String str1 = “abc”;

      String str2 = “abc”;

      System.out.println(str1==str2); //true

      注意。我们这里并不用str1.equals(str2);的方式,由于这将比較两个字符串的值是否相等。==号。依据JDK的说明,仅仅有在两个引用都指向了同一个对象时才返回真值。而我们在这里要看的是。str1与str2是否都指向了同一个对象。

      结果说明,JVM创建了两个引用str1和str2。但仅仅创建了一个对象,并且两个引用都指向了这个对象。

      我们再来更进一步,将以上代码改成:

      String str1 = “abc”;

      String str2 = “abc”;

      str1 = “bcd”;

      System.out.println(str1 + “,” + str2); //bcd, abc

      System.out.println(str1==str2); //false

      这就是说。赋值的变化导致了类对象引用的变化。str1指向了另外一个新对象!

    而str2仍旧指向原来的对象。上例中,当我们将str1的值改为”bcd”时,JVM发如今栈中没有存放该值的地址。便开辟了这个地址。并创建了一个新的对象,其字符串的值指向这个地址。

      事实上,String类被设计成为不可改变(immutable)的类。假设你要改变其值,能够,但JVM在执行时依据新值悄悄创建了一个新对象,然后将这个对象的地址返回给原来类的引用。

    这个创建过程虽说是全然自己主动进行的,但它毕竟占用了很多其它的时间。在对时间要求比較敏感的环境中,会带有一定的不良影响。

      再改动原来代码:

      String str1 = “abc”;

      String str2 = “abc”;

      str1 = “bcd”;

      String str3 = str1;

      System.out.println(str3); //bcd

      String str4 = “bcd”;

      System.out.println(str1 == str4); //true

      str3 这个对象的引用直接指向str1所指向的对象(注意。str3并没有创建新对象)。当str1改完其值后,再创建一个String的引用str4,并指向因str1改动值而创建的新的对象。

    能够发现,这回str4也没有创建新的对象,从而再次实现栈中数据的共享。

      我们再接着看下面的代码。

      String str1 = new String(“abc”);

      String str2 = “abc”;

      System.out.println(str1==str2); //false

      创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。

      String str1 = “abc”;

      String str2 = new String(“abc”);

      System.out.println(str1==str2); //false

      创建了两个引用。

    创建了两个对象。

    两个引用分别指向不同的两个对象。

      以上两段代码说明,仅仅要是用new()来新建对象的,都会在堆中创建。并且其字符串是单独存值的,即使与栈中的数据同样,也不会与栈中的数据共享。

  • 数据类型包装类的值不可改动。不仅仅是String类的值不可改动。全部的数据类型包装类都不能更改其内部的值。

  • 结论与建议:

      (1)我们在使用诸如String str = “abc”;的格式定义类时,总是想当然地觉得。我们创建了String类的对象str。

    操心陷阱!对象可能并没有被创建!唯一能够肯定的是,指向 String类的引用被创建了。至于这个引用究竟是否指向了一个新的对象,必须依据上下文来考虑。除非你通过new()方法来显要地创建一个新的对象。因此,更为准确的说法是,我们创建了一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为”abc”的String类。

    清醒地认识到这一点对排除程序中难以发现的bug是非常有帮助的。

      (2)使用String str = “abc”。的方式,能够在一定程度上提高程序的执行速度,由于JVM会自己主动依据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String(“abc”);的代码,则一概在堆中创建新对象。而无论其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。这个思想应该是享元模式的思想,但JDK的内部在这里实现是否应用了这个模式,不得而知。

      (3)当比較包装类里面的数值是否相等时,用equals()方法;当測试两个包装类的引用是否指向同一个对象时。用==。

      (4)由于String类的immutable性质,当String变量须要常常变换其值时。应该考虑使用StringBuffer类,以提高程序效率。

JVM学习心得的更多相关文章

  1. 深入JVM学习心得

    前言 相信很多人和我一样长期使用java编程,却很少关注过JVM底层实现,这很大程度上是因为JVM设计的很精巧,因此平时项目也很少遇到涉及JVM的问题.但是一方面出于对java底层技术的好奇,另一方面 ...

  2. JVM学习心得—JVM内存模型(个人整理,请勿转载)

    一.运行时数据区域 线程私有的:程序计数器+虚拟机栈+本地方法栈 线程共享的:堆+方法区(运行时常量池)+直接内存(非运行时数据区的一部分) *JDK1.8后将方法区废除,新增元空间. 1.1 程序计 ...

  3. effective java 学习心得

    目的 记录一下最主要学习心得,不然凭我这种辣鸡记忆力分分钟就忘记白看了... 用静态工厂方法代替构造器的最主要好处 1.不必每次都创建新的对象 Boolean.valueOf Long.valueOf ...

  4. 我的MYSQL学习心得(一) 简单语法

    我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(五) 运 ...

  5. 我的MYSQL学习心得(二) 数据类型宽度

    我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(五) 运 ...

  6. 我的MYSQL学习心得(三) 查看字段长度

    我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(五) 运 ...

  7. 我的MYSQL学习心得(四) 数据类型

    我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(五) 运 ...

  8. 我的MYSQL学习心得(五) 运算符

    我的MYSQL学习心得(五) 运算符 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据 ...

  9. 我的MYSQL学习心得(六) 函数

    我的MYSQL学习心得(六) 函数 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类 ...

随机推荐

  1. Caffe 激励层(Activation)分析

    Caffe_Activation 一般来说,激励层的输入输出尺寸一致,为非线性函数,完成非线性映射,从而能够拟合更为复杂的函数表达式激励层都派生于NeuronLayer: class XXXlayer ...

  2. java中class,public的用法

    java中class,public的用法 一.Java访问权限饰词(access specifiers) Java有public.protect.friendly.private四种访问权限,并且这四 ...

  3. JS 前端 将图片转换为Base64利用H5 FileReader新特性

      file = document.getElementById("image"); var file=file.files[0]; var fileName=file.name; ...

  4. tigergao

    互联网从业 6 年.前码农&DBA,现运维&电商创业者,也在做自媒体.终生学习者. 运营微信公众号:高哥咋么看 感兴趣的朋友们可以订阅.

  5. 15条JavaScript最佳实践【转】

    本文档整理大部分公认的.或者少有争议的JavaScript良好书写规范(Best Practice).一些显而易见的常识就不再论述(比如要用对象支持识别判断,而不是浏览器识别判断:比如不要嵌套太深). ...

  6. Python seed() 函数--每次产生一样的随机数系列

    import random random.seed( 10 ) print("Random number with seed 10 : ", random.random()) #0 ...

  7. Qt之QStackedLayout

    简述 QStackedLayout继承自QLayout. QStackedLayout类提供了多页面切换的布局,一次只能看到一个界面. QStackedLayout可用于创建类似于QTabWidget ...

  8. Redit集群搭建-Sentinel模式搭建

    Redit集群搭建 学习了: Windows:http://blog.csdn.net/mrxiagc/article/details/52799081 Linux:https://www.cnblo ...

  9. JS在页面限制checkbox最大复选数

    应该是挺简单的代码, 记录一下分享. 首先最直接的想法就是使用循环, 用局部变量记录已选的checkbox, 达到最大值就将余下的checkbox都禁止选择, 例如以下: <!DOCTYPE h ...

  10. 主程的晋升攻略(4):TCP、消息分包和协议设计

    在<主程的晋升攻略(3):IP.DNS和CDN>中,一次网络请求经过DNS解析知道了目的IP,如今就要发出网络包,这里我们说一说TCP的相关话题. TCP是一种流式协议 讲网络编程的教科书 ...