原创 | 求你了,别再说Java对象都是在堆内存上分配空间的了!
△Hollis, 一个对Coding有着独特追求的人△
Java作为一种面向对象的,跨平台语言,其对象、内存等一直是比较难的知识点,所以,即使是一个Java的初学者,也一定或多或少的对JVM有一些了解。可以说,关于JVM的相关知识,基本是每个Java开发者必学的知识点,也是面试的时候必考的知识点。
在JVM的内存结构中,比较常见的两个区域就是堆内存和栈内存(如无特指,本文提到的栈均指的是虚拟机栈),关于堆和栈的区别,很多开发者也是如数家珍,有很多书籍,或者网上的文章大概都是这样介绍的:
1、堆是线程共享的内存区域,栈是线程独享的内存区域。
2、堆中主要存放对象实例,栈中主要存放各种基本数据类型、对象的引用。
但是,作者可以很负责任的告诉大家,以上两个结论均不是完全正确的。
在我之前的文章《》中,介绍过了关于堆内存并不是完完全全的线程共享有关的知识点,本文就第二个话题来探讨一下。
对象内存分配
在《Java虚拟机规范》中,关于堆有这样的描述:
在Java虚拟机中,堆是可供各个线程共享的运行时内存区域,也是供所有类实例和数组对象分配内存的区域。
在《》文章中,我们也介绍过,一个Java对象在堆上分配的时候,主要是在Eden区上,如果启动了TLAB的话会优先在TLAB上分配,少数情况下也可能会直接分配在老年代中,分配规则并不是百分之百固定的,这取决于当前使用的是哪一种垃圾收集器,还有虚拟机中与内存有关的参数的设置。
但是一般情况下是遵循以下原则的:
-
对象优先在Eden区分配 -
优先在Eden分配,如果Eden没有足够空间,会触发一次Monitor GC -
大对象直接进入老年代 -
需要大量连续内存空间的Java对象,当对象需要的内存大于-XX:PretenureSizeThreshold参数的值时,对象会直接在老年代分配内存。
JIT 技术
热点检测
基于计数器的热点探测(Counter Based Hot Spot Detection)。采用这种方法的虚拟机会为每个方法,甚至是代码块建立计数器,统计方法的执行次数,某个方法超过阀值就认为是热点方法,触发JIT编译。
编译优化
逃逸分析
public static String craeteStringBuffer(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb.toString();
}
-
同步省略 -
标量替换 -
栈上分配
标量替换、栈上分配
标量替换
public static void main(String[] args) {
alloc();
}
private static void alloc() {
Point point = new Point(1,2);
System.out.println("point.x="+point.x+"; point.y="+point.y);
}
class Point{
private int x;
private int y;
}
private static void alloc() {
int x = 1;
int y = 2;
System.out.println("point.x="+x+"; point.y="+y);
}
实验证明
public static void main(String[] args) {
long a1 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
alloc();
}
// 查看执行时间
long a2 = System.currentTimeMillis();
System.out.println("cost " + (a2 - a1) + " ms");
// 为了方便查看堆内存中对象个数,线程sleep
try {
Thread.sleep(100000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
private static void alloc() {
User user = new User();
}
static class User {
}
-Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
➜ ~ jmap -histo 2809
num #instances #bytes class name
----------------------------------------------
1: 524 87282184 [I
2: 1000000 16000000 StackAllocTest$User
3: 6806 2093136 [B
4: 8006 1320872 [C
5: 4188 100512 java.lang.String
6: 581 66304 java.lang.Class
-Xmx4G -Xms4G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
➜ ~ jmap -histo 2859
num #instances #bytes class name
----------------------------------------------
1: 524 101944280 [I
2: 6806 2093136 [B
3: 83619 1337904 StackAllocTest$User
4: 8006 1320872 [C
5: 4188 100512 java.lang.String
6: 581 66304 java.lang.Class
逃逸分析并不成熟
总结
在 GitHub 更新中,欢迎关注,欢迎star。
直面Java第300期:什么是逃逸分析?
深入并发第013期:拓展synchronized——锁优化
- MORE | 更多精彩文章 -
如果你喜欢本文,
请长按二维码,关注 Hollis.
好文章,我在看❤️
