前段日子在和leader交流技术的时候,偶然听到jvm在分配内存空间给大对象时,如果young区空间不足会直接在old区切一块过去。对于这个结论很好奇,也比较怀疑,所以就上网搜了下,发现还真有这么回事。以下给出具体代码来说明:
首先定义好jvm内存各个区域的大小。我设定的是eden区8M,from和to各1M,old区10M,总共20M的空间,参数如下:
-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
紧接着,开始写程序。很简单,就是初始化一个9M的程序,然后用jstat命令看jdk的内存使用情况。
public class App {
private static final int _1MB = 1024 * 1024;
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
* -XX:PretenureSizeThreshold=3145728
*/
public static void main(String[] args) {
byte[] allocation = new byte[9*_1MB];
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
然后打成jar,执行。结果如下:
S0 S1 E O P YGC YGCT FGC FGCT GCT
0.00 0.00 18.04 90.00 23.08 0 0.000 20 0.027 0.027
果然,当对象大小大于eden区的时候会直接扔到old区。但我还不满足与此,于是将对象改大了些,改成了11M。再次尝试发现结果如下:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.taobao.jdkmem.App.main(App.java:17)
到这里结束了么?当然没有:)这个是一个大的完整的对象,当大对象本身是由一连串的小对象组成的时候,会不会不再OOM呢?于是改了代码再次尝试:
public class App {
private static final int _1MB = 1024 * 1024;
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
* -XX:PretenureSizeThreshold=3145728
*/
public static void main(String[] args) {
byte[][] allocation;
allocation = new byte[11][_1MB]; // 直接分配在老年代中
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
再次运行,结果如下:
S0 S1 E O P YGC YGCT FGC FGCT GCT
0.00 38.06 67.68 60.02 23.10 1 0.007 14 0.012 0.019
果然,这次居然又被jvm给生吃下去了。不过这次并非所有的都在old区,而是有一部分还在young区里活着。看来jvm还是足够彪悍的。
由此可见,当出现大对象的时候,jvm用牺牲部分宝贵的old区的方式来保证了整个jvm的正常运转。所以,程序中尽量要避免大对象,如果实在不行,就让大对象活的尽量久些,莫要new一个然后gc掉再new一个再gc,这么爆jvm可不太友好。
到这里结束了吧?你猜对了,还没有:P既然知道jvm会对大对象申请内存做特殊处理,那么就在琢磨程序员有没有方法干预这个过程呢?答案是有的,就是使用这个参数-XX:PretenureSizeThreshold。这个参数的单位是Byte,其作用是当新对象申请的内存空间大于这个参数值的时候,直接扔到old区。做个试验就证明了:
public class App {
private static final int _1MB = 1024 * 1024;
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
* -XX:PretenureSizeThreshold=3145728
*/
public static void main(String[] args) {
byte[] allocation = new byte[4*_1MB];
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行命令如下:
java -jar -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728 memtest-1.0-SNAPSHOT.jar
我设置的阈值是3M,意味着超过3M的对象会被直接扔到old区。结果是皆大欢喜,新对象直接被扔到了old区:
S0 S1 E O P YGC YGCT FGC FGCT GCT
0.00 0.00 18.04 40.00 23.08 0 0.000 0 0.000 0.000
试验有了结果,自然而然心情愉悦。但这个参数使用时需要慎重,因为fullgc的代价很高,因此old区就显得非常宝贵。除非你真的清楚你在干什么,否则莫要轻易玩这个参数,万一搞个频繁fullgc就玩大了。ok,到此打完收工。
分享到:
相关推荐
4. Java 堆:它是 Java 虚拟机管理的内存中最大的一块,是被所有线程共享的一块内存区域,它在虚拟机启动时创建,它的唯一目的是存储对象实例,几乎所有的对象都在这里分配内存。Java 堆也是垃圾收集器管理的主要...
- 特殊情况下直接在 Old 区分配,如大对象或长时间存活的对象。 - TLAB (Thread Local Allocation Buffer) 是一种优化技术,允许每个线程拥有自己的缓冲区,从而加快分配速度。 2. **栈上分配**: - 适用于原子...
5. ** dominator tree**:这是一种特殊的引用树,显示了哪些对象控制着内存的大部分。根节点是全局最不可达的对象,即没有其他对象引用它的对象,而从根到叶的路径代表了内存消耗的路径。 6. **对象集分析**:MAT...
当执行`new`操作时,JVM会在堆中为对象分配空间,并调用构造函数初始化对象。而栈内存则用于存储方法调用时的局部变量,一旦方法执行完毕,这些局部变量就会被自动释放。 "MemoryAllocation.java"这个源码文件可能...
在JVM的内存模型中,直接内存(Direct Memory)是一个特殊的区域,虽然它并不属于标准的JVM运行时数据区,但对高性能的应用程序设计有着重要的影响。本文将深入探讨直接内存的概念、用途、优缺点以及如何进行配置。 ...
5. 内存管理:JVM的垃圾回收机制自动管理内存,包括对象的分配和回收。常见的垃圾收集算法有标记-清除、复制、标记-整理和分代收集等。 6. 多线程:JVM支持多线程并发执行,每个线程都有自己的程序计数器和虚拟机栈...
准备为类的静态变量分配内存并初始化为默认值;解析将符号引用转换为直接引用;初始化执行类的初始化方法。了解这个过程有助于我们理解和控制类的生命周期。 四、垃圾收集与内存优化 JVM的垃圾收集机制负责自动...
新一代的垃圾回收算法,如Garbage First(G1)收集器,旨在更好地处理大内存应用的性能问题。G1收集器将堆内存划分为多个区域,跟踪各区域垃圾回收的价值和成本,并优先收集价值最高的区域。 在调优过程中,我们...
3. **准备**:为类的静态变量分配内存并赋予默认初始值。非静态变量的内存分配发生在对象实例化时。 4. **解析**:将符号引用转换为直接引用,不一定要在初始化之前完成。 5. **初始化**:执行类中定义的初始化...
直接内存是 JVM 体系架构中的一种特殊的内存区域,它并不是虚拟机内存的一部分,也不是 Java 虚拟机规范中定义的内存区域。直接内存是通过 NIO 库中的 DirectBuffer 类来分配的,它可以调用 Native 方法直接分配堆外...
gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存。java语言并不要求jvm有gc,也没有规定gc如何工作。不过常用的jvm都有gc,而且大多数gc都使用类似的算法管理内存和执行收集操作。 在充分理解了...
JVM TI允许开发人员创建工具,如内存分析器、性能监视器、调试器等,这些工具可以深入洞察JVM的内部工作,包括类加载、对象分配、线程活动、方法执行等。 在标题"JVM TI Watch dll"中,"Watch dll"通常指的是一个...
Java虚拟机(JVM)作为Java程序的运行环境,负责管理和分配内存资源。为了更好地理解和掌握JVM内部的工作机制,本篇文章将重点介绍JVM中的几个关键内存区域:程序计数器、Java虚拟机栈、本地方法栈、堆以及方法区。 #...
堆内存则是JVM用来动态分配对象和数组的地方。当使用`new`关键字创建一个对象时,对象的数据成员和方法将被存储在堆内存中。由于堆内存的分配和回收由Java的垃圾回收机制自动管理,所以开发者无需手动释放内存。然而...
Java 内存分配寄存器:Java 内存分配寄存器是 JVM 中的一块特殊区域,用于存储对象的引用变量和基本类型变量的值。 静态域:静态域是指 static 定义的静态成员,它们的生命周期和虚拟机相同。 常量池:常量池是...
与栈相比,堆的最大优点在于能够动态分配内存,其大小和生存周期无需事先告知编译器,由Java的垃圾回收机制自动管理。这种灵活性使得堆成为复杂数据结构和大型对象的理想选择。然而,动态内存分配的代价是存取速度...
内存的一部分用于创建堆空间,当程序中创建对象时,就从对空间中分配内存。GC是JVM内部的一个进程,回收无效对象的内存用于将来的分配。 10. JVM内存区域: JVM内存区域主要分为线程私有区域【程序计数器、虚拟机栈...
在加载阶段,JVM通过类的全限定名获取类的二进制数据,并在方法区为这个类分配内存,同时在堆中创建一个代表这个类的java.lang.Class对象。验证阶段是对类文件的格式、元数据、字节码和符号引用等进行检验,确保类...
声明一个对象rect时,将在栈内存为对象的引用变量rect分配内存空间,但Rectangle的值为空,称rect是一个空对象。空对象不能使用,因为它还没有引用任何"实体"。(2)对象实例化时的内存模型当执行rect=new Rectangle...