出处: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. KVO VS isa : KVO 建立在 KVC 之上

    Key-Value Observing (KVO) 建立在 KVC 之上,它通过重写 KVC 和监听 setter 方法,向外发送通知. https://blog.csdn.net/y55091811 ...

  2. SpringBoot学习笔记(16)----SpringBoot整合Swagger2

    Swagger 是一个规范和完整的框架,用于生成,描述,调用和可视化RESTful风格的web服务 http://swagger.io Springfox的前身是swagger-springmvc,是 ...

  3. DNS BIND之dnssec安全介绍

    Domain Name System Security Extensions (DNSSEC)DNS安全扩展,是由IETF提供的一系列DNS安全认证的机制(可参考RFC2535).它提供了一种来源鉴定 ...

  4. kissy延迟加载demo

    <!doctype html><html><head>    <meta charset="gbk"/>    <title& ...

  5. linux查看系统cpu信息

    # 查看物理CPU个数 cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l # 查看每个物理CPU中core的个数(即 ...

  6. qume-kvm 命令管理

    sudo /etc/init.d/acpid start 安装管理包工具 sudo apt install libguestfs-tools [ qemu-kvm qemu-kvm-tools vir ...

  7. python学习笔记:第五天

    day05: 1.字符串格式化输出: 1.占位符:%s (字符串)    %d(整型)   %f (浮点型) 打印格式:print("字符串为%s" %s) 2.字符串:判断是否是 ...

  8. 《Craking the Coding interview》python实现---02

    ###题目:翻转一个字符串###思路:从字符串的最后一位开始,依次取###实现:伪代码.函数.类实现#伪代码: #01string=sNew_s=""for i in range( ...

  9. 紫书 习题 8-17 UVa 11536 (滑动窗口)

    这道题说连续子序列, 马上就想到滑动窗口. 注意窗口里面的元素中小于等于k的才是有效元素.记录窗口里面有效元素的个数, 满足了之后开始 缩短窗口, 如果左端点不是有效元素或者即使窗口中存在这个元素的个 ...

  10. windos环境python3.5安装 paramiko

    一.执行命令pip install paramiko,情况如下: C:\Users\ZFH>pip install paramikoCollecting paramiko  Downloadin ...