JVM内存管理及垃圾回收

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/OiteBody/article/details/79884542

JVM内存管理

根据JVM规范,JVM把内存划分了如下几个区域:

  • 方法区
  • 堆区
  • 本地方法栈
  • 虚拟机栈
  • 程序计数器 

其中,方法区和堆是所有线程共享的。


方法区

方法区存放了要加载的类的信息(如类名,修饰符)、类中的静态变量、final定义的常量、类中的field、方法信息,当开发人员调用类对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区。方法区是全局共享的,在一定条件下它也会被GC。当方法区使用的内存超过它允许的大小时,就会抛出OutOfMemory:PermGen Space异常。

在Hotspot虚拟机中,这块区域对应的是Permanent Generation(持久代),一般的,方法区上执行的垃圾收集是很少的,因此方法区又被称为持久代的原因之一,但这也不代表着在方法区上完全没有垃圾收集,其上的垃圾收集主要是针对常量池的内存回收和对已加载类的卸载。

运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存储编译期就生成的字面常量、符号引用、翻译出来的直接引用(符号引用就是编码是用字符串表示某个变量、接口的位置,直接引用就是根据符号引用翻译出来的地址,将在类链接阶段完成翻译);运行时常量池除了存储编译期常量外,也可以存储在运行时间产生的常量,比如String类的intern()方法,作用是String维护了一个常量池,如果调用的字符“abc”已经在常量池中,则返回池中的字符串地址,否则,新建一个常量加入池中,并返回地址。

JVM方法区的相关参数,最小值:--XX:PermSize;最大值 --XX:MaxPermSize

堆区 (Heap)

堆区是理解JavaGC机制最重要的区域。在JVM所管理的内存中,堆区是最大的一块,堆区也是JavaGC机制所管理的主要内存区域,堆区由所有线程共享,在虚拟机启动时创建。堆区用来存储对象实例及数组值,可以认为java中所有通过new创建的对象都在此分配。

对于堆区大小,可以通过参数-Xms-Xmx来控制,-Xms为JVM启动时申请的最新heap内存,默认为物理内存的1/64但小于1GB;-Xmx为JVM可申请的最大Heap内存,默认为物理内存的1/4但小于1GB,默认当剩余堆空间小于40%时,JVM会增大Heap到-Xmx大小,可通过-XX:MinHeapFreeRadio参数来控制这个比例;当空余堆内存大于70%时,JVM会减小Heap大小到-Xms指定大小,可通过-XX:MaxHeapFreeRatio来指定这个比例。对于系统而言,为了避免在运行期间频繁的调整Heap大小,我们通常将-Xms和-Xmx设置成一样。

为了让内存回收更加高效(后面会具体讲为何要分代划分),从Sun JDK 1.2开始对堆采用了分代管理方式,堆被划分为新生代和旧生代,新生代又被进一步划分为Eden和Survivor区,最后Survivor由FromSpace和ToSpace组成如下图所示: 


年轻代(Young Generation)

对象在被创建时,内存首先是在年轻代进行分配(注意,大对象可以直接在老年代分配)。当年轻代需要回收时会触发Minor GC(也称作Young GC)。

年轻代由Eden Space和两块相同大小的Survivor Space(又称S0和S1)构成,可通过-Xmn参数来调整新生代大小,也可通过-XX:SurvivorRadio来调整Eden Space和Survivor Space大小。不同的GC方式会按不同的方式来按此值划分Eden Space和Survivor Space,有些GC方式还会根据运行状况来动态调整Eden、S0、S1的大小。

年轻代的Eden区内存是连续的,所以其分配会非常快;同样Eden区的回收也非常快(因为大部分情况下Eden区对象存活时间非常短,而Eden区采用的复制回收算法,此算法在存活对象比例很少的情况下非常高效,后面会详细介绍)。

如果在执行垃圾回收之后,仍没有足够的内存分配,也不能再扩展,将会抛出OutOfMemoryError:Java Heap Space异常。

老年代(Old Generation)

老年代用于存放在年轻代中经多次垃圾回收仍然存活的对象,可以理解为比较老一点的对象,例如缓存对象;新建的对象也有可能在老年代上直接分配内存,这主要有两种情况:一种为大对象,可以通过启动参数设置-XX:PretenureSizeThreshold=1024,表示超过多大时就不在年轻代分配,而是直接在老年代分配。此参数在年轻代采用Parallel Scavenge GC时无效,因为其会根据运行情况自己决定什么对象直接在老年代上分配内存;另一种为大的数组对象,且数组对象中无引用外部对象。

当老年代满了的时候就需要对老年代进行垃圾回收,老年代的垃圾回收称作Major GC(也称作Full GC)。

老年代所占用的内存大小为-Xmx对应的值减去-Xmn对应的值。


虚拟机栈(JVM Stack)

虚拟机栈占用的是操作系统内存,每个线程都对应着一个虚拟机栈,它是线程私有的,而且分配非常高效。一个线程的每个方法在执行的同时,都会创建一个栈帧(Statck Frame),栈帧中存储的有局部变量表、操作站、动态链接、方法出口等,当方法被调用时,栈帧在JVM栈中入栈,当方法执行完成时,栈帧出栈。

局部变量表中存储着方法的相关局部变量,包括各种基本数据类型,对象的引用,返回地址等。在局部变量表中,只有long和double类型会占用2个局部变量空间(Slot,对于32位机器,一个Slot就是32个bit),其它都是1个Slot。需要注意的是,局部变量表是在编译时就已经确定好的,方法运行所需要分配的空间在栈帧中是完全确定的,在方法的生命周期内都不会改变。

虚拟机栈中定义了两种异常,如果线程调用的栈深度大于虚拟机允许的最大深度,则抛出StatckOverFlowError(栈溢出);不过多数Java虚拟机都允许动态扩展虚拟机栈的大小(有少部分是固定长度的),所以线程可以一直申请栈,直到内存不足,此时,会抛出OutOfMemoryError(内存溢出)。

本地方法栈(Native Method Stack)

本地方法栈用于支持native方法的执行,存储了每个native方法调用的状态。本地方法栈和虚拟机方法栈运行机制一致,它们唯一的区别就是,虚拟机栈是执行Java方法的,而本地方法栈是用来执行native方法的,在很多虚拟机中(如Sun的JDK默认的HotSpot虚拟机),会将本地方法栈与虚拟机栈放在一起使用。

程序计数器(Program Counter Register)

程序计数器是一个比较小的内存区域,可能是CPU寄存器或者操作系统内存,其主要用于指示当前线程所执行的字节码执行到了第几行,可以理解为是当前线程的行号指示器。字节码解释器在工作时,会通过改变这个计数器的值来取下一条语句指令。 每个程序计数器只用来记录一个线程的行号,所以它是线程私有(一个线程就有一个程序计数器)的。

如果程序执行的是一个Java方法,则计数器记录的是正在执行的虚拟机字节码指令地址;如果正在执行的是一个本地(native,由C语言编写完成)方法,则计数器的值为Undefined,由于程序计数器只是记录当前指令地址,所以不存在内存溢出的情况,因此,程序计数器也是所有JVM内存区域中唯一一个没有定义OutOfMemoryError的区域。

JVM内存分配

Java对象所占用的内存主要在堆上实现,因为堆是线程共享的,因此在堆上分配内存时需要进行加锁,这就导致了创建对象的开销比较大。当堆上空间不足时,会出发GC,如果GC后空间仍然不足,则会抛出OutOfMemory异常。

为了提升内存分配效率,在年轻代的Eden区HotSpot虚拟机使用了两种技术来加快内存分配 ,分别是bump-the-pointerTLAB(Thread-Local Allocation Buffers)。由于Eden区是连续的,因此bump-the-pointer技术的核心就是跟踪最后创建的一个对象,在对象创建时,只需要检查最后一个对象后面是否有足够的内存即可,从而大大加快内存分配速度;而对于TLAB技术是对于多线程而言的, 它会为每个新创建的线程在新生代的Eden Space上分配一块独立的空间,这块空间称为TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行情况计算而得。可通过-XX:TLABWasteTargetPercent来设置其可占用的Eden Space的百分比,默认是1%。在TLAB上分配内存不需要加锁,一般JVM会优先在TLAB上分配内存,如果对象过大或者TLAB空间已经用完,则仍然在堆上进行分配。因此,在编写程序时,多个小对象比大的对象分配起来效率更高。可在启动参数上增加-XX:+PrintTLAB来查看TLAB空间的使用情况。


对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次Minor GC后存活了下来),则会被复制到年老代,年老代的空间一般比年轻代大,能存放更多的对象,在年老代上发生的GC次数也比年轻代少。当年老代内存不足时,将执行Major GC,也叫 Full GC。

可以使用-XX:+UseAdaptiveSizePolicy开关来控制是否采用动态控制策略,如果动态控制,则动态调整Java堆中各个区域的大小以及进入老年代的年龄。

如果对象比较大(比如长字符串或大数组),年轻代空间不足,则大对象会直接分配到老年代上(大对象可能触发提前GC,应少用,更应避免使用短命的大对象)。用 -XX:PretenureSizeThreshold来控制直接升入老年代的对象大小,大于这个值的对象会直接分配在老年代上。

内存回收

在上面介绍的五个内存区域中,有3个是不需要进行垃圾回收的:本地方法栈、程序计数器、虚拟机栈。因为他们的生命周期是和线程同步的,随着线程的销毁,他们占用的内存会自动释放。所以,只有方法区和堆区需要进行垃圾回收,回收的对象就是那些不存在任何引用的对象。

分代回收

在一开始的时候,JVM的GC就是采用标记-清除-压缩方式进行的,这么做并不是很高效,因为当对象分配的越来越多时,对象列表也越来也大,扫描和移动越来越耗时,造成了内存回收越来越慢。然而,经过根据对java应用的分析,发现大部分对象的存活时间都非常短,只有少部分数据存活周期是比较长的,请看下面对java对象内存存活时间的统计:

这里写图片描述

从图表中可以看出,大部分对象存活时间是非常短的,随着时间的推移,被分配的对象越来越少。


查找算法

JVM主要采用两种查找算法来查找需要回收的内存,分别是引用计数算法和根搜索算法。

引用计数算法

经典的引用计数算法,每个对象添加到引用计数器,每被引用一次,计数器+1,失去引用,计数器-1,当计数器在一段时间内为0时,即认为该对象可以被回收了。但是这个算法有个明显的缺陷:当两个对象相互引用,但是二者都已经没有作用时,理应把它们都回收,但是由于它们相互引用,不符合垃圾回收的条件,所以就导致无法处理掉这一块内存区域。

在上图中,ObjectA释放了对ObjectB的引用后,ObjectB的引用计数器变为0,此时可回收ObjectB所占有的内存。

引用计数器需要在每次对象赋值时进行引用计数器的增减,他有一定消耗。另外,引用计数器对于循环引用的场景没有办法实现回收。例如在上面的例子中,如果ObjectB和ObjectC互相引用,那么即使ObjectA释放了对ObjectB和ObjectC的引用,也无法回收ObjectB、ObjectC,因此对于java这种会形成复杂引用关系的语言而言,引用计数器是非常不适合的,SunJDK在实现GC时也未采用这种方式。

根搜索算法

基本思想是:从一个叫GC Roots的根节点出发,向下搜索,如果一个对象不能达到GC Roots的时候,说明该对象不再被引用,可以被回收。如上图中的Object5、Object6、Object7,虽然它们三个依然相互引用,但是它们其实已经没有作用了,这样就解决了引用计数算法的缺陷。

基本思想是:从一个叫GC Roots的根节点出发,向下搜索,如果一个对象不能达到GC Roots的时候,说明该对象不再被引用,可以被回收。如上图中的Object5、Object6、Object7,虽然它们三个依然相互引用,但是它们其实已经没有作用了,这样就解决了引用计数算法的缺陷。

       补充概念,在JDK1.2之后引入了四个概念:强引用、软引用、弱引用、虚引用
       强引用:new出来的对象都是强引用,GC无论如何都不会回收,即使抛出OOM异常。
       软引用:只有当JVM内存不足时才会被回收。
       弱引用:只要GC,就会立马回收,不管内存是否充足。
       虚引用:可以忽略不计,JVM完全不会在乎虚引用,你可以理解为它是来凑数的,凑够"四大天王"。它唯一的作用就是做一些跟踪记录,辅助finalize函数的使用。

GC算法

主要有复制(Copying)标记-清除(Mark-Sweep)标记-压缩(Mark-Compact)三种实现算法。

复制(Copying)

复制采用的方式为从根集合扫描出存活的对象,并将找到的存活的对象复制到一块新的完全未被使用的空间中,如图所示:

这里写图片描述

复制收集器方式仅需要从根集合扫描所有存活对象,当要回收的空间中存活对象较少时,复制算法会比较高效(年轻代的Eden区就是采用这个算法),其带来的成本是要增加一块空的内存空间及进行对象的移动。

标记-清除(Marking-Deleting)

标记-清除采用的方式为从根集合开始扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未标记的对象,并进行清除,标记和清除过程如下图所示:

这里写图片描述

上图中蓝色的部分是有被引用的存活的对象,褐色部分没被引用的可回收的对象。在marking阶段为了mark对象,所有的对象都会被扫描一遍,扫描这个过程是比较耗时的。

这里写图片描述

清除阶段回收的是没有被引用的对象,存活的对象被保留。内存分配器会持有空闲空间的引用列表,当有分配请求时会查询空闲空间引用列表进行分配。

标记-清除动作不需要进行对象移动,且仅对其不存活的对象进行处理。在空间中存活对象较多的情况下较为高效,但由于标记-清除直接回收不存活对象占用的内存,因此会造成内存碎片。

标记-压缩(Mark-Compact)

标记-压缩和标记-清除一样,是对活的对象进行标记,但是在清除后的处理不一样,标记-压缩在清除对象占用的内存后,会把所有活的对象向左端空闲空间移动,然后再更新引用其对象的指针,如下图所示:

这里写图片描述

很明显,标记-压缩在标记-清除的基础上对存活的对象进行了移动规整动作,解决了内存碎片问题,得到更多连续的内存空间以提高分配效率,但由于需要对对象进行移动,因此成本也比较高。

垃圾收集器

在虚拟机中,GC是由垃圾回收器来具体执行的,所以,在实际应用场景中我们需要根据应用情况选择合适的垃圾收集器,下面我们就介绍一下垃圾收集器。

串行(Serial)收集器

串行收集器JavaSE5和6中客户端虚拟机所采用的默认配置,它是最简单的收集器,比较适合于只有一个处理器的系统。在串行收集器中,minor和major GC过程都是用一个线程进行垃圾回收。

使用场景

首先,串行GC一般用在对应用暂停要求不是很高和运行在客户端模式的场景,它仅仅利用一个CPU核心来进行垃圾回收。在现在的硬件条件下,串行GC可以管理很多小内存的应用,并且能够保证相对较小的暂停(在Full GC的情况下大约需要几秒的时间)。另一个通常采用串行GC的场景就是一台机器运行多个JVM虚拟机的情况(JVM虚拟机个数大于CPU核心数),在这种场景下,当一个JVM进行垃圾回收时只利用一个处理器,不会对其它JVM造成较大的影响。最后,在一些内存比较小和CPU核心数比较少的硬件设备中也比较适合采用串行收集器。

相关参数命令

1 启用串行收集器: -XX:+UseSerialGC

2 命令行示例:

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar xxx.jar

并行收集器

并行收集器采用多线程的方式来进行垃圾回收,采用并行的方式能够带来极大的CPU吞吐量。它在不进行垃圾回收的时候对正在运行的应用程序没有任何影响,在进程GC的时候采用多线程的方式来提高回收速度,因此,并行收集器非常适用于批处理的情形。当然,如果应用对程序暂停要求很高的话,建议采用下面介绍的并发收集器。默认一个N cpu的机器上,并行回收的线程数为N。当然,并行的数量可以通过参数进行控制: -XX:ParallelGCThreads=<desired number>。并行收集器是Server级别机器(CPU大于2且内存大于2G)上采用的默认回收方式,

应用场景

并行回收器适用于多CPU、对暂停时间要求短的情况下。通常,一些批处理的应用如报告打印、数据库查询可采用并行收集器。

在年轻代用多线程、老年代用单线程

1 启用命令:-XX:+UseParallelGC

2 命令行示例:

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseParallelGC -jar xxx.jar

年轻代和老年代都用多线程

1 启用命令:-XX:+UseParallelOldGC

当启用 -XX:+UseParallelOldGC 选项时,年轻代和老年代的垃圾收集都会用多线程进行,在压缩阶段也是多线程。因为HotSpot虚拟机在年轻代采用的是停止-复制算法,年轻代没有压缩过程,而老年代采用的是标记-清除-压缩算法,所以仅在老年代有compact过程。

2 命令行示例:

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseParallelOldGC -jar xxx.jar

CMS(Concurrent Mark Sweep)收集器

CMS收集器主要用于永久区,它试图用多线程并发的形式来减少垃圾收集过程中的暂停。CMS收集器不会对存活的对象进行复制或移动。

应用场景

CMS收集器主要用在应用程序对暂停时间要求很高的场景,比如桌面UI应用需要及时响应用户操作事件、服务器必须能快速响应客户端请求或者数据库要快速响应查询请求等等。

相关命令参数

1 启用CMS收集器:-XX:+UseConcMarkSweepGC

2 设置线程数:-XX:ParallelCMSThreads=<n>

3 命令行示例:

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseConcMarkSweepGC -XX:ParallelCMSThreads=2 -jar xxx.jar

G1收集器

G1即Garbage First,它是在java 7中出现的新的收集器,它的目标是替换掉现有的CMS收集器。G1具有并行、并发、增量压缩、暂停时间段等特点。

相关命令参数

1 启用G1收集器:-XX:+UseG1GC

2 命令行示例:

java -Xmx12m -Xms3m -XX:+UseG1GC -jar xxx.jar

参考:

展开阅读全文

关于垃圾回收(内存管理)的一点疑问

08-12

import javax.swing.*;rnimport java.awt.event.*;rnimport java.awt.FlowLayout;rnimport java.awt.Dimension;rnimport java.lang.String;rnimport javax.swing.JOptionPane;rnimport java.awt.Container;rnimport java.util.Hashtable;rnimport java.awt.BorderLayout;rnimport java.awt.Component;rnimport javax.swing.DefaultListCellRenderer;rnimport javax.swing.border.Border;rnimport javax.swing.border.EmptyBorder;rnclass ComboBoxRendererrn implements ListCellRenderer rnrn public ComboBoxRenderer() rn rnrn public Component getListCellRendererComponent(rn JList list,rn Object value,rn int index,rn boolean isSelected,rn boolean cellHasFocus) rn JLabel itemLabel = new JLabel(value);rn JPanel itemPanel = new JPanel();rn itemPanel.setToolTipText(sValue);rn itemPanel.setSize(list.getSize().width, 25);rn itemPanel.setLayout(new BorderLayout());rn itemPanel.add(itemLabel, BorderLayout.WEST);rn if (isSelected || cellHasFocus) rn itemPanel.setForeground(list.getSelectionForeground());rn itemPanel.setBackground(list.getSelectionBackground());rn rn else rn itemPanel.setForeground(list.getForeground());rn itemPanel.setBackground(list.getBackground());rn rn return itemPanel;rn rnrnpublic class test extends JFramern JComboBox select=null;rn JButton button=null;rn consuMem mem=null;rn String preItem=null;rn Hashtable sessionTable=new Hashtable();rn public test(String title) rn super(title);rn setSize(new Dimension(200,100));rn getContentPane().setLayout(new FlowLayout());rn addWindowListener(rn new WindowAdapter() rn public void windowClosing(WindowEvent e) rn System.exit(0);rn rnrn );rn select=new JComboBox();rn select.setRenderer(rn new ComboBoxRenderer(ComboBoxRenderer.COMBOTYPE_TEMPLATE));rn for (int i=0;i<200;i++) rn select.addItem(String.valueOf(i));rn rn select.addActionListener(new ActionListener() rn public void actionPerformed(ActionEvent e)rn rn rn );rn getContentPane().add(select);rnrn rn public static void main(String[] args) rn sessionWindow swindow=null;rn test test1=new test("MainWindow");rn test1.show();rn rnrn以上程序主要是自己实现一个ListCellRenderer类,调试,用键盘在combobox的item中反复上下切换,观察内存继续增长,怀疑是 JLabel itemLabel = new JLabel(value);rn JPanel itemPanel = new JPanel();rn这两句的问题,因为它们每次都会分配新的对象,于是将ComboBoxRenderer类改为如下形式:rnclass ComboBoxRendererrn implements ListCellRenderer rn public static JLabel itemLabel = new JLabel();rn public static JPanel itemPanel = new JPanel();rn private int type_;rnrn public ComboBoxRenderer(int type) rn rn rnrn public Component getListCellRendererComponent(rn JList list,rn Object value,rn int index,rn boolean isSelected,rn boolean cellHasFocus) rn if (value == null) rn return null;rn rn itemLabel.setText(value);rn itemPanel.setToolTipText(sValue);rn itemPanel.setSize(list.getSize().width, 25);rn itemPanel.setLayout(new BorderLayout());rn itemPanel.add(itemLabel, BorderLayout.WEST);rn if (isSelected || cellHasFocus) rn itemPanel.setForeground(list.getSelectionForeground());rn itemPanel.setBackground(list.getSelectionBackground());rn rn else rn itemPanel.setForeground(list.getForeground());rn itemPanel.setBackground(list.getBackground());rn rn return itemPanel;rn rnrn发现不会出现上面的现象了。但是我不大理解为什么会这样,按道理说,JVM每次getListCellRendererComponent()绘制完后,对它们的引用就会消失,内存也会被回收,为什么会出现上面的现象内,哪位大侠能够给解释一下,谢谢了 论坛

JVM内存管理总结【分享】

06-02

近期看了看Java内存泄露的一些案例,跟原来的几个哥们讨论了一下,深入研究发现JVM里面还是有不少以前不知道的细节,这里稍微剖析一下。先看一看JVM的内部结构——rn[img=http://p.blog.csdn.net/images/p_blog_csdn_net/Ant_Yan/EntryImages/20090520/a7413588-2618-3301-9eb4-e025e5afc2d8.png][/img]rnrn如图所示,JVM主要包括两个子系统和两个组件。两个子系统分别是Class loader子系统和Execution engine(执行引擎) 子系统;两个组件分别是Runtime data area (运行时数据区域)组件和Native interface(本地接口)组件。rn rnClass loader子系统的作用:根据给定的全限定名类名(如 java.lang.Object)来装载class文件的内容到 Runtime data area中的method area(方法区域)。Java程序员可以extends java.lang.ClassLoader类来写自己的Class loader。 rnrnExecution engine子系统的作用:执行classes中的指令。任何JVM specification实现(JDK)的核心都是Execution engine,不同的JDK例如Sun 的JDK 和IBM的JDK好坏主要就取决于他们各自实现的Execution engine的好坏。rnrnNative interface组件:与native libraries交互,是其它编程语言交互的接口。当调用native方法的时候,就进入了一个全新的并且不再受虚拟机限制的世界,所以也很容易出现JVM无法控制的native heap OutOfMemory。rnrnRuntime Data Area组件:这就是我们常说的JVM的内存了。它主要分为五个部分——rn1、Heap (堆):一个Java虚拟实例中只存在一个堆空间rn2、Method Area(方法区域):被装载的class的信息存储在Method area的内存中。当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件内容并把它传输到虚拟机中。rn3、Java Stack(java的栈):虚拟机只会直接对Java stack执行两种操作:以帧为单位的压栈或出栈rn4、Program Counter(程序计数器):每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。PC寄存器的内容总是指向下一条将被执行指令的饿地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。 rn5、Native method stack(本地方法栈):保存native方法进入区域的地址rnrn以上五部分只有Heap 和Method Area是被所有线程的共享使用的;而Java stack, Program counter 和Native method stack是以线程为粒度的,每个线程独自拥有自己的部分。rnrn了解JVM的系统结构,再来看看JVM内存回收问题了——rnSun的JVM Generational Collecting(垃圾回收)原理是这样的:把对象分为年青代(Young)、年老代(Tenured)、持久代(Perm),对不同生命周期的对象使用不同的算法。(基于对对象生命周期分析)rn[img=http://p.blog.csdn.net/images/p_blog_csdn_net/Ant_Yan/EntryImages/20090520/39bbb07d-fe97-3d9e-b55f-2f48a6d46395.png][/img]rnrn如上图所示,为Java堆中的各代分布。 rn1. Young(年轻代)rn年轻代分三个区。一个Eden区,两个Survivor区。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制年老区(Tenured。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。 rn2. Tenured(年老代)rn年老代存放从年轻代存活的对象。一般来说年老代存放的都是生命期较长的对象。 rn3. Perm(持久代)rn用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。rnrn举个例子:当在程序中生成对象时,正常对象会在年轻代中分配空间,如果是过大的对象也可能会直接在年老代生成(据观测在运行某程序时候每次会生成一个十兆的空间用收发消息,这部分内存就会直接在年老代分配)。年轻代在空间被分配完的时候就会发起内存回收,大部分内存会被回收,一部分幸存的内存会被拷贝至Survivor的from区,经过多次回收以后如果from区内存也分配完毕,就会也发生内存回收然后将剩余的对象拷贝至to区。等到to区也满的时候,就会再次发生内存回收然后把幸存的对象拷贝至年老区。rnrn通常我们说的JVM内存回收总是在指堆内存回收,确实只有堆中的内容是动态申请分配的,所以以上对象的年轻代和年老代都是指的JVM的Heap空间,而持久代则是之前提到的Method Area,不属于Heap。rnrn了解完这些之后,以下的转载一热衷于钻研技术的哥们Richen Wang关于内存管理的一些建议——rn1、手动将生成的无用对象,中间对象置为null,加快内存回收。rn2、对象池技术 如果生成的对象是可重用的对象,只是其中的属性不同时,可以考虑采用对象池来较少对象的生成。如果有空闲的对象就从对象池中取出使用,没有再生成新的对象,大大提高了对象的复用率。rn3、JVM调优 通过配置JVM的参数来提高垃圾回收的速度,如果在没有出现内存泄露且上面两种办法都不能保证内存的回收时,可以考虑采用JVM调优的方式来解决,不过一定要经过实体机的长期测试,因为不同的参数可能引起不同的效果。如-Xnoclassgc参数等。rnrn推荐的两款内存检测工具rn1、jconsole JDK自带的内存监测工具,路径jdk bin目录下jconsole.exe,双击可运行。连接方式有两种,第一种是本地方式如调试时运行的进程可以直接连,第二种是远程方式,可以连接以服务形式启动的进程。远程连接方式是:在目标进程的jvm启动参数中添加-Dcom.sun.management.jmxremote.port=1090 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false 1090是监听的端口号具体使用时要进行修改,然后使用IP加端口号连接即可。通过该工具可以监测到当时内存的大小,CPU的使用量以及类的加载,还提供了手动gc的功能。优点是效率高,速度快,在不影响进行运行的情况下监测产品的运行。缺点是无法看到类或者对象之类的具体信息。使用方式很简单点击几下就可以知道功能如何了,确实有不明白之处可以上网查询文档。rnrn2、JProfiler 收费的工具,但是到处都有破解办法。安装好以后按照配置调试的方式配置好一个本地的session即可运行。可以监测当时的内存、CPU、线程等,能具体的列出内存的占用情况,还可以就某个类进行分析。优点很多,缺点太影响速度,而且有的类可能无法被织入方法,例如我使用jprofiler时一直没有备份成功过,总会有一些类的错误。rnrnrnrnrn 论坛

没有更多推荐了,返回首页