技术中心 :  罗伟峰   2009-10-22  JVM 内存管理和垃圾回收
纲  要 Java 内存管理 Java 内存模型  (memory model) Java 栈内存与堆内存 JVM 的 stack 和 heap 分配 Sun java GC  执行策略 分代收集 年轻代、老年代、持久代 Java OOM OOM(Java Heap)  错误提示: java.lang.OutOfMemoryError 。 OOM(Native Heap)  错误提示: requested XXXX bytes for ChunkPool::allocate. Out of swap space 。 Java 虚拟机 (JVM) 及其内存分配的设置 (Sun HotSpot JVM 5) Java 非内存资源的管理 Java  内存泄漏的总结
Java 内存管理 Java 内存模型  (memory model) 内存模型  (memory model) 内存模型描述的是程序中各变量(实例域、静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存取出变量这样的低层细节 . 不同平台间的处理器架构将直接影响内存模型的结构 . 内存模型的特征 :  a,   Ordering  有序性   b,   Visibility  可视性
Java 内存管理 JMM(java memory model) java 利用自身虚拟机的优势 ,  使内存模型不束缚于具体的处理器架构 ,  真正实现了跨平台 . Java Language Specification 说明 , jvm 系统中存在一个主内存 (Main Memory 或 Java Heap Memory) , Java 中所有变量都储存在主 内 存中,对于所有线程都是共享的。 每条线程都有自己的工作内存 (Working Memory) ,工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。 其中 , 工作内存里的变量 , 在多核处理器下 , 将大部分储存于处理器高速缓存中 , 高速缓存在不经过内存时 , 是不可见的
Java 内存管理
Java 内存管理 Java 内存模型  (memory model) 一般情况下的示例程序 :  x = 0; y = 0; i = 0; j = 0; // thread A y = 1; x = 1; // thread B i = x; j = y; 如上程序中 ,  如果线程 A,B 在无保障情况下运行 ,  那么 i,j 各会是什么值呢 ?
Java 内存管理 Java 内存模型  (memory model) 答案是 ,  不确定 . (0,1,10,11 等都有可能出现 ) 重新排序 当编译器不会改变程序的语义时,作为一种优化它可以随意地重新排序某些指令。 在某些情况下,可以允许处理器以颠倒的次序执行一些操作 , 通常允许缓存以与程序写入变量时所不相同的次序把变量存入主存。
Java 内存管理 Java 内存模型  (memory model) 缓存一致性( cache coherency ) 什么是缓存一致性 ?  它是一种管理多处理器系统的高速缓存区结构,可以保证数据在高速缓存区到内存的传输中不会丢失或重复。 举例 : 假如有一个处理器有一个更新了的变量值位于其缓存中,但还没有被写入主内存,这样别的处理器就可能会看不到这个更新的值 . 解决缓存一致性的两种方法 a,  顺序一致性模型 : 要求某处理器对所改变的变量值立即进行传播 ,  并确保该值被所有处理器接受后 ,  才能继续执行其他指令 . b,  释放一致性模型 :  允许处理器将改变的变量值延迟到释放锁时才进行传播 .
Java 内存管理 Java 内存模型  (memory model) jmm 缓存一致性模型 – “ happens-before ordering( 先行发生排序 )” happens-before ordering 操作模型 :
Java 内存管理 Java 内存模型  (memory model) jmm 缓存一致性模型 – “ happens-before ordering( 先行发生排序 )” (1)  获取对象监视器的锁 (lock) (2)  清空工作内存数据 ,  从主存复制变量到当前工作内存 ,  即同步数据  (read and load) (3)  执行代码,改变共享变量值  (use and assign) (4)  将工作内存数据刷回主存  (store and write) (5)  释放对象监视器的锁  (unlock) [ 备注 ]
Java 内存管理 Java 内存模型  (memory model) jmm 缓存一致性模型 – “ happens-before ordering( 先行发生排序 )” 实现  happends-before ordering 原则 , java 及 jdk 提供的工具 : a, synchronized  关键字 b, volatile  关键字 c, final 变量 d, java.util.concurrent.locks 包 (since jdk 1.5) e, java.util.concurrent.atmoic 包 (since jdk 1.5)
Java 内存管理 Java 内存模型  (memory model) Double-Checked Locking 失效问题  Class Foo {  Private Resource res = null; Public Resource getResource() {  If (res == null){  // 只有在第一次初始化时 , 才使用同步方式 .   synchronized(this) { if(res == null){ res = new Resource();  } } }   return res; } }
Java 内存管理 Java 内存模型  (memory model) Double-Checked Locking 失效问题   最重要的原因如下 : 1,  编译器优化了程序指令 ,  以加快 cpu 处理速度 . 2,  多核 cpu 动态调整指令顺序 ,  以加快并行运算能力 . 问题 : 1,  线程 A,  发现对象未实例化 ,  准备开始实例化 2,  由于编译器优化了程序指令 ,  允许对象在构造函数未调用完前 ,  将共享变量的引用指向部分构造的对象 ,  虽然对象未完全实例化 ,  但已经不为 null 了 . 3,  线程 B,  发现部分构造的对象已不是 null,  则直接返回了该对象 . DCL 失效问题的出现率还是比较低的 .
Java 内存管理 Java  栈内存与堆内存 Java 把内存划分成两种 一种是栈内存 一种是堆内存 Java 自动管理堆和栈
Java 内存管理 Java  栈内存与堆内存 栈内存 : 在函数中定义的一些基本类型的变量和对象的引用变量都在栈内存中分配 . 堆内存 : 用来存放由 new () 方法 创建的对象和数组。 在堆中分配的内存,由 Java 虚拟机的自动垃圾回收器来管理。
Java 内存管理 Java  栈内存与堆内存 栈 (stack) 优势 :  管理简单, 存取速度快,数据可以共享。 缺点 :  存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。 堆 (heap) 优势 :  可以动态地分配内存大小,生存期也不必事先告诉编译器,灵活 。 缺点 :  存取速度较慢。 [ 备注 ]
Java 内存管理 JVM 的 stack 和 heap 分配 stack (栈)是 JVM 的内存指令区。 Java  基本数据类型, Java  指令代码,常量都保存在 stack 中。 heap  (堆)是 JVM 的内存数据区。 对象实例包括其属性值作为数据,保存在 heap  中。 每一个对象 在 heap  中分配一定的内存来保存对象实例,实际上也只是保存对象实例的属性值,属性的类型标记和对象本身的类型标记等。而对象实例在 heap  中分配好以后,需要在 stack 中保存一个 4 字节的 heap  内存地址,用来定位该对象实例在 heap  中的位置,便于找到该对象实例。
Java 内存管理 JVM 的 stack 和 heap 分配 对象的方法和对象的属性分别保存在哪里 ? 1 )方法本身是指令的操作码部分,保存在 stack 中 2 )方法内部变量作为指令的操作数部分,跟在指令的操作码之后,保存在 stack 中(实际上是简单类型保存在 stack 中 ; 对象 , 引用 地址保存在 stack 中,在 heap  中保存值) 3 )对象实例包括其属性值作为数据,保存在数据区 heap  中。
Java 内存管理 JVM 的 stack 和 heap 分配 静态属性和非静态属性: 对象的静态属性是保存在 stack 中的(基本类型保存在 stack 中,对象引用保存在 stack 中,值保存在 heap 中)。因为静态属性都是在 stack 中,也因此不管什么指令(类的方法),都可以访问到类的静态属性 , 具有了全局属性 对象的非静态属性作为对象实例的一部分保存在 heap 中,而 heap 必须通过 stack 中的地址指针才能够被指令(类的方法)访问到。
Java 内存管理 JVM 的 stack 和 heap 分配 非静态方法和静态方法 : 非静态方法有一个隐含的传入参数,该参数是 JVM 给它的,这个隐含的参数就是对象实例在 stack 中的地址指针。因此非静态方法在调用前,必须先 new 一个对象实例,获得 stack 中的地址指针,否则 JVM 将无法将隐含参数传给非静态方法。 静态方法无此隐含参数,因此也不需要 new 对象,只要 class 文件被 ClassLoader load 进入 JVM 的 stack ,该静态方法即可被调用。 当然此时静态方法是存取不到 heap  中的对象属性的。 [ 备注 ]
Sun java GC  执行策略 分代垃圾收集 每一种垃圾收集的算法(引用计数、复制、标记 - 清除和标记 - 整理等)在特定条件下都有其优点和缺点。例如,当有很多对象成为垃圾时,复制可以做得很好,但是有许多长寿对象时它就变得很糟(要反复复制它们)。相反,标记 - 整理对于长寿对象可以做得很好(只复制一次),但是当有许多短 寿对象时就没有那么好了。 Sun JVM 1.2  及以后版本使用的技术称为  分代垃圾收集( generational garbage collection ) , 它结合了这两种技术以结合二者的长处。
Sun java GC  执行策略
Sun java GC  执行策略 年轻代、老年代、持久代 年轻代 生命周期很短的对象,归为年轻代。由于生命周期很短,这部分对象在 gc 的时候,很大部分的对象已经成为非活动对象。因此针对年轻代的对象,采用复制算法,只需要将少量的存活下来的对象 copy 到 to space 。存活的对象数量越少,那么复制算法的效率越高。  年轻代的 gc 称为 minor gc 经过数次复制,依旧存活的对象,将被移出年轻代,移到老年代。
Sun java GC  执行策略 年轻代、老年代、持久代 年轻代分为: eden :每当对象创建的时候,总是被分配在这个区域  survivor1 : copy 算法中的  from space  survivor2 : copy 算法中的  to sapce
Sun java GC  执行策略
Sun java GC  执行策略 年轻代、老年代、持久代 老年代:  生命周期较常的对象,归入到老年代。一般是经过多次 minor gc ,还依旧存活的对象,将移入到老年代。 老年代的 gc 称为 major gc ,就是通常说的 full gc 。  采用标记 - 整理算法。由于老年区域比较大,而且通常对象生命周期都比较长,标记 - 整理需要一定时间。所以这部分的 gc 时间比较长。  minor gc 可能引发 full gc 。当 eden + from space 的空间大于老年代的剩余空间时,会引发 full gc 。这是悲观算法,要确保 eden + from space 的对象如果都存活,必须有足够的老年代空间存放这些对象。
Sun java GC  执行策略 年轻代、老年代、持久代 持久代 : 该区域比较稳定,主要用于存放 classloader 信息,比如类信息和 method 信息
Java OOM 什么是 OOM Java 的对象生命周期管理都是 JVM 来做的,简化了开发人员的非业务逻辑的处理,但是这种自动管理回收机制也是基于一些规则的,而违背了这些规则的时候,就会造成所谓的 “ Memory Leak” 。
Java OOM OOM(Java Heap)  错误提示: java.lang.OutOfMemoryError 这类 OOM 是由于 JVM 分配的给应用的 Heap Memory 已经被耗尽 。 一种是应用在高负荷的情况下的却需要很大的内存,因此可以通过修改 JVM 参数来增加 Java Heap Memory 。 另一种情况是因为应用程序使用对象或者资源没有释放,导致内存消耗持续增加,最后出现 OOM ,这类问题引起的原因往往是应用已不需要的对象还被其他有效对象所引用,那么就无法释放 . 可能是业务代码逻辑造成的(异常处理不够例如 IO 等资源) 。 再一种也可能是对于第三方开源项目中资源释放了解不够导致使用以后资源没有释放(例如 JDBC 的 ResultSet 等)。
Java OOM OOM(Java Heap)  错误提示: java.lang.OutOfMemoryError 几个容易出现问题的场景: 1 .应用的缓存或者 Collection :如果应用要缓存 Java 对象或者是在一个 Collection 中保存对象,那么就要确定是否会有大量的对象存入,要做保护,以防止在大数据量下大量内存被消耗,同时要保证 Cache 的大小不会无限制增加。 2 .生命周期较长的对象:尽量简短对象的生命周期,现在采用对象的创建释放代价已经很低,同时作了很好的优化,要比创建一个对象长期反复使用要好。如果能够设置超时的情 况 下,尽量设置超时。
Java OOM OOM(Native Heap)  错误提示: requested XXXX bytes for ChunkPool::allocate. Out of swap space Native Heap Memory 是 JVM  内部使用的 Memory ,这部分的 Memory 可以通过 JDK 提供的 JNI 的方式去访问 。 JVM  使用 Native Heap Memory 用来优化代码载入( JTI 代码生成),临时对象空间申请,以及 JVM 内部的一些操作。
Java OOM OOM(Native Heap)  错误提示: requested XXXX bytes for ChunkPool::allocate. Out of swap space 。 同样这类 OOM 产生的问题也是分成正常使用耗尽和无释放资源耗尽两类。  无释放资源耗尽 , 要确定这类问题,就需要去观察 Native Heap Memory 的增长和使用情况,在服务器应用起来以后,运行一段时间后 JVM 对于 Native Heap Memory 的使用会达到一个稳定的阶段,此时可以看看什么操作对于 Native Heap Memory 操作频繁,而且使得 Native Heap Memory 增长 . 能够通过 JVM 启动时候增加 -verbose:jni 参数来观察对于 Native Heap Memory 的操作。 另一种情况就是正常消耗 Native Heap Memory ,对于 Native Heap Memory 的使用主要取决于 JVM 代码生成,线程创建,用于优化的临时代码和对象产生。当正常耗尽 Native Heap Memory 时,那么就需要增加 Native Heap Memory ,此时就会和我们前面提到增加 java Heap Memory 的情况出现矛盾。   [ 备注 ]
Java OOM Java 虚拟机 (JVM) 及其内存分配的设置 (Sun HotSpot JVM 5) 通常情况下是不建议在没有任何统计和分析的情况下去手动配置  JVM 的参数来调整性能,因为在 JVM 5 以上已经作了根据机器和 OS 的情况自动配置合适参数的算法,基本能够满足大部分的情况,当然这种自动适配只是一种通用的方式,如果说真的要达到最优,那么还是需要根据实际的使用情况来手动的配置各种参数设置,提高性能。 JVM 能够对性能产生影响的最大部分就是对于内存的管理。
Java OOM Java 虚拟机 (JVM) 及其内存分配的设置 (Sun HotSpot JVM 5) 关于 Heap 的几个参数设置: 在申请了 Java Heap 以后,剩下的可用资源就会被使用到 Native Heap 。 Xms: java heap 初始化时的大小。默认情况是机器物理内存的 1/64 。这个主要是根据应用启动时消耗的资源决定,分配少了申请起来会降低启动速度,分配多了也浪费。 Xmx:java heap 的 最大值,默认是机器物理内存的 1/4 ,最大也就到 1G 。这个值决定了最多可用的 Java Heap Memory ,分配过少就会在应用需要大量内存作缓存或者零时对象时出现 OOM 的问题,如果分配过大, 那么就会产生上文提到的第二类 OOM 。所以如何配置还是根据运行过程中的分析和计算来确定,如果不能确定还是采用默认的配置。 Xmn:java heap 新生代的空间大小。在 GC 模型中,根据对象的生命周期的长短,产生了内存分代的设计:年 轻 代(内部也分成三部分,类似于整体划分的作用,可以通过配置来设置比例),老年代,持久代。每一代的管理和回收策略都不相同,最为活跃的就是青年代,同时这部分的内存分配和管理效率也是最高。通常情况下,对于内存的申请优先在新生代中申请,当内存不够时会整理新生代,当整理以后还是不能满足申请的内存,就会向老年代移动一些生命周期较长的对象。  这种整理和移动会消耗资源,同时降低系统运行响应能力,因此如果青年代设置的过小,就会频繁的整理和移动,对性能造成影响。 那是否把年青代设置的越大越好,其实不然,年青代采用的是复制搜集算法,这种算法必须停止所有应用程序线程, 服务器线程切换时间就会成为应用响应的瓶颈(当然永远不用收集那么就不存在这个问题)。老年代采用的是串行标记收集的方式,并发收集可以减少对于应用的影响。 Xss: 线程堆栈最大值。允许更多的虚拟内存空间地址被 Java Heap 使用   [ 备注 ]
Java 非内存资源的管理 显式地释放资源 Java  程序中使用的绝大多数资源都是对象,垃圾收集在清理对象方面做得很好。因此,您可以使用任意多的 对象 , 垃圾收集器最终无需您的干预就会算出它们何时失效,并收回它们使用的内存。但是,像文件句柄和套接字句柄这类非内存资源必须由程序显式地释放,比如使用  close() 、 destroy() 、 shutdown()  或  release()  这样的方法来释放。
Java 非内存资源的管理 单资源的内存释放 不正确地获取、使用和释放资源  public static Properties loadProperties(String fileName) throws IOException {  FileInputStream stream = new FileInputStream(fileName);  Properties props = new Properties();   props.load(stream); stream.close();   return props;  }   这个例子存在潜在的资源泄漏。如果一切进展顺利,流将会在方法返回之前被关闭。但是如果  props.load()  方法抛出一个  IOException ,那么流则不会被关闭(直到垃圾收集器运行其终结器)。解决方案是使用  try...finally  机制来确保流被关闭,而不管是否发生错误  .
Java 非内存资源的管理 单资源的内存释放 正确地获取、使用和释放资源 public static Properties loadProperties(String fileName) throws IOException {    FileInputStream stream = new FileInputStream(fileName); try {  Properties props = new Properties();    props.load(stream);   return props;   } finally {   stream.close();   }   }  注意,资源获取(打开文件)是在  try  块外面进行的;如果把它放在  try  块中,资源获取抛出异常, finally  块也会运行。不仅该方法不适当(您无法释放您没有获取的资源),而且 finally  块中的代码也可能抛出其自己的异常,比如  NullPointerException 。从  finally  块抛出的异常取代导致块退出的异常,这意味着原来的异常丢失了,不能用于帮助进行调试。
Java 非内存资源的管理 多个资源的内存释放 错误的释放多个资源 public void enumerateFoo() throws SQLException {   Statement statement = null;  ResultSet resultSet = null;   Connection connection = getConnection();   try {  statement = connection.createStatement();   resultSet = statement.executeQuery("SELECT * FROM Foo"); // Use resultSet  } finally {   if (resultSet != null)  resultSet.close();   if (statement != null)   statement.close();  connection.close();   }   }   这个 “解决方案” 不成功的原因在于, ResultSet  和  Statement  的  close()  方法自己可以抛出  SQLException ,这会导致后面  finally  块中的  close()  语句不执行。要用一个  try..catch  块封装每一个  close() ,
Java 非内存资源的管理 多个资源的内存释放 可靠的释放多个资源的方法 public void enumerateBar() throws SQLException {   Statement statement = null;   ResultSet resultSet = null;   Connection connection = getConnection();  try {  statement = connection.createStatement();  resultSet = statement.executeQuery("SELECT * FROM Bar");   } finally {   try { if (resultSet != null)  resultSet.close(); }  finally {  try { if (statement != null) statement.close(); }   finally { connection.close(); }   }   }   }
Java 非内存资源的管理 不抛出检查异常使用  finally   释放资源 public class BoundedSet<T> { private final Set<T> set =  … private final Semaphore sem;  public BoundedHashSet(int bound) { sem = new Semaphore(bound);  } public boolean add(T o) throws InterruptedException { sem.acquire(); boolean wasAdded = false; try { wasAdded = set.add(o); return wasAdded;  } finally { if (!wasAdded) sem.release();  } } }
Java  内存泄漏的总结 ( 1 )尽早释放无用对象的引用 。 ( 2 )尽量 不使 用 finalize 函数。 ( 3 )应用缓存或者是在一个 Collection 中保存对象,那么就要确定是否会有大量的对象存入,要做保护,以防止在大数据量下大量内存被消耗,同时要保证 Cache 的大小不会无限制增加。 (弱引用和弱集合) ( 4 ) 生命周期较长的对象 , 尽量简短对象的生命周期。如果能够设置超时,尽量设置超时。
Java  内存泄漏的总结 ( 5 )尽量避免在类的默认构造器中创建,初始化大量的对象,防止在调用其类的构造器时造成不必要的内存浪费 。 ( 6 )尽量避免强制系统做垃圾内存的回收 ( 通过显示调用方法 System.gc()) 。 ( 7 )尽量避免显式申请数组空间,当不得不不显式地申请数组空间时尽量准确地估计出其合理值,以免造成不必要的系统内存开销。 ( 8 )尽量在合适的场景下使用对象池 化 技术以提高系统性能,缩减系统内存开销 。 但是要注意对象池的尺寸不易过大,及时清 除 无效对象释放内存资源。 ( 9 ) 现代 GC 执行的效率非常好。一般不会出现 OOM 的问题 !
JVM 内存管理和垃圾回收 总结回顾 : JAVA 内存模型 JVM 堆栈管理 JAVA GC  执行策略 JAVA OOM JVM 的内存参数配置管理 JAVA  非内存资源的管理
End Thank You!

JVM内容管理和垃圾回收

  • 1.
    技术中心 : 罗伟峰 2009-10-22 JVM 内存管理和垃圾回收
  • 2.
    纲 要Java 内存管理 Java 内存模型 (memory model) Java 栈内存与堆内存 JVM 的 stack 和 heap 分配 Sun java GC 执行策略 分代收集 年轻代、老年代、持久代 Java OOM OOM(Java Heap) 错误提示: java.lang.OutOfMemoryError 。 OOM(Native Heap) 错误提示: requested XXXX bytes for ChunkPool::allocate. Out of swap space 。 Java 虚拟机 (JVM) 及其内存分配的设置 (Sun HotSpot JVM 5) Java 非内存资源的管理 Java 内存泄漏的总结
  • 3.
    Java 内存管理 Java内存模型 (memory model) 内存模型 (memory model) 内存模型描述的是程序中各变量(实例域、静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存取出变量这样的低层细节 . 不同平台间的处理器架构将直接影响内存模型的结构 . 内存模型的特征 : a, Ordering 有序性 b, Visibility 可视性
  • 4.
    Java 内存管理 JMM(javamemory model) java 利用自身虚拟机的优势 , 使内存模型不束缚于具体的处理器架构 , 真正实现了跨平台 . Java Language Specification 说明 , jvm 系统中存在一个主内存 (Main Memory 或 Java Heap Memory) , Java 中所有变量都储存在主 内 存中,对于所有线程都是共享的。 每条线程都有自己的工作内存 (Working Memory) ,工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。 其中 , 工作内存里的变量 , 在多核处理器下 , 将大部分储存于处理器高速缓存中 , 高速缓存在不经过内存时 , 是不可见的
  • 5.
  • 6.
    Java 内存管理 Java内存模型 (memory model) 一般情况下的示例程序 : x = 0; y = 0; i = 0; j = 0; // thread A y = 1; x = 1; // thread B i = x; j = y; 如上程序中 , 如果线程 A,B 在无保障情况下运行 , 那么 i,j 各会是什么值呢 ?
  • 7.
    Java 内存管理 Java内存模型 (memory model) 答案是 , 不确定 . (0,1,10,11 等都有可能出现 ) 重新排序 当编译器不会改变程序的语义时,作为一种优化它可以随意地重新排序某些指令。 在某些情况下,可以允许处理器以颠倒的次序执行一些操作 , 通常允许缓存以与程序写入变量时所不相同的次序把变量存入主存。
  • 8.
    Java 内存管理 Java内存模型 (memory model) 缓存一致性( cache coherency ) 什么是缓存一致性 ?  它是一种管理多处理器系统的高速缓存区结构,可以保证数据在高速缓存区到内存的传输中不会丢失或重复。 举例 : 假如有一个处理器有一个更新了的变量值位于其缓存中,但还没有被写入主内存,这样别的处理器就可能会看不到这个更新的值 . 解决缓存一致性的两种方法 a, 顺序一致性模型 : 要求某处理器对所改变的变量值立即进行传播 , 并确保该值被所有处理器接受后 , 才能继续执行其他指令 . b, 释放一致性模型 : 允许处理器将改变的变量值延迟到释放锁时才进行传播 .
  • 9.
    Java 内存管理 Java内存模型 (memory model) jmm 缓存一致性模型 – “ happens-before ordering( 先行发生排序 )” happens-before ordering 操作模型 :
  • 10.
    Java 内存管理 Java内存模型 (memory model) jmm 缓存一致性模型 – “ happens-before ordering( 先行发生排序 )” (1) 获取对象监视器的锁 (lock) (2) 清空工作内存数据 , 从主存复制变量到当前工作内存 , 即同步数据 (read and load) (3) 执行代码,改变共享变量值 (use and assign) (4) 将工作内存数据刷回主存 (store and write) (5) 释放对象监视器的锁 (unlock) [ 备注 ]
  • 11.
    Java 内存管理 Java内存模型 (memory model) jmm 缓存一致性模型 – “ happens-before ordering( 先行发生排序 )” 实现 happends-before ordering 原则 , java 及 jdk 提供的工具 : a, synchronized 关键字 b, volatile 关键字 c, final 变量 d, java.util.concurrent.locks 包 (since jdk 1.5) e, java.util.concurrent.atmoic 包 (since jdk 1.5)
  • 12.
    Java 内存管理 Java内存模型 (memory model) Double-Checked Locking 失效问题  Class Foo { Private Resource res = null; Public Resource getResource() { If (res == null){ // 只有在第一次初始化时 , 才使用同步方式 . synchronized(this) { if(res == null){ res = new Resource(); } } } return res; } }
  • 13.
    Java 内存管理 Java内存模型 (memory model) Double-Checked Locking 失效问题   最重要的原因如下 : 1, 编译器优化了程序指令 , 以加快 cpu 处理速度 . 2, 多核 cpu 动态调整指令顺序 , 以加快并行运算能力 . 问题 : 1, 线程 A, 发现对象未实例化 , 准备开始实例化 2, 由于编译器优化了程序指令 , 允许对象在构造函数未调用完前 , 将共享变量的引用指向部分构造的对象 , 虽然对象未完全实例化 , 但已经不为 null 了 . 3, 线程 B, 发现部分构造的对象已不是 null, 则直接返回了该对象 . DCL 失效问题的出现率还是比较低的 .
  • 14.
    Java 内存管理 Java 栈内存与堆内存 Java 把内存划分成两种 一种是栈内存 一种是堆内存 Java 自动管理堆和栈
  • 15.
    Java 内存管理 Java 栈内存与堆内存 栈内存 : 在函数中定义的一些基本类型的变量和对象的引用变量都在栈内存中分配 . 堆内存 : 用来存放由 new () 方法 创建的对象和数组。 在堆中分配的内存,由 Java 虚拟机的自动垃圾回收器来管理。
  • 16.
    Java 内存管理 Java 栈内存与堆内存 栈 (stack) 优势 : 管理简单, 存取速度快,数据可以共享。 缺点 : 存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。 堆 (heap) 优势 : 可以动态地分配内存大小,生存期也不必事先告诉编译器,灵活 。 缺点 : 存取速度较慢。 [ 备注 ]
  • 17.
    Java 内存管理 JVM的 stack 和 heap 分配 stack (栈)是 JVM 的内存指令区。 Java 基本数据类型, Java 指令代码,常量都保存在 stack 中。 heap (堆)是 JVM 的内存数据区。 对象实例包括其属性值作为数据,保存在 heap 中。 每一个对象 在 heap 中分配一定的内存来保存对象实例,实际上也只是保存对象实例的属性值,属性的类型标记和对象本身的类型标记等。而对象实例在 heap 中分配好以后,需要在 stack 中保存一个 4 字节的 heap 内存地址,用来定位该对象实例在 heap 中的位置,便于找到该对象实例。
  • 18.
    Java 内存管理 JVM的 stack 和 heap 分配 对象的方法和对象的属性分别保存在哪里 ? 1 )方法本身是指令的操作码部分,保存在 stack 中 2 )方法内部变量作为指令的操作数部分,跟在指令的操作码之后,保存在 stack 中(实际上是简单类型保存在 stack 中 ; 对象 , 引用 地址保存在 stack 中,在 heap 中保存值) 3 )对象实例包括其属性值作为数据,保存在数据区 heap 中。
  • 19.
    Java 内存管理 JVM的 stack 和 heap 分配 静态属性和非静态属性: 对象的静态属性是保存在 stack 中的(基本类型保存在 stack 中,对象引用保存在 stack 中,值保存在 heap 中)。因为静态属性都是在 stack 中,也因此不管什么指令(类的方法),都可以访问到类的静态属性 , 具有了全局属性 对象的非静态属性作为对象实例的一部分保存在 heap 中,而 heap 必须通过 stack 中的地址指针才能够被指令(类的方法)访问到。
  • 20.
    Java 内存管理 JVM的 stack 和 heap 分配 非静态方法和静态方法 : 非静态方法有一个隐含的传入参数,该参数是 JVM 给它的,这个隐含的参数就是对象实例在 stack 中的地址指针。因此非静态方法在调用前,必须先 new 一个对象实例,获得 stack 中的地址指针,否则 JVM 将无法将隐含参数传给非静态方法。 静态方法无此隐含参数,因此也不需要 new 对象,只要 class 文件被 ClassLoader load 进入 JVM 的 stack ,该静态方法即可被调用。 当然此时静态方法是存取不到 heap 中的对象属性的。 [ 备注 ]
  • 21.
    Sun java GC 执行策略 分代垃圾收集 每一种垃圾收集的算法(引用计数、复制、标记 - 清除和标记 - 整理等)在特定条件下都有其优点和缺点。例如,当有很多对象成为垃圾时,复制可以做得很好,但是有许多长寿对象时它就变得很糟(要反复复制它们)。相反,标记 - 整理对于长寿对象可以做得很好(只复制一次),但是当有许多短 寿对象时就没有那么好了。 Sun JVM 1.2 及以后版本使用的技术称为 分代垃圾收集( generational garbage collection ) , 它结合了这两种技术以结合二者的长处。
  • 22.
    Sun java GC 执行策略
  • 23.
    Sun java GC 执行策略 年轻代、老年代、持久代 年轻代 生命周期很短的对象,归为年轻代。由于生命周期很短,这部分对象在 gc 的时候,很大部分的对象已经成为非活动对象。因此针对年轻代的对象,采用复制算法,只需要将少量的存活下来的对象 copy 到 to space 。存活的对象数量越少,那么复制算法的效率越高。 年轻代的 gc 称为 minor gc 经过数次复制,依旧存活的对象,将被移出年轻代,移到老年代。
  • 24.
    Sun java GC 执行策略 年轻代、老年代、持久代 年轻代分为: eden :每当对象创建的时候,总是被分配在这个区域 survivor1 : copy 算法中的 from space survivor2 : copy 算法中的 to sapce
  • 25.
    Sun java GC 执行策略
  • 26.
    Sun java GC 执行策略 年轻代、老年代、持久代 老年代: 生命周期较常的对象,归入到老年代。一般是经过多次 minor gc ,还依旧存活的对象,将移入到老年代。 老年代的 gc 称为 major gc ,就是通常说的 full gc 。 采用标记 - 整理算法。由于老年区域比较大,而且通常对象生命周期都比较长,标记 - 整理需要一定时间。所以这部分的 gc 时间比较长。 minor gc 可能引发 full gc 。当 eden + from space 的空间大于老年代的剩余空间时,会引发 full gc 。这是悲观算法,要确保 eden + from space 的对象如果都存活,必须有足够的老年代空间存放这些对象。
  • 27.
    Sun java GC 执行策略 年轻代、老年代、持久代 持久代 : 该区域比较稳定,主要用于存放 classloader 信息,比如类信息和 method 信息
  • 28.
    Java OOM 什么是OOM Java 的对象生命周期管理都是 JVM 来做的,简化了开发人员的非业务逻辑的处理,但是这种自动管理回收机制也是基于一些规则的,而违背了这些规则的时候,就会造成所谓的 “ Memory Leak” 。
  • 29.
    Java OOM OOM(JavaHeap) 错误提示: java.lang.OutOfMemoryError 这类 OOM 是由于 JVM 分配的给应用的 Heap Memory 已经被耗尽 。 一种是应用在高负荷的情况下的却需要很大的内存,因此可以通过修改 JVM 参数来增加 Java Heap Memory 。 另一种情况是因为应用程序使用对象或者资源没有释放,导致内存消耗持续增加,最后出现 OOM ,这类问题引起的原因往往是应用已不需要的对象还被其他有效对象所引用,那么就无法释放 . 可能是业务代码逻辑造成的(异常处理不够例如 IO 等资源) 。 再一种也可能是对于第三方开源项目中资源释放了解不够导致使用以后资源没有释放(例如 JDBC 的 ResultSet 等)。
  • 30.
    Java OOM OOM(JavaHeap) 错误提示: java.lang.OutOfMemoryError 几个容易出现问题的场景: 1 .应用的缓存或者 Collection :如果应用要缓存 Java 对象或者是在一个 Collection 中保存对象,那么就要确定是否会有大量的对象存入,要做保护,以防止在大数据量下大量内存被消耗,同时要保证 Cache 的大小不会无限制增加。 2 .生命周期较长的对象:尽量简短对象的生命周期,现在采用对象的创建释放代价已经很低,同时作了很好的优化,要比创建一个对象长期反复使用要好。如果能够设置超时的情 况 下,尽量设置超时。
  • 31.
    Java OOM OOM(NativeHeap) 错误提示: requested XXXX bytes for ChunkPool::allocate. Out of swap space Native Heap Memory 是 JVM 内部使用的 Memory ,这部分的 Memory 可以通过 JDK 提供的 JNI 的方式去访问 。 JVM 使用 Native Heap Memory 用来优化代码载入( JTI 代码生成),临时对象空间申请,以及 JVM 内部的一些操作。
  • 32.
    Java OOM OOM(NativeHeap) 错误提示: requested XXXX bytes for ChunkPool::allocate. Out of swap space 。 同样这类 OOM 产生的问题也是分成正常使用耗尽和无释放资源耗尽两类。 无释放资源耗尽 , 要确定这类问题,就需要去观察 Native Heap Memory 的增长和使用情况,在服务器应用起来以后,运行一段时间后 JVM 对于 Native Heap Memory 的使用会达到一个稳定的阶段,此时可以看看什么操作对于 Native Heap Memory 操作频繁,而且使得 Native Heap Memory 增长 . 能够通过 JVM 启动时候增加 -verbose:jni 参数来观察对于 Native Heap Memory 的操作。 另一种情况就是正常消耗 Native Heap Memory ,对于 Native Heap Memory 的使用主要取决于 JVM 代码生成,线程创建,用于优化的临时代码和对象产生。当正常耗尽 Native Heap Memory 时,那么就需要增加 Native Heap Memory ,此时就会和我们前面提到增加 java Heap Memory 的情况出现矛盾。 [ 备注 ]
  • 33.
    Java OOM Java虚拟机 (JVM) 及其内存分配的设置 (Sun HotSpot JVM 5) 通常情况下是不建议在没有任何统计和分析的情况下去手动配置 JVM 的参数来调整性能,因为在 JVM 5 以上已经作了根据机器和 OS 的情况自动配置合适参数的算法,基本能够满足大部分的情况,当然这种自动适配只是一种通用的方式,如果说真的要达到最优,那么还是需要根据实际的使用情况来手动的配置各种参数设置,提高性能。 JVM 能够对性能产生影响的最大部分就是对于内存的管理。
  • 34.
    Java OOM Java虚拟机 (JVM) 及其内存分配的设置 (Sun HotSpot JVM 5) 关于 Heap 的几个参数设置: 在申请了 Java Heap 以后,剩下的可用资源就会被使用到 Native Heap 。 Xms: java heap 初始化时的大小。默认情况是机器物理内存的 1/64 。这个主要是根据应用启动时消耗的资源决定,分配少了申请起来会降低启动速度,分配多了也浪费。 Xmx:java heap 的 最大值,默认是机器物理内存的 1/4 ,最大也就到 1G 。这个值决定了最多可用的 Java Heap Memory ,分配过少就会在应用需要大量内存作缓存或者零时对象时出现 OOM 的问题,如果分配过大, 那么就会产生上文提到的第二类 OOM 。所以如何配置还是根据运行过程中的分析和计算来确定,如果不能确定还是采用默认的配置。 Xmn:java heap 新生代的空间大小。在 GC 模型中,根据对象的生命周期的长短,产生了内存分代的设计:年 轻 代(内部也分成三部分,类似于整体划分的作用,可以通过配置来设置比例),老年代,持久代。每一代的管理和回收策略都不相同,最为活跃的就是青年代,同时这部分的内存分配和管理效率也是最高。通常情况下,对于内存的申请优先在新生代中申请,当内存不够时会整理新生代,当整理以后还是不能满足申请的内存,就会向老年代移动一些生命周期较长的对象。 这种整理和移动会消耗资源,同时降低系统运行响应能力,因此如果青年代设置的过小,就会频繁的整理和移动,对性能造成影响。 那是否把年青代设置的越大越好,其实不然,年青代采用的是复制搜集算法,这种算法必须停止所有应用程序线程, 服务器线程切换时间就会成为应用响应的瓶颈(当然永远不用收集那么就不存在这个问题)。老年代采用的是串行标记收集的方式,并发收集可以减少对于应用的影响。 Xss: 线程堆栈最大值。允许更多的虚拟内存空间地址被 Java Heap 使用 [ 备注 ]
  • 35.
    Java 非内存资源的管理 显式地释放资源Java 程序中使用的绝大多数资源都是对象,垃圾收集在清理对象方面做得很好。因此,您可以使用任意多的 对象 , 垃圾收集器最终无需您的干预就会算出它们何时失效,并收回它们使用的内存。但是,像文件句柄和套接字句柄这类非内存资源必须由程序显式地释放,比如使用 close() 、 destroy() 、 shutdown() 或 release() 这样的方法来释放。
  • 36.
    Java 非内存资源的管理 单资源的内存释放不正确地获取、使用和释放资源 public static Properties loadProperties(String fileName) throws IOException { FileInputStream stream = new FileInputStream(fileName); Properties props = new Properties(); props.load(stream); stream.close(); return props; } 这个例子存在潜在的资源泄漏。如果一切进展顺利,流将会在方法返回之前被关闭。但是如果 props.load() 方法抛出一个 IOException ,那么流则不会被关闭(直到垃圾收集器运行其终结器)。解决方案是使用 try...finally 机制来确保流被关闭,而不管是否发生错误 .
  • 37.
    Java 非内存资源的管理 单资源的内存释放正确地获取、使用和释放资源 public static Properties loadProperties(String fileName) throws IOException { FileInputStream stream = new FileInputStream(fileName); try { Properties props = new Properties(); props.load(stream); return props; } finally { stream.close(); } } 注意,资源获取(打开文件)是在 try 块外面进行的;如果把它放在 try 块中,资源获取抛出异常, finally 块也会运行。不仅该方法不适当(您无法释放您没有获取的资源),而且 finally 块中的代码也可能抛出其自己的异常,比如 NullPointerException 。从 finally 块抛出的异常取代导致块退出的异常,这意味着原来的异常丢失了,不能用于帮助进行调试。
  • 38.
    Java 非内存资源的管理 多个资源的内存释放错误的释放多个资源 public void enumerateFoo() throws SQLException { Statement statement = null; ResultSet resultSet = null; Connection connection = getConnection(); try { statement = connection.createStatement(); resultSet = statement.executeQuery(&quot;SELECT * FROM Foo&quot;); // Use resultSet } finally { if (resultSet != null) resultSet.close(); if (statement != null) statement.close(); connection.close(); } } 这个 “解决方案” 不成功的原因在于, ResultSet 和 Statement 的 close() 方法自己可以抛出 SQLException ,这会导致后面 finally 块中的 close() 语句不执行。要用一个 try..catch 块封装每一个 close() ,
  • 39.
    Java 非内存资源的管理 多个资源的内存释放可靠的释放多个资源的方法 public void enumerateBar() throws SQLException { Statement statement = null; ResultSet resultSet = null; Connection connection = getConnection(); try { statement = connection.createStatement(); resultSet = statement.executeQuery(&quot;SELECT * FROM Bar&quot;); } finally { try { if (resultSet != null) resultSet.close(); } finally { try { if (statement != null) statement.close(); } finally { connection.close(); } } } }
  • 40.
    Java 非内存资源的管理 不抛出检查异常使用 finally 释放资源 public class BoundedSet<T> { private final Set<T> set = … private final Semaphore sem; public BoundedHashSet(int bound) { sem = new Semaphore(bound); } public boolean add(T o) throws InterruptedException { sem.acquire(); boolean wasAdded = false; try { wasAdded = set.add(o); return wasAdded; } finally { if (!wasAdded) sem.release(); } } }
  • 41.
    Java 内存泄漏的总结( 1 )尽早释放无用对象的引用 。 ( 2 )尽量 不使 用 finalize 函数。 ( 3 )应用缓存或者是在一个 Collection 中保存对象,那么就要确定是否会有大量的对象存入,要做保护,以防止在大数据量下大量内存被消耗,同时要保证 Cache 的大小不会无限制增加。 (弱引用和弱集合) ( 4 ) 生命周期较长的对象 , 尽量简短对象的生命周期。如果能够设置超时,尽量设置超时。
  • 42.
    Java 内存泄漏的总结( 5 )尽量避免在类的默认构造器中创建,初始化大量的对象,防止在调用其类的构造器时造成不必要的内存浪费 。 ( 6 )尽量避免强制系统做垃圾内存的回收 ( 通过显示调用方法 System.gc()) 。 ( 7 )尽量避免显式申请数组空间,当不得不不显式地申请数组空间时尽量准确地估计出其合理值,以免造成不必要的系统内存开销。 ( 8 )尽量在合适的场景下使用对象池 化 技术以提高系统性能,缩减系统内存开销 。 但是要注意对象池的尺寸不易过大,及时清 除 无效对象释放内存资源。 ( 9 ) 现代 GC 执行的效率非常好。一般不会出现 OOM 的问题 !
  • 43.
    JVM 内存管理和垃圾回收 总结回顾: JAVA 内存模型 JVM 堆栈管理 JAVA GC 执行策略 JAVA OOM JVM 的内存参数配置管理 JAVA 非内存资源的管理
  • 44.

Editor's Notes

  • #8 “ 重新排序”这个术语用于描述几种对内存操作的真实明显的重新排序的类型:
  • #11 jmm 怎么体现 可视性 (Visibility) ?  在 jmm 中 , 通过并发线程修改变量值 , 必须将线程变量同步回主存后 , 其他线程才能访问到 . jmm 怎么体现 有序性 (Ordering) ?  通过 java 提供的同步机制或 volatile 关键字 , 来保证内存的访问顺序 . 注意 : 其中 4,5 两步是同时进行的 . 这边最核心的就是第二步 , 他同步了主内存 , 即前一个线程对变量改动的结果 , 可以被当前线程获知 !( 利用了 happens-before ordering 原则 ) 对比之前的例子 如果多个线程同时执行一段未经锁保护的代码段,很有可能某条线程已经改动了变量的值,但是其他线程却无法看到这个改动,依然在旧的变量值上进行运算,最终导致不可预料的运算结果。
  • #17 栈有一个很重要的特殊性,就是存在栈中的数据可以共享。 假设我们同时定义: 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=4 ;那么编译器会重新搜索栈中是否有 4 值,如果没有,则将 4 存放进来,并令 a 指向 4 ; 如果已经有了,则直接将 a 指向这个地址。因此 a 值的改变不会影响到 b 的值。 要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况 a 的修改并不会影响到 b, 它是由编译器完成的,它有利于节省空间。 而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。 关于 String str = &amp;quot;abc&amp;quot; 的内部工作。 Java 内部将此语句转化为以下几个步骤: (1) 先定义一个名为 str 的对 String 类的对象引用变量: String str ; (2) 在栈中查找有没有存放值为 &amp;quot;abc&amp;quot; 的地址,如果没有,则开辟一个存放字面值为 &amp;quot;abc&amp;quot; 的地址,接着创建一个新的 String 类的对象 o ,并将 o 的字符串值指向这个地址, 而且在栈中这个地址旁边记下这个引用的对象 o 。 如果已经有了值为 &amp;quot;abc&amp;quot; 的地址,则查找对象 o ,并返回 o 的地址。 (3) 将 str 指向对象 o 的地址。 值得注意的是,一般 String 类中字符串值都是直接存值的。 但像 String str = &amp;quot;abc&amp;quot; ;这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用! 例子说明: String str1 = &amp;quot;abc&amp;quot;; String str2 = &amp;quot;abc&amp;quot;; System.out.println(str1==str2); //true 我们这里并不用 str1.equals(str2) ;的方式,因为这将比较两个字符串的值是否相等。 == 号,只有在两个引用都指向了同一个对象时才返回真值。 而我们在这里要看的是, str1 与 str2 是否都指向了同一个对象。 结果说明, JVM 创建了两个引用 str1 和 str2 ,但只创建了一个对象,而且两个引用都指向了这个对象。 下一个例子: String str1 = &amp;quot;abc&amp;quot;; String str2 = &amp;quot;abc&amp;quot;; str1 = &amp;quot;bcd&amp;quot;; System.out.println(str1 + &amp;quot;,&amp;quot; + str2); //bcd, abc System.out.println(str1==str2); //false 这就是说,赋值的变化导致了类对象引用的变化, str1 指向了另外一个新对象!而 str2 仍旧指向原来的对象。上例中,当我们将 str1 的值改为 &amp;quot;bcd&amp;quot; 时, JVM 发现在栈中没有存放该值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。 事实上, String 类被设计成为不可改变 (immutable) 的类。 如果你要改变其值,可以,但 JVM 在运行时根据新值悄悄创建了一个新对象,然后将这个对象的地址返回给原来类的引用。 这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良影响。 修改代码 : String str1 = &amp;quot;abc&amp;quot;; String str2 = &amp;quot;abc&amp;quot;; str1 = &amp;quot;bcd&amp;quot;; String str3 = str1; System.out.println(str3); //bcd String str4 = &amp;quot;bcd&amp;quot;; System.out.println(str1 == str4); //true str3 这个对象的引用直接指向 str1 所指向的对象 ( 注意, str3 并没有创建新对象 ) 。当 str1 改完其值后,再创建一个 String 的引用 str4 , 并指向因 str1 修改值而创建的新的对象。 可以发现,这回 str4 也没有创建新的对象,从而再次实现栈中数据的共享。 继续修改代码 : String str1 = new String(&amp;quot;abc&amp;quot;); String str2 = &amp;quot;abc&amp;quot;; System.out.println(str1==str2); //false 创建了两个引用。 创建了两个对象。两个引用分别指向不同的两个对象。 以上两段代码说明,只要是用 new() 来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。 总结 : (1) 我们在使用诸如 String str = &amp;quot;abc&amp;quot; ;的格式定义类时, String 类的 str 对象可能并没有被创建!唯一可以肯定的是,指向 String 类的引用被创建了。 因此,更为准确的说法是,我们创建了一个指向 String 类的对象的引用变量 str ,这个对象引用变量指向了某个值为 &amp;quot;abc&amp;quot; 的 String 类。 至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,除非你通过 new() 方法来显要地创建一个新的对象。 (2) 使用 String str = &amp;quot;abc&amp;quot; ;的方式,可以在一定程度上提高程序的运行速度, 因为 JVM 会自动根据栈中数据的实际情况来决定是否有必要创建新对象。 而对于 String str = new String(&amp;quot;abc&amp;quot;) ;的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。
  • #21 过程如下: 当一个 class 文件被 ClassLoader load 进入 JVM 后,方法指令保存在 stack 中,此时 heap 区没有数据。然后程序计数器开始执行指令, 如果是静态方法,直接依次执行指令代码,当然此时指令代码是不能访问 heap 数据区的; 如果是非静态方法,由于隐含参数没有值,会报错。 因此在非静态方法执行前,要先 new 对象,在 heap 中分配数据,并把 stack 中的地址指针交给非静态方法,这样程序计数器依次执行指令, 而指令代码此时能够访问到 heap 数据区了。
  • #29 Java 程序中使用的绝大多数资源都是对象,垃圾收集在清理对象方面做得很好。因此,您可以使用任意多的 String 。垃圾收集器最终无需您的干预就会算出它们何时失效,并收回它们使用的内存。
  • #30 垃圾收集为我们做了大量可怕的资源清除工作,但是有些资源仍然需要显式的释放,比如文件句柄、套接字句柄、线程、数据库连接和信号量许可证。当资源的生命周期被绑定到特定调用帧的生命周期时,我们通常可以使用 finally 块来释放该资源,但是长期存活的资源需要一种策略来确保它们最终被释放。对于任何一个这样的对象,即它直接或间接拥有一个需要显式释放的对象,您必须提供生命周期方法 —— 比如 close() 、 release() 、 destroy() 等 —— 来确保可靠的清除。
  • #33 应用组合 对于应用来说,可分配的内存受到 OS 的限制,不同的 OS 对进程所能访问虚拟内存地址区间直接影响对于应用内存的分配, 32 位的操作系统通常最大支持 4G 的内 存寻址,而 Linux 一般为 3G , Windows 为 2G 。 然而这些大小的内存并不会全部给 JVM 的 Java Heap 使用,它主要会分成三部分: Java Heap , Native Heap ,载入资源和类库等所占用的内存。 那么由此可见, Native Heap 和 Java Heap 大小配置是相互制约的,哪一部分分配多了都可能会影响到另外一部分的正常工作,因此如果通过命令行去配置, 那么需要确切的了解应用使用情况,否则 采用默认配置自动监测会更好的优化应用使用情况。 同样要注意的就是进程的虚拟内存和机器的实际内存还是有区别的,对于机器来说实际内存以及硬盘提供的虚拟内存都是提供给机器上所有进程使用的, 因此在设置 JVM 参数时,它的虚拟内存绝对不应该超过实际内存的大小。
  • #35 sun 公司的性能优化白皮书中提到的几个例子: 1 .对于吞吐量的调优。机器配置: 4G 的内存, 32 个线程并发能力。 java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -Xmx3800m -Xms3800m 配置了最大 Java Heap 来充分利用系统内存。 -Xmn2g 创建足够大的青年代(可以并行被回收)充分利用系统内存,防止将短期对象复制到老年代。 -Xss128 减少默认最大的线程栈大小,提供更多的处理虚拟内存地址空间被进程使用。 -XX:+UseParallelGC 采用并行垃圾收集器对年青代的内存进行收集,提高效率。 -XX:ParallelGCThreads=20 减少垃圾收集线程,默认是和服务器可支持的线程最大并发数相同,往往不需要配置到最大值。 2 .尝试采用对老年代并行收集 java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC -Xmx3550m -Xms3550m 内存分配被减小,因为 ParallelOldGC 会增加对于 Native Heap 的需求,因此需要减小 Java Heap 来满足需求。 -XX:+UseParallelOldGC 采用对于老年代并发收集的策略,可以提高收集效率。 3 .提高吞吐量,减少应用停顿时间 java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=31 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC 选择了并发标记交换收集器,它可以并发执行收集操作,降低应用停止时间,同时它也是并行处理模式,可以有效地利用多处理器的系统的多进程处理。 -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=31 表示在青年代中 Eden 和 Survivor 比例,设置增加了 Survivor 的大小,越大的 survivor 空间可以允许短期对象尽量在年青代消亡。 -XX:TargetSurvivorRatio=90 允许 90% 的空间被占用,超过默认的 50% ,提高对于 survivor 的使用率。