为您推荐:
精华内容
最热下载
问答
  • 5星
    63.04MB qq_41141585 2021-06-17 17:43:19
  • 5星
    2.25MB qq_40866897 2021-10-19 22:45:29
  • 5星
    5.25MB weixin_45709829 2021-07-12 15:32:52
  • 5星
    2.98MB qq_41496513 2020-12-08 11:31:33
  • 5星
    27KB qq_52889967 2021-07-08 19:52:37
  • 5星
    24KB helongqiang 2021-08-12 20:08:15
  • 5星
    2.15MB helongqiang 2021-06-14 22:53:15
  • 5星
    696KB weixin_40228600 2021-04-20 16:16:03
  • 5星
    99.2MB xiaohua1992 2020-12-23 22:01:39
  • 5星
    17.2MB xiaohua1992 2021-01-07 21:31:21
  • Java程序实际上是把内存控制的权力交给了Java虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存...Java内存管理分为两个方面:内存分配和垃圾回收,下面我们一一的来看一下。 Jvm定义了5个区
    Java程序实际上是把内存控制的权力交给了Java虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那排查错误将会成为一项异常艰难的工作。而且了解了Java的内存管理,有助于优化JVM,从而使得自己的应用获得最佳的性能体验。所以还等什么,赶紧跟着我来一起学习这方面的知识吧~
    Java内存管理分为两个方面:内存分配和垃圾回收,下面我们一一的来看一下。

    Jvm定义了5个区域用于存储运行时的数据,如下:


    第一、程序计数器(PC、Program Counter Register)
    程序计数器(Program Counter Register)是一块较小的内存空间,它可以看做当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来取下一条需要执行的字节码指令,分支、跳转、循环、异常处理、线程恢复等基础功能都需要这个计数器来完成。
    当线程正在执行的是一个Java方法,这个计数器记录的是在正在执行的虚拟机字节码指令的地址;当执行的是Native方法,这个计数器值为空。
    注:每条线程都会有一个独立的程序计数器,唯一不会出现OOM的。
    第二、Java栈/虚拟机栈(VM Stack)

    Java栈就是Java中的方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,这个栈帧用于存储局部变量表、操作数栈、指向当前方法所属的类的运行时常量池(运行时常量池的概念在方法区部分会谈到)的引用、方法返回地址等信息,每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

    局部变量表,顾名思义,想必不用解释大家应该明白它的作用了吧。就是用来存储方法中的局部变量(包括在方法中声明的非静态变量以及函数形参)。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。局部变量表的大小在编译器就可以确定其大小了,因此在程序执行期间局部变量表的大小是不会改变的。

    操作数栈,想必学过数据结构中的栈的朋友想必对表达式求值问题不会陌生,栈最典型的一个应用就是用来对表达式求值。想想一个线程执行方法的过程中,实际上就是不断执行语句的过程,而归根到底就是进行计算的过程。因此可以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。

    指向运行时常量池的引用,因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。

    方法返回地址,当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。

    异常可能性:对于栈有两种异常情况,如果线程请求的栈深度大于栈所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态拓展,在拓展的时无法申请到足够的内存,将会抛出OutOfMemoryError异常
    第三、本地方法栈(Native Method Stack)
    本地方法栈与Java栈所发挥的作用是非常相似的,它们之间的区别不过是Java栈执行Java方法,本地方法栈执行的是本地方法。有的虚拟机直接把本地方法栈和虚拟机栈合二为一。
    异常可能性:和Java栈一样,可能抛出StackOverflowError和OutOfMemeryError异常
    第四、Java堆(Heap)
    对于大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块,在虚拟机启动时创建。此内存区域的目的就是存放对象实例以及数组(当然,数组引用是存放在Java栈中的),几乎所有的对象实例都在这里分配内存,当然我们后面说到的垃圾回收器的内容的时候,其实Java堆就是垃圾回收器管理的主要区域。
    从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(TLAB)。Java堆可以处于物理上不连续的内存空间,只要逻辑上连续的即可。在实现上,既可以实现固定大小的,也可以是扩展的。
    异常可能性:如果堆中没有内存完成实例分配,并且堆也无法再拓展时,将会抛出OutOfMemeryError异常
    第五、方法区(Method Area)
    方法区它用于存储已被虚拟机加载的类信息(包括类的名称、方法信息、字段信息)、常量、静态变量、以及编译器编译后的代码等数据。
    1)运行时常量池
    运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载器进入方法区后的运行时异常常量池存放。它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法(将字符串的值放到常量池)。
    相对而言,垃圾收集行为在这个区域比较少出现,但并非数据进了方法区就永久的存在了,这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。 
    异常可能性:当方法区无法满足内存分配需求时,将抛出OutOfMemeryError异常
    直接内存
    直接内存不是虚拟机运行时数据区的一部分,在NIO类中引入一种基于通道与缓冲区的IO方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,或直接使用Unsafe.allocateMemory,但不推荐这种方式。
    直接内存的分配不会受到Java堆大小的限制,但是会受到本机内存大小的限制,所有也可能会抛OutOfMemoryError异常。
    说明:
    线程私有:程序计数器,Java栈,本地方法栈
    线程共享:Java堆,方法区

    垃圾回收(garbage collection,简称GC)可以自动清空堆中不再使用的对象,由于不需要手动释放内存,程序员在编程中也可以减少犯错的机会。利用垃圾回收,程序员可以避免一些指针和内存泄露相关的bug(这一类bug通常很隐蔽),但另一方面,垃圾回收需要耗费更多的计算时间,垃圾回收实际上是将原本属于程序员的责任转移给了计算机。

    在Java中,对象的是通过引用使用的,如果不再有引用指向对象,那么我们就再也无从调用或者处理该对象。这样的对象将不可到达(unreachable)。垃圾回收用于释放不可到达对象所占据的内存。这是垃圾回收的基本原则。

    Java中的引用类型有4种,由强到弱依次如下:
    1) 强引用(StrongReference)是使用最普遍的引用,类似:“Object obj = new Object()” 。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
    2) 软引用(Soft Reference)是用来描述一些有用但并不是必需的对象,如果内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
    3) 弱引用(WeakReference)也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
    4) 虚引用(PhantomReference)也称为幽灵引用或者幻影引用,它是最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。

    GC在进行回收时,需要通过算法检查是否回收Soft引用对象,而对于Weak引用对象,GC总是进行回收。Weak引用对象更容易、更快被GC回收。虽然GC在运行时一定回收Weak对象,但是复杂关系的Weak对象群常常需要好几次 GC的运行才能完成。Weak引用对象常常用于Map结构中,引用数据量较大的对象,一旦该对象的强引用为null时,GC能够快速地回收该对象空间。 

    Soft Reference的主要特点是据有较强的引用功能。只有当内存不够的时候,才进行回收这类内存,因此在内存足够的时候,它们通常不被回收。另外,这些引用对象还能保证在Java抛出OutOfMemory 异常之前,被设置为null。它可以用于实现一些常用图片的缓存,实现Cache的功能,保证最大限度的使用内存而不引起OutOfMemory。
    下面就是一个使用弱引用的例子:
        // 申请一个图像对象  
     Image image=new Image();       // 创建Image对象   
     // 使用 image  
     …  
     // 使用完了image,将它设置为soft 引用类型,并且释放强引用;  
     SoftReference sr=new SoftReference(image);  
     image=null;  
     …  
     // 下次使用时  
     if (sr!=null)   
    	image=sr.get();  
     else{  
    	image=new Image();  //由于GC由于低内存,已释放image,因此需要重新装载;  
    	sr=new SoftReference(image);  
     }  

    虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃 圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是 否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。 

    下面的例子是将虚引用关联到引用队列:

    ReferenceQueue refQueue = new ReferenceQueue(); //reference will be stored in this queue for cleanup
    DigitalCounter digit = new DigitalCounter();
    PhantomReference<DigitalCounter> phantom = new PhantomReference<DigitalCounter>(digit, refQueue);


    GC算法

    “标记-清除”(Mark-Sweep)算法,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。


    它的主要缺点是:
    1、效率问题,标记和清除过程的效率都不高;
    2、空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作;

    “复制”(Copying)算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。


    它的主要缺点是:
    1、只是这种算法是将内存缩小为原来的一半,有点过于浪费;
    2、对象存活率较高时就要执行较多的复制操作,效率将会变低;

    “标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,这样话连续的内存空间就比较多了。


    上面几种算法是通过分代回收(generational collection)混合在一起的,一般是把Java堆分为Young Generation(新生代),Old Generation(老年代)和Permanent Generation(持久代),这样就可以根据各个年代的特点采用最适当的回收算法。


    1) 在Young Generation中,有一个叫Eden Space的空间,主要是用来存放新生的对象,还有两个Survivor Spaces(from、to),它们的大小总是一样,它们用来存放每次垃圾回收后存活下来的对象。
    3) 在Young Generation块中,垃圾回收一般用Copying的算法,速度快。每次GC的时候,存活下来的对象首先由Eden拷贝到某个SurvivorSpace,当Survivor Space空间满了后,剩下的live对象就被直接拷贝到OldGeneration中去。因此,每次GC后,Eden内存块会被清空。
    4) 在Old Generation块中主要存放应用程序中生命周期长的内存对象,垃圾回收一般用mark-compact的算法,速度慢些,但减少内存要求。
    5)在Permanent Generation中,主要用来放JVM自己的反射对象,比如类对象和方法对象等。
    6) 垃圾回收分多级,0级为全部(Full)的垃圾回收,会回收Old段中的垃圾;1级或以上为部分垃圾回收,只会回收Young中的垃圾,内存溢出通常发生于Old段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。

    说明:
    from, to: 这两个区域大小相等,相当于copying算法中的两个区域,当新建对象无法放入eden区时,将出发minor collection。JVM采用copying算法,将eden区与from区的可到达对象复制到to区。经过一次垃圾回收,eden区和from区清空,to区中则紧密的存放着存活对象。随后from区成为新的to区, to区成为新的from区。如果进行minor collection的时候,发现to区放不下,则将部分对象放入成熟世代。另一方面,即使to区没有满,JVM依然会移动世代足够久远的对象到成熟世代。如果成熟世代放满对象,无法移入新的对象,那么将触发major collection(Full回收)。
    展开全文
    wdong_love_cl 2016-06-06 20:28:53
  • Java 内存管理(堆和栈)及 垃圾回收算法一.Jvm虚拟机内存简介1.1 Java运行时内存区1.2 线程私有的如下:1.3 线程共享的如下:二.Java 栈和堆2.1 堆栈的概念和特点2.2 栈与堆的异同2.3 举个例子另注:三.垃圾回收...


    一.Jvm虚拟机内存简介

    1.1 Java运行时内存区

    Java的运行时内存组成如下图所示:
    在这里插入图片描述

    1.2 线程私有的如下:

    • 程序计数器

      当前线程所执行的字节码的行号指示器

    • Java虚拟机栈

      Java方法执行的内存模型,每个方法被执行时都会创建一个栈帧,存储局部变量表、操作栈、动态链接、方法出口等信息。

      • 每个线程都有自己独立的栈空间
      • 线程栈只存基本类型和对象地址(重点)
      • 方法中局部变量在线程空间中(重点)
    • 本地方法栈

      Native方法服务。在HotSpot虚拟机中和Java虚拟机栈合二为一。

    1.3 线程共享的如下:

    • Java堆

      存放对象实例,几乎所有的对象实例以及其属性都在这里分配内存。(重点)

    • 方法区

      存储已经被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等数据。

    • 运行时常量池

      方法区的一部分。用于存放编译期生成的各种字面量和符号引用。

    在这里插入图片描述

    • 直接内存

      NIO、Native函数直接分配的堆外内存。DirectBuffer引用也会使用此部分内存。

    二.Java 栈和堆

    2.1 堆栈的概念和特点

    Java把内存划分成两种:一种是栈内存,一种是堆内存。

    栈内存中存储 基本类型的变量方法中的局部变量 以及 对象的引用变量

    堆内存用来存放 由new创建的对象和数组

    在这里插入图片描述
    在栈内存中保存的是堆内存空间的访问地址,或者说栈中的变量指向堆内存中的变量(Java中的指针)

    详细解释:
    在堆中产生了一个数组或对象后,在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。

    引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。

    当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。

    在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。


    2.2 栈与堆的异同

    栈与堆都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。

    Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,

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

    栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量(,int, short, long, byte, float, double, boolean, char)和对象句柄。

    2.3 举个例子

    栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:

    int a = 3;
    int b = 3
    1. 编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。
    2. 接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。
    3. 这样,就出现了a与b同时均指向3的情况。
    4. 这时,如果再令a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。
    5. 因此a值的改变不会影响到b的值。

    要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量

    String是一个特殊的包装类数据。可以使用如下两种形式才创建,放在堆中或常量池(方法区)中:

    String str = new String("abc");
    String str = "abc";
    
    • 第一种是用new()来新建对象的,这种语法是在堆中创建对象,并将str指向它,然后去字符串常量池中看看,是否有与之相同的内容的对象,如果有,则将new出来的字符串对象与字符串常量池中的对象联系起来,如果没有,则在字符串常量池中再创建一个包含该内容的字符串对象,并将堆内存中的对象与字符串常量池中新建出来的对象联系起来。
      这就是字符串的一次投入,终生回报的内存机制,对字符串的比较带来好处。
    • 第二种是先在栈中创建一个对String类的对象引用变量str,然后查找常量池中有没有存放"abc",如果没有,则将"abc"存放进常量池,并令str指向”abc”,如果已经有”abc” 则直接令str指向“abc”。
    • 要注意: 我们在使用诸如String str = “abc”;的格式定义类时,可能并没有创建新的String类的对象str,可能只是指向一个先前已经创建的对象,使用方法一 new() 可以保证每一次都是一个新的对象。
      String str1 = "aaa";
     //解析:str1指向方法区;
    
      String str2 = "bbb";
     //解析: str2 指向方法区
    
      String str3 = "aaabbb";
      //解析:str3指向方法区
    
      String str4 = str1 + str2;
      //解析:此行代码执行的底层执行过程是 首先使用StringBuffer的append方法将"str1"和"str2"拼接在一块,然后调用toString方法new出“str4”;所以此时的str4是创建在堆区的
    
      String str5 = "aaa" + "bbb";
      //解析:该行代码重点说明一下,jvm对其有优化处理,也就是在编译阶段就会将这两个字符串常量进行拼接,也就是"aaabbb";所以他是在方法区中的;’
    
      System.out.println(str3 == str4); // false or true
     //解析:很明显 为false, 一个指向堆 一个指向方法区
    
      System.out.println(str3 == str4.intern()); // true or false
     //解析:jdk1.6中str4.intern会把“aaabbb”放在方法区,1.7后在堆区,所以在1.6中会是true 但是在1.7中是false
    
      System.out.println(str3 == str5);// true or false
      //解析:都指向字符串常量区,字符串长常量区在方法区,相同的字符串只存在一份,其实这个地方在扩展一下,
      //因为方法区的字符串常量是共享的,在两个线程同时共享这个字符串时,如果一个线程改变他会是怎么样的呢,
      //其实这种场景下是线程安全的,jvm会将改变后的字符串常量在
      // 字符串常量池中重新创建一个处理,可以保证线程安全
    
    

    另注:

    • (1). 内存中有一个java基本类型封装类的常量池。这些类包括Byte, Short, Integer, Long, Character, Boolean。需要注意的是,Float和Double这两个类并没有对应的常量池。

    • (2).上面5种整型的包装类的对象是存在范围限定的;范围在-128~127存在在常量池,范围以外则在堆区进行分配。

    • (3). 在周志明的那本虚拟机中有这样一句话:包装类的“ = = ” 运行符在不遇到算术运算的情况下不会自动拆箱,以及他们的equals()方法不处理数据类型的关系,通俗的讲也就是 “ = = ”两边如果有算术运算, 那么自动拆箱和进行数据类型转换处理,比较的是数值等不等能。

    • (4).Long的equals方法会先判断是否是Long类型。

    • (5).无论是Integer还是Long,他们的equals方法比较的是数值。

        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
    
        System.out.println(c == d); //  true
        //解析:由于常量池的作用,c与d指向的是同一个对象(注意此时的==比较的是对象,也就是地址,而不是数值)。
        
        System.out.Println(e == f); // false
        //由于321超过了127,因此常量池失去了作用,所以e和f数值虽然相同,但不是同一个对象。
        
        System.out.println(c == (a + b)); // true
        //此时==两边有算术运算,会进行拆箱,因此此时比较的是数值,而并非对象。
        
        System.out.println(c.equals(a+b)); // true
        //c与a+b的数值相等。
        
        System.out.println(g == (a + b)); // true。
        //由于==两边有算术运算,所以比较的是数值。
        
        System.out.println(g.equals(a + b)); // false
        //Long类型的equal在比较是时候,会先判断a+b是否为Long类型,显然a+b不是.
    
    

    三.垃圾回收算法

    3.1 GC机制

    Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码。这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制。该机制对 JVM(Java Virtual Machine)中的内存进行标记,并确定哪些内存需要回收,根据一定的回收策略,自动的回收内存,永不停息(Nerver Stop)的保证JVM中的内存空间,防止出现内存泄露和溢出问题。

    GC回收的是无用对象,而对象创建后在jvm堆中,所以先来看jvm堆

    JVM堆(分代法)分为

    • (1) 新域:存储所有新成生的对象(使用 “停止-复制”算法 进行清理)
        新生代内存分为2部分:
         第一部分 Eden区,空间较大;
         第二部分部分Survivor区,空间比较小,并被划分为两个等量的部分。

    • (2) 旧域:新域中的对象,经过了一定次数的GC循环后,被移入旧域(算法是 标记-整理算法)

    • (3)永久域(方法区):存储类和方法对象,从配置的角度看,这个域是独立的,不包括在JVM堆内。默认为4M。

    方法区(永久域)的回收有两种:

    • 常量池中的常量
    • 无用的类信息

    常量的回收很简单,没有引用了就可以被回收。
    对于无用的类进行回收,必须保证3点:

    • 类的所有实例都已经被回收
    • 加载类的ClassLoader已经被回收
    • 类对象的Class对象没有被引用(即没有通过反射引用该类的地方)

    永久域的回收并不是必须的,可以通过参数来设置是否对类进行回收。

    3.2 GC 流程

    当eden满了,触发 young GC,young GC做2件事:

    • 一,去掉一部分没用的object;
    • 二,把老的还被引用的object发到survior里面,等下几次GC以后,survivor再放到old里面。

    当old满了,触发 full GC。full GC很消耗内存,把old,young里面大部分垃圾回收掉。这个时候用户线程都会被block。

    3.3 GC算法

    (1) Mark-Sweep 标记—清除算法

    遍历所有的GC Root,分别标记处可达的对象和不可达的对象,然后将不可达的对象回收。

    优点是:内存消耗较复制法小
    缺点是:效率低、回收得到的空间不连续

    设立若干根对象(GC Root),每个对象都是一个子节点,当一个对象找不到根时,就认为该对象不可达。

    在这里插入图片描述

    没有一条从根到Object4 和 Object5的路径,说明这两个对象到根是不可达的,可以被回收

    补充:java中,可以作为GC Roots的对象包括:

    • java虚拟机栈中引用的对象
    • 方法区中静态变量引用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中引用的对象

    (2) Copying Collector 复制算法

    将内存分为两块,每次只使用一块。当这一块内存满了,就将还存活的对象复制到另一块上,并且严格按照内存地址排列,然后把已使用的那块内存统一回收。

    优点是:能够得到连续的内存空间 效率高
    缺点是:浪费了一半内存

    (3) Mark-Sweep-Compact 标记—整理算法
    对于对象的清除,会产生一些内存碎片,这时候就需要对这些内存进行压缩、整理。包括:relocate(将存货的对象移动到一起,从而释放出连续的可用内存)、remap(收集所有的对象引用指向新的对象地址)。效率和存活对象的数量是线性相关的。

    3.4 常见小问题1: Java程序是否会出现内存泄露

    参考答案:会出现内存泄漏。

    一般来说内存泄漏有两种情况。一是在堆中分配的内存,在没有将其释放掉的时候,就将所有能访问这块内存的方式都删掉;另一种情况则是在内存对象明明已经不需要的时候,还仍然保留着这块内存和它的访问方式(引用)。第一种情况,在Java中已经由于垃圾回收机制的引入,得到了很好的解决。所以,Java中的内存泄漏,主要指的是第二种情况。

    下面给出了一个简单的内存泄露的例子。在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个List中,如果我们仅仅释放引用本身,那么List仍然引用该对象,所以这个对象对GC来说是不可回收的。代码如下所示:

        List list=new ArrayList(10);
        for (int i=1;i<100; i++)
        {
            Object o=new Object();
            list.add(o);
            o=null;    
        }
    

    此时,所有的Object对象都没有被释放,因为变量list引用这些对象。

    3.5 常见小问题2:JVM如何管理内存,分成几个部分?分别有什么用途?说出下面代码的内存实现原理:

    Foo foo = new Foo();
    foo.f();
    

    参考答案

    JVM内存分为“堆”、“栈”和“方法区”三个区域,分别用于存储不同的数据。
      堆内存用于存储使用new关键字所创建的对象;栈内存用于存储程序运行时在方法中声明的所有的局部变量,基本类型和对象地址;方法区用于存放类的信息,Java程序运行时,首先会通过类装载器载入类文件的字节码信息,经过解析后将其装入方法区。类的各种信息(包括方法)都在方法区存储。

    以上代码的内存实现原理为:(重点)
      1.Foo类首先被装载到JVM的方法区,其中包括类的信息,包括方法和构造等。
      2.在栈内存中分配引用变量foo。
      3.在堆内存中按照Foo类型信息分配实例变量内存空间;然后,将栈中引用foo指向foo对象堆内存的首地址。
      4.使用引用foo调用方法,根据foo引用的类型Foo调用f方法。


    文章参考以下博客:
    https://blog.csdn.net/u010429424/article/details/77333311#commentBox
    https://blog.csdn.net/wulianghuan/article/details/8599181
    https://www.imooc.com/article/68981
    https://www.cnblogs.com/lyf906522290/p/8350756.html

    展开全文
    qunqunstyle99 2019-06-28 20:00:58
  • Java内存管理-内存分配与回收

    一、Java内存分配

        Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域存储不同类型的数据,这些区域的内存分配和销毁的时间也不同,有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范(第2版)》的规定,Java虚拟机管理的内存包括五个运行时数据区域,如下图所示:

        

    1、方法区

        方法区(Method Area)是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息(包括类的名称、方法信息、成员变量信息)、常量、静态变量、以及编译器编译后的代码等数据。当方法区无法满足内存分配需求时,将抛出OutOfMemeryError异常。

        运行时常量池(Runtime Constant Pool)是方法区的一部分,此区域会在两种情况下存储数据。

        (1)class文件的常量池中的数据

        class文件中的常量池用于存放编译期生成的各种字面值和常量,这部分内容在类被加载后存放到方法区的运行时常量池中。

        字面值:private String name="zhangSan";private int age = 23+3;

        常量:private final String TAG = "MainActivity";private final int age = 26;

        (2)运行期间生成的常量

        运行时常量池相对于class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只能在编译期产生,也就是并非预置入class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。String str = "abc".intern();当运行时常量池中存在字符串"abc时,将该字符串的引用返回,赋值给str,否则创建字符串"abc",加入运行时常量池中,并返回引用赋值给str。既然运行时常量池是方法区的一部分,自然会受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。


    2、虚拟机栈

        虚拟机栈是线程私有的内存空间,每个线程都有一个线程栈,每个方法被执行时都会创建一个栈帧,方法执行完成,栈帧弹出,线程运行结束,线程栈被回收。虚拟机栈就是Java中的方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,这个栈帧用于存储局部变量表、操作数栈、指向当前方法所属的类的运行时常量池的引用、方法返回地址等信息,每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。局部变量表用来存储方法中的局部变量,包括方法中声明的变量以及函数形参。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。局部变量表的大小在编译器就可以确定其大小,并且在程序执行期间局部变量表的大小是不会改变的。程序中的所有计算过程都是在借助于操作数栈来完成的。指向运行时常量池的引用,因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向当前方法所属的类的运行时常量池。方法返回地址,当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。

        在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。


    3、本地方法栈

        本地方法栈也是线程私有的内存空间,本地方法栈与Java栈所发挥的作用是非常相似的,它们之间的区别不过是Java栈执行Java方法,本地方法栈执行的是本地方法,有的虚拟机直接把本地方法栈和虚拟机栈合二为一。


    4、堆

        Java堆是Java虚拟机所管理的内存中最大的一块,在虚拟机启动时创建,此内存区域的目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(TLAB)。Java堆可以处于物理上不连续的内存空间,只要逻辑上连续即可,在实现上,既可以实现固定大小的,也可以是扩展的。如果堆中没有足够的内存分配给实例,并且堆也无法再拓展时,将会抛出OutOfMemeryError异常。

        堆是运行时动态分配内存,对象在没有引用变量指向它的时候,才变成垃圾,但是仍然占着内存,在程序空闲的时候(没有工作线程运行,GC线程优先级最低)或者堆内存不足的时候(GC线程被触发),被垃圾回收器释放掉,由于要在运行时动态分配内存,存取速度较慢。


    5、程序计数器

        程序计数器的作用可以看做是当前线程所执行的字节码的行号指示。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为线程私有的内存。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie方法,这个计数器值则为空。


    二、Java内存回收

        对于虚拟机栈空间,当方法调用结束后,基本类型变量、引用类型变量、形参占据的空间会被自动释放,但引用类型指向的对象在堆中,堆中的无用内存由垃圾回收线程回收,GC线程优先级最低,只有当没有工作线程存在时GC线程才会执行,或者堆空间不足时会自动触发GC线程工作。除了回收内存,GC线程还负责整理堆中的碎片。

    1、四种引用类型

        Java中的对象引用分为四种,强引用类型、软引用类型、弱引用类型、虚引用类型。Java中提供这四种引用类型主要有两个目的:第一是可以让程序员通过代码的方式决定某些对象的生命周期;第二是有利于JVM进行垃圾回收。使用软引用和弱引用可以有效的避免oom。软引用关联的对象,只有软引用关联时,才可回收,如果有强引用同时关联,不会回收对象占用的内存,弱引用也如此。

    (1)强引用

        强引用是使用最普遍的引用,类似Object obj = new Object()、String str = "hello"。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

    (2)软引用(SoftReference)

        软引用是用来描述一些有用但并不是必需的对象,在Java中用java.lang.ref.SoftReference类来表示,如果内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用通常用于网页缓存、图片缓存,防止内存溢出,在内存充足的时候,缓存对象会一直存在,在内存不足的时候,缓存对象占用的内存会被垃圾收集器回收。使用示例:

    public void testSoftReference() {
        Map<String,SoftReference<Bitmap>> imagesCache = new HashMap<String,SoftReference<Bitmap>>();
        Bitmap bitmap = getBitmap();
        SoftReference<Bitmap> image1 = new SoftReference<Bitmap>(bitmap);
        imagesCache.put("image1",image1);
        SoftReference<Bitmap> result_SoftReference = imagesCache.get("image1");
        Bitmap result_Bitmap = result_SoftReference .get();
    
    }

    import java.lang.ref.SoftReference;
     
    public class Main {
        public static void main(String[] args) {
            SoftReference<String> sr = new SoftReference<String>(new String("hello"));
            System.out.println(sr.get());
        }
    }

    (3)弱引用(WeakReference)

        弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,在java中用java.lang.ref.WeakReference类来表示。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象,不过由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用可以用于:单例类持有一个activity引用时,会造成内存泄露,把activity声明为弱引用,在activity销毁后,垃圾收集器扫描到activity对象时,会回收activity对象的内存。使用示例:

    public class SingleTon1 {  
        private static final SingleTon1 mInstance = null;  
        private WeakReference<Context> mContext;
        private SingleTon1(WeakReference<Context> context) {  
      	mContext = context;
        }  
      
        public static SingleTon1 getInstance(WeakReference<Context> context) {  
            if (mInstance == null) {  
                synchronized (SingleTon1.class) {  
                    if (mInstance == null) {  
                        mInstance = new SingleTon1(context);  
                    }  
                }  
            }  
            return mInstance;  
        }  
    }  
    
    
    public class MyActivity extents Activity {
        public void onCreate (Bundle savedInstanceState){
           super.onCreate(savedInstanceState);
           setContentView(R.layout.main);
           SingleTon1 singleTon1 = SingleTon1.getInstance(new WeakReference<Context>(this));
       }
    
    }
    import java.lang.ref.WeakReference;
     
    public class Main {
        public static void main(String[] args) {
         
            WeakReference<String> sr = new WeakReference<String>(new String("hello"));
             
            System.out.println(sr.get());
            System.gc();                //通知JVM的gc进行垃圾回收
            System.out.println(sr.get());
        }
    }

    输出结果:

    hello
    null

        第二个输出结果是null,这说明只要JVM进行垃圾回收,被弱引用关联的对象必定会被回收掉。不过要注意的是,这里所说的被弱引用关联的对象是指只有弱引用与之关联,如果存在强引用同时与之关联,则进行垃圾回收时也不会回收该对象(软引用也是如此)。

    (4)虚引用

        虚引用和软引用、弱引用不同,它并不影响对象的生命周期,也无法通过虚引用来取得一个对象实例,在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。虚引用必须和引用队列(ReferenceQueue)联合使用,如下:

    import java.lang.ref.PhantomReference;
    import java.lang.ref.ReferenceQueue;
     
     
    public class Main {
        public static void main(String[] args) {
            ReferenceQueue<String> queue = new ReferenceQueue<String>();
            PhantomReference<String> pr = new PhantomReference<String>(new String("hello"), queue);
            System.out.println(pr.get());
        }
    }

    2、垃圾回收算法

    (1)标记-清除(Mark-Sweep)

       标记-清除(Mark-Sweep)算法,分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。


    标记-清除算法主要问题是:
    1、效率问题,标记和清除过程的效率很低
    2、空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集


    (2)复制(Copying)算法

        复制算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。


    复制算法的主要问题是:
    1、复制算法将内存缩小为原来的一半,过于浪费
    2、对象存活率较高时就要执行较多的复制操作,造成频繁GC,效率将会变低


    (3)标记-整理(Mark-Compact)

        标记-整理算法的标记过程仍然与标记-清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,这样连续的内存空间就比较多了。


        如上图所示,所有存活的对象依次向左上角移动,(0,4)移动到(0,2),(1,0)移动到(0,3),依次类推,当所有的存活对象移动完成后,把剩余的所有空间清空,也就是清空(1,1)后的所有空间。


    (4)分代回收(generational collection)

    程序创建的大部分对象的生命周期都很短,只有一小部分对象的生命周期比较长,根据这样的规律,一般把Java堆分为Young Generation(新生代),Old Generation(老年代)和Permanent Generation(持久代),上面几种算法是通过分代回收混合在一起的,这样就可以根据各个年代的特点采用最适当的回收算法。


    (1)新生代

        在新生代中,有一个叫Eden Space的空间,主要是用来存放新生的对象,还有两个Survivor Spaces(from、to), 这两个区域大小相等,相当于copying算法中的两个区域,它们用来存放每次垃圾回收后存活下来的对象。在新生代中,垃圾回收一般用Copying的算法,速度快。

        当新建对象无法放入eden区时,将触发minor collection(minorGC 是清理新生代的GC线程,eden的清理,from、to的清理都由MinorGC完成),将eden区与from区的存活对象复制到to区,经过一次垃圾回收,eden区和from区清空,to区中则紧密的存放着存活对象;当eden区再次满时,minor collection将eden区和to区的存活对象复制到from区,eden区和to区被清空,from区存放eden区和to区的存活对象,就这样from区和to区来回切换。如果进行minor collection的时候,发现to区放不下,则将eden区和from区的部分对象放入成熟代。另一方面,即使to区没有满,JVM依然会移动世代足够久远的对象到成熟代。

    (2)成熟代

        在成熟代中主要存放应用程序中生命周期长的内存对象,垃圾回收一般用mark-compact的算法,速度慢些,但减少内存要求。如果成熟代放满对象,无法从新生代移入新的对象,那么将触发major collection(major GC清理整合OldGen的内存空间)。

    (3)永久代

       在永久代中,主要用来放JVM自己的反射对象,比如类对象、方法对象、成员变量对象、构造方法对象等。

        此外,垃圾回收一般是在程序空闲的时候(没有工作线程,GC线程优先级较低)或者堆内存不足的时候自动触发,也可以调用System.gc()主动的通知Java虚拟机进行垃圾回收,但这只是个建议,Java虚拟机不一定马上执行,启动时机的选择由JVM决定,并且取决于堆内存中Eden区是否可用。

    展开全文
    qq_29078329 2017-12-29 17:48:00
  • 面试总结——Java内存管理与多线程1. 什么是线程?什么是进程?同一进程下的线程共享线程:程序在执行过程中,能够执行程序代码的一个执行单元,一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以...

    Java内存管理与多线程

    1. 什么是线程?什么是进程?同一进程下的线程共享

    线程:程序在执行过程中,能够执行程序代码的一个执行单元,一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。在Java语言中有4种状态:运行、就绪、挂起、结束。

    进程:指一段正在执行的程序。线程有时也被称为轻量级进程,它是程序执行的最小单元,一个进程可以拥有多个线程,各个线程之间共享程序的内存空间及一些进程级的资源,但是各个线程拥有自己的栈空间。

    进程的作用和定义:进程是为了提高CPU的执行效率,减少因为程序等待带来的CPU空转以及其他计算机软硬件资源的浪费而提出来的。进程是为了完成用户任务所需要的程序的一次执行过程和为其分配资源的一个基本单位,是一个具有独立功能的程序段对某个数据集的一次执行活动。

    线程和进程的区别:
    A. 地址空间和其它资源:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
    B. 通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
    C. 调度和切换:线程上下文切换比进程上下文切换要快得多。
    D. 在多线程OS中,进程不是一个可执行的实体。
    E. 线程是进程的一部分,所以线程有的时候被称为是轻权进程或者轻量级进程。

    线程与进程资源分配:
    线程共享的内容包括: 进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和、进程用户ID、进程组ID。
    线程独有的内容包括: 线程ID 、寄存器组的值 、线程的堆栈 、错误返回码 、线程的信号屏蔽码 。

    2. Java的内存机制

    Java 把内存划分成两种:一种是栈内存,另一种是堆内存。
    堆和栈相同点:
    栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
    堆和栈区别:
    栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

    在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java 会自动释放掉为该变量分配的内存空间,该内存空间可以立即被另作它用。

    堆内存用来存放由 new 创建的对象和数组,在堆中分配的内存,由 Java 虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或者对象之后,还可以在栈中定义一个特殊的变量,让栈中的这个变量的取值等于数组或对象在堆内存中的 首地址,栈中的这个变量就成了数组或对象的引用变量,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或者对象,引用变量就相当于是为数组或者对象 起的一个名称。引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用 new 产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍 然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。

    3. java中变量在内存中的分配

    (1)类变量(static修饰的变量):在程序加载时系统就为它在堆中开辟了内存,堆中的内存地址存放于栈以便于高速访问。静态变量的生命周期–一直持续到整个”系统”关闭

    (2)实例变量:当你使用java关键字new的时候,系统在堆中开辟并不一定是连续的空间分配给变量(比如说类实例),然后根据零散的堆内存地址,通过哈希算法换算为一长串数字以表征这个变量在堆中的”物理位置”。 实例变量的生命周期–当实例变量的引用丢失后,将被GC(垃圾回收器)列入可回收“名单”中,但并不是马上就释放堆中内存

    (3)局部变量:局部变量,由声明在某方法,或某代码段里(比如for循环),执行到它的时候在栈中开辟内存,当局部变量一但脱离作用域,内存立即释放

    4. JVM内存分配
    JVM 将内存区域划分为: Method Are(Non-Heap)(方法区),Heap(堆),Program Counter Register(程序计数器),VM Stack(虚拟机栈,也有翻译成JAVA 方法栈的),Native Method Stack(本地方法栈)。
    方法区和堆是线程共享的,虚拟机栈,程序计数器和本地方法栈是非线程共享的。
    一般性的 Java 程序的工作过程:一个 Java 源程序文件,会被编译为字节码文件(以 class 为扩展名),每个java程序都需要运行在自己的JVM上,然后告知 JVM 程序的运行入口,再被 JVM 通过字节码解释器加载运行。那么程序开始运行后,都是如何涉及到各内存区域的呢?
    概括地说来,JVM初始运行的时候都会分配好方法区和堆,而JVM每遇到一个线程,就为其分配一个程序计数器,虚拟机栈和本地方法栈,当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。这也是为什么我把内存区域分为线程共享和非线程共享的原因,非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说知发生在Heap上)的原因。

    5. Java实现多线程,创建并启动线程的过程

    (1)定义线程:
    1)扩展java.lang.Thread类。 2)实现java.lang.Runnable接口。

    (2)实例化线程:
    1)如果是扩展java.lang.Thread类的线程,则直接new即可。
    2)如果是实现了java.lang.Runnable接口的类,则用Thread的构造方法:
    Thread(Runnable target)
    Thread(Runnable target, String name)
    Thread(ThreadGroup group, Runnable target)
    Thread(ThreadGroup group, Runnable target, String name)
    Thread(ThreadGroup group, Runnable target, String name, long stackSize)

    (3)启动线程:在线程的Thread对象上调用start()方法,而不是run()或者别的方法。

    通过继承Thread类创建线程:
    public class ThreadDemo02 extends Thread{
    public void run(){
    System.out.println(“线程启动!”);
    }
    public static void main(String[] args) {
    ThreadDemo02 thread = new ThreadDemo02();
    thread.start();
    }
    }
    通过实现Runnable接口创建线程:
    public class ThreadDemo03 implements Runnable{
    public void run() {
    System.out.println(“线程启动02”);
    }
    public static void main(String[] args) {
    Thread thread01 = new Thread(new ThreadDemo03());
    thread01.start();
    }
    }
    start()方法和run()方法的区别:
    (1)启动一个线程是start()方法,启动线程之后start()方法会去调用run方法内容。
    (2)start是创建并启动一个线程,而run是要运行线程中的代码。
    (3)run()方法 : 在本线程内调用该Runnable对象的run()方法,可以重复多次调用;
    start()方法 : 启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程;

    7. 在JAVA中,有六个不同的地方可以存储数据

    (1) 寄存器(register):这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部。但是寄存器的数量极其有限,所以寄存器由编译器根据需求进行分配。你不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象。
    (2) 堆栈(stack):位于通用RAM中,但通过它的“堆栈指针”可以从处理器哪里获得支持。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时候,JAVA编译器必须知道存储在堆栈内所有数据的确切大小和生命周期,因为它必须生成相应的代码,以便上下移动堆栈指针。这一约束限制了程序的灵活性,所以虽然某些JAVA数据存储在堆栈中——特别是对象引用,但是JAVA对象不存储其中。
    (3)堆(heap):一种通用性的内存池(也存在于RAM中),用于存放所以的JAVA对象。堆不同于堆栈的好处是:编译器不需要知道要从堆里分配多少存储区域,也不必知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。当你需要创建一个对象的时候,只需要new写一行简单的代码,当执行这行代码时,会自动在堆里进行存储分配。当然,为这种灵活性必须要付出相应的代码。用堆进行存储分配比用堆栈进行存储存储需要更多的时间。
    (4)静态存储(static storage):这里的“静态”是指“在固定的位置”。静态存储里存放程序运行时一直存在的数据。你可用关键字static来标识一个对象的特定元素是静态的,但JAVA对象本身从来不会存放在静态存储空间里。
    (5)常量存储(constant storage):常量值通常直接存放在程序代码内部,这样做是安全的,因为它们永远不会被改变。有时,在嵌入式系统中,常量本身会和其他部分分割离开,所以在这种情况下,可以选择将其放在ROM中
    (6)非RAM存储:如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。

    8. java concurrent包下的4个类,选出差别最大的一个 (C)
    A. Semaphore B. ReentrantLock C. Future D. CountDownLatch
    别的类都处理线程间的关系,处理并发机制,但Future 只用于获取线程结果。
    Future是个接口,表示获取一个正在指定的线程的结果。对该线程有取消和判断是否执行完毕等操作。
    CountDownLatch 是个锁存器,他表示我要占用给定的多少个线程且我优先执行,我执行完之前其他要使用该资源的都要等待。
    Semaphore,就像是一个许可证发放者,也像一个数据库连接池。证就这么多,如果池中的证没换回来,其他人就不能用。
    ReentrantLock 和 synchronized一样,用于锁定线程。

    展开全文
    jianyuerensheng 2016-11-14 09:57:54
  • a382064640 2015-11-11 15:29:19
  • wl9739 2018-07-08 18:01:29
  • u010223904 2015-08-28 10:48:07
  • u010648555 2019-04-06 21:23:43
  • xubo_ob 2016-03-10 23:10:31
  • u010648555 2019-05-04 23:18:39
  • tjiyu 2016-12-29 00:36:20
  • Liveor_Die 2017-09-08 15:46:09
  • u010648555 2019-03-22 00:20:08
  • lynnlovemin 2017-09-19 08:45:21
  • qq_37769323 2020-02-09 22:41:31
  • eieiei438 2018-06-14 19:07:05
  • ghost_Programmer 2014-11-27 14:30:14
  • dreamrealised 2013-06-18 19:11:31
  • Evankaka 2015-03-26 20:21:54
  • baidu_34122324 2018-10-28 11:11:33
  • hongbo781202 2013-03-31 16:39:43
  • qq_34598667 2016-11-16 10:08:31
  • u012440687 2016-07-25 09:03:17
  • qq_34939489 2017-09-29 13:59:42

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 617,205
精华内容 246,882
关键字:

java内存管理

java 订阅
友情链接: chapter8_16.rar