3.内存分配逃逸分析与栈上分配直接内存和运行时常量池基本类型的包装类和常量池TLAB可达性分析算法(学习笔记)
Posted to.to
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了3.内存分配逃逸分析与栈上分配直接内存和运行时常量池基本类型的包装类和常量池TLAB可达性分析算法(学习笔记)相关的知识,希望对你有一定的参考价值。
3.JVM内存分配
3.1.内存分配概述
3.2.内存分配–Eden区域
3.3.内存分配–大对象直接进老年代
3.3.1.背景
3.3.2.解析
3.4.内存分配–长期存活的对象进去老年代
3.5.内存分配–空间分配担保
3.5.1.堆空间参数
3.5.2.-XX:HandlePromotionFailure
3.6.内存分配–逃逸分析与栈上分配
3.6.1.逃逸分析
3.6.1.1.方法逃逸
3.6.1.2.线程分配
3.6.2.栈上分配
3.6.3.逃逸分析/栈上分配的优势分析
3.6.3.1.同步消除
3.6.4.标量替换
3.6.5.什么情况下会发生逃逸?
3.7.直接内存
3.8.Java内存区域-直接内存和运行时常量池
3.8.1.运行时常量池简介
3.8.2.Class文件中的信息常量池
3.8.3.常量池的好处
3.8.4.基本类型的包装类和常量池
3.9.对象在内存中的布局-对象的创建
3.10.探究对象的结构
3.11.深度理解对象的访问定位
3.12.Java对象访问方式
3.12.1.通过句柄访问
3.12.2.通过直接指针访问
3.13.对象分配内存的策略
3.13.1.线程安全问题
3.13.1.1.本地线程分配缓冲----TLAB
3.13.1.2.TLAB生命周期
3.13.1.3.TLAB的大小
3.13.1.4.总结
3.13.1.5.参数总结
3.14.垃圾回收-判断对象是否存活算法-引用计数法详解
3.15.垃圾回收-判断对象是否存活算法-可达性分析法详解
3.15.1.可达性分析算法
3.15.2.finalize()方法最终判定对象是否存活
3.15.3.Java引用
3.15.3.1.强引用
3.15.3.2.软引用
3.15.3.3.弱引用
3.15.3.4.虚引用
3.15.3.5.软引用和弱引用进一步说明
3.15.3.6.虚引用进一步说明:
3.JVM内存分配
3.1.内存分配概述
3.1.1.优先分配到eden
3.1.2.大对象直接分配到老年代
3.1.3.长期存活的对象分配到老年代
3.1.4.空间分配担保
3.1.5.动态对象年龄判断
Java对象所占用的内存主要在堆上实现,因为堆是线程共享的,因此在堆上分配内存时需要进行加锁,这就导致了创建对象的开销比较大。当堆上空间不足时,会触发GC,如果GC后空间仍然不足,则会抛出OutOfMemory异常。
为了提升内存分配效率,在年轻代的Eden区HotSpot虚拟机使用了两种技术来加快内存分配 ,分别是bump-the-pointer和TLAB(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.2.内存分配–Eden区域
Java执行的时候,默认使用parallel收集器。对象优先到Eden中:
案例:
创建Main类:
package com.toto.jvm.demo;
public class Main {
public static void main(String[] args) {
byte[] b1 = new byte[4 * 1024 * 1024];
}
}
在Eclipse中配置VM arguments参数(-verbose:gc -XX:+PrintGCDetails):
Eden是新生代上的一部分区域,当运行上面的代码的时候,GC日志输出中可以看到优先到Eden,输出结果如下:
上面输出ParOldGen,说明使用的parallel收集器。
使用serialGC的时候,打印的gc日志(-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC):
运行后输出结果:
由于上面的b1分配的内存是4 * 1024 * 1024 即4M
而上图可以看到只有eden space 138816K,占比8%。可以得出结论:创建的对象优先进入eden区域。
当把b1变成200M时(即大对象):
说明:大对象直接分配到老年代。
再如案例:
package com.toto.jvm.demo;
public class Main {
public static void main(String[] args) {
byte[] b1 = new byte[5 * 1024 * 1024];
byte[] b2 = new byte[4 * 1024 * 1024];
byte[] b3 = new byte[4 * 1024 * 1024];
byte[] b4 = new byte[4 * 1024 * 1024];
}
}
修改VM参数:
-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
输出结果:
-XX:SurvivorRatio=8表示Survivor是Eden的1/8。
3.3.内存分配–大对象直接进老年代
3.3.1.背景
讲到大对象主要指字符串和数组,虚拟机提供了一个-XX:PretenureSizeThreshold参数,大于这个值的参数直接在老年代分配。
这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存复制(新生代采用复制算法)。
3.3.2.解析
有两种情况,对象会直接分配到老年代:
如果在新生代分配失败且对象是一个不含任何对象引用的大数组,可被直接分配到老年代。通过在老年代的分配避免新生代的一次垃圾回收。
XX:PretenureSizeThreshold=<字节大小>可以设分配到新生代分配内存。任何比这个大的对象都不会尝试在新生代分配,将在老年代分配内存。
PretenureSizeThreshold默认值是0,意味着任何对象都会现在新生代分配内存。
案例:
设置虚拟机参数:
-verbose:gc -XX:+PrintGCDetails -Xms2048M -Xmx2048M -Xmn1024M -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC
-Xms表示初始化堆内存
-Xmx表示最大堆内存
-Xmn表示新生代的内存
-XX:SurvivorRatio=8表示新生代的Eden占8/10,S1和S2各占1/10
因此Eden的内存大小为:0.8 * 1024 * 1024 * 1024字节 约为819 * 1024 * 1024
上代码:
package com.toto.jvm.demo2;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
public class Main {
public static void main(String[] args) {
//734003216
byte[] array = new byte[700 * 1024 * 1024];
for (MemoryPoolMXBean memoryPoolMXBean : ManagementFactory.getMemoryPoolMXBeans()) {
System.out.println(memoryPoolMXBean.getName() + " 总量:" + memoryPoolMXBean.getUsage().getCommitted() + " 使用的内存:" + memoryPoolMXBean.getUsage().getUsed());
}
}
}
输出结果:
当把代码改成:
package com.toto.jvm.demo2;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
public class Main {
public static void main(String[] args) {
//734003216
byte[] array = new byte[900 * 1024 * 1024];
for (MemoryPoolMXBean memoryPoolMXBean : ManagementFactory.getMemoryPoolMXBeans()) {
System.out.println(memoryPoolMXBean.getName() + " 总量:" + memoryPoolMXBean.getUsage().getCommitted() + " 使用的内存:" + memoryPoolMXBean.getUsage().getUsed());
}
}
}
3.4.内存分配–长期存活的对象进去老年代
用法: -XX:MaxTenuringThreshold=15
该参数主要是控制新生代需要经历多少次GC晋升到老年代中的最大阈值。在JVM中用4个bit存储(放在对象头中),(1111)所以其最大值是15。
但并非意味着,对象必须要经历15次YGC才会晋升到老年代中。例如,当Survivor区空间不够时,便会提前进入到老年代中,但这个次数一定不大于设置的最大阈值。
那么JVM到底是如何来计算S区对象晋升到Old区的呢?
首先介绍另一个重要的JVM参数:
-XX:TargetSurvivorRatio:一个计算期望S区存活大小(Desired survivor size)的参数。默认值为50,即50%。
当一个S区中所有的age对象的大小如果大于等于Desired survivor size,则重新计算threshold,以age和MaxTenuringThreshold两者的最小值为准。
以一个Demo为例。设置VM参数值:
-Xmx200M -Xmn50m -XX:TargetSurvivorRatio=60 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:MaxTenuringThreshold=3 -XX:+PrintTenuringDistribution
代码:
package com.toto.jvm.demo3;
/**
* -Xmx200M -Xmn50m -XX:TargetSurvivorRatio=60 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:MaxTenuringThreshold=3
* 最小堆为50M,默认SurvivorRatio为8,那么可以知道Eden区为40M,S0和S1为5M
*
* 可以在JVM启动参数中加上-XX:+PrintTenuringDistribution,该参数可以输出age的额外信息。
*/
public class App {
public static void main(String[] args) throws InterruptedException {
// main方法作为主线程,变量不会被回收
byte[] byte1 = new byte[1 * 1024 * 1024];
byte[] byte2 = new byte[1 * 1024 * 1024];
YGC(40);
Thread.sleep(3000);
YGC(40);
Thread.sleep(3000);
YGC(40);
Thread.sleep(3000);
// 这次再ygc时, 由于byte1和byte2的年龄经过3次ygc后已经达到3(-XX:MaxTenuringThreshold=3),
// 所以会晋升到old
YGC(40);
// ygc后, s0(from)/s1(to)的空间为0
Thread.sleep(3000);
// 达到TargetSurvivorRatio这个比例指定的值,即5M(S区)*60%(TargetSurvivorRatio)=3M(Desired survivor size)
byte[] byte4 = new byte[1 * 1024 * 1024];
byte[] byte5 = new byte[1 * 1024 * 1024];
byte[] byte6 = new byte[1 * 1024 * 1024];
// 这次ygc时, 由于s区已经占用达到了60%(-XX:TargetSurvivorRatio=60),
// 所以会重新计算对象晋升的min(age, MaxTenuringThreshold) = 1
YGC(40);
Thread.sleep(3000);
// 由于前一次ygc时算出age=1, 所以这一次再ygc时, byte4, byte5, byte6就要晋升到Old,
// 而不需要等MaxTenuringThreshold这么多次, 此次ygc后, s0(from)/s1(to)的空间再次为0,
// 对象全部晋升到old
YGC(40);
Thread.sleep(3000);
System.out.println("GC end!");
}
// 塞满Eden区,局部变量会被回收,作为触发GC的小工具
private static void YGC(int edenSize) {
for (int i = 0; i < edenSize; i++) {
byte[] byte1m = new byte[1 * 1024 * 1024];
}
}
}
输出结果:
2021-05-03T10:53:15.791+0800: [GC (Allocation Failure) 2021-05-03T10:53:15.791+0800: [ParNew
Desired survivor size 3145728 bytes, new threshold 3 (max 3)
- age 1: 2649352 bytes, 2649352 total
: 40551K->2623K(46080K), 0.0022131 secs] 40551K->2623K(199680K), 0.0023103 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-05-03T10:53:18.797+0800: [GC (Allocation Failure) 2021-05-03T10:53:18.797+0800: [ParNew
Desired survivor size 3145728 bytes, new threshold 3 (max 3)
- age 1: 168 bytes, 168 total
- age 2: 2647416 bytes, 2647584 total
: 43362K->2824K(46080K), 0.0025757 secs] 43362K->2824K(199680K), 0.0026316 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-05-03T10:53:21.805+0800: [GC (Allocation Failure) 2021-05-03T10:53:21.805+0800: [ParNew
Desired survivor size 3145728 bytes, new threshold 3 (max 3)
- age 2: 168 bytes, 168 total
- age 3: 2647416 bytes, 2647584 total
: 43562K->2694K(46080K), 0.0009461 secs] 43562K->2694K(199680K), 0.0009973 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-05-03T10:53:24.808+0800: [GC (Allocation Failure) 2021-05-03T10:53:24.808+0800: [ParNew
Desired survivor size 3145728 bytes, new threshold 3 (max 3)
- age 3: 168 bytes, 168 total
: 43432K->104K(46080K), 0.0048805 secs] 43432K->2740K(199680K), 0.0049507 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-05-03T10:53:27.820+0800: [GC (Allocation Failure) 2021-05-03T10:53:27.821+0800: [ParNew
Desired survivor size 3145728 bytes, new threshold 1 (max 3)
- age 1: 3145776 bytes, 3145776 total
: 40842K->3072K(46080K), 0.0028666 secs] 43478K->5708K(199680K), 0.0030672 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-05-03T10:53:30.827+0800: [GC (Allocation Failure) 2021-05-03T10:53:30.827+0800: [ParNew
Desired survivor size 3145728 bytes, new threshold 3 (max 3)
: 43811K->0K(46080K), 0.0033850 secs] 46447K->5708K(199680K), 0.0034430 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
GC end!
Heap
par new generation total 46080K, used 13910K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
eden space 40960K, 33% used [0x00000000f3800000, 0x00000000f45959a0, 0x00000000f6000000)
from space 5120K, 0% used [0x00000000f6000000, 0x00000000f6000000, 0x00000000f6500000)
to space 5120K, 0% used [0x00000000f6500000, 0x00000000f6500000, 0x00000000f6a00000)
concurrent mark-sweep generation total 153600K, used 5708K [0x00000000f6a00000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 2595K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 288K, capacity 386K, committed 512K, reserved 1048576K
============================================================================
另外的一篇文章的说明:
-XX:MaxTenuringThreshold设置的是年龄阈值,默认15(对象被复制的次数)
JVM为每个对象定义了一个对象年龄(Age)计数器, 对象在Eden出生如果经第一次Minor GC后仍然存活, 且能被Survivor容纳的话, 将被移动到Survivor空间中, 并将年龄设为1. 以后对象在Survivor区中每熬过一次Minor GC年龄就+1. 当增加到设置的阀值时将会晋升到老年代。
但有一个疑惑,为什么我设置-XX:MaxTenuringThreshold足够大了防止大量对象进入老年区,虽然进入老年区的对象减少了,但还是有?
因为如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半, 年龄大于或等于该年龄的对象就可以直接进入老年代。
3.5.内存分配–空间分配担保
主要使用的JVM参数配置是:-XX:HandlePromotionFailure,使用空间分配担保的时候使用-XX:+HandlePromotionFailure,不使用分配担保的时候使用-XX:-HandlePromotionFailure。
3.5.1.堆空间参数
官网地址:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
-XX:+PrintFlagsInitial : 查看所有的参数默认初始值
-XX:+PrintFlagsFinal: 查看所有的参数的最终值(可能会存在修改,不再是初始值)
-Xms: 初始堆空间内存(默认为物理内存的1/64)
-Xmx: 最大堆空间内存(默认为物理内存的1/4)
-Xmn: 设置新生代的大小(初始值及最大值)。
-XX:NewRatio: 配置新生代与老年代在堆结构的占比。
-XX:SurvivorRatio: 设置新生代中Eden和S0/S1空间的比例。
-XX:MaxTenuringThreshold: 设置新生代垃圾的最大年龄。
-XX:+PrintGCDetails: 输出详细的GC处理日志
打印gc简要信息:(1) -XX:+PrintGC (2) -verbose:gc
-XX:HandlePromotionFailure: 是否设置空间分配担保
3.5.2.-XX:HandlePromotionFailure
JDK7及以后这个参数就失效了。
只要老年代的连续空间大于新生代对象的总大小或者历次晋升到老年代的对象的平均大小就进行MinorGC,否则FullGC。
JDK7及以前这个参数的作用见下图:
3.6.内存分配–逃逸分析与栈上分配
3.6.1.逃逸分析
内存逃逸主要是对象的动态作用域的改变而引起的,故而内存逃逸的分析就是分析对象的动态作用域。
发生逃逸行为的情况分为两种:方法逃逸和线程逃逸
逃逸是指在某个方法之内创建的对象,除了在方法体之内被引用之外,还在方法体之外被其它变量引用到;这样带来的后果是在该方法执行完毕之后,该方法中创建的对象将无法将GC回收,由于其被其它变量引用。正常的方法调用中,方法体中创建的对象将在执行完毕之后,将回收其中创建的对象;故由于无法回收,即成为逃逸。
如果对象发生逃逸,那会分配到堆中。(因为对象发生了逃逸,就代表这个对象可以被外部访问,换句话说,就是可以共享,能共享数据的,无非就是堆或方法区,这就是堆。)
如果对象没发生逃逸,那会分配到栈中。(因为对象没发生逃逸,那就代表这个对象不能外部访问,换句话说,就是不可共享,这里就是栈。)
package com.toto.jvm.demo4;
import jvm.test;
public class Main {
public static Object obj;
public void globalVariableEscape() {
// 给全局变量赋值,发生逃逸
obj = new Object();
}
public Object methodEscape() {
// 方法返回值,发生逃逸
return new Object();
}
public void instanceEscape() {
// 实例引用,发生逃逸
test(this);
}
public void getInstance() {
//对象的作用域只在当前方法中有效,没有发生逃逸
Object obj1 = new Object();
}
}
运行java时传递jvm参数-XX:+DoEscapeAnalysis
栈上分配与逃逸分析的关系
进行逃逸分析之后,产生的后果是所有的对象都将由栈上分配,而非从JVM内存模型中的堆来分配。
栈上分配可以提升代码性能,降低在多线程情况下的锁使用,但是会受限于其空间的大小。
分析找到未逃逸的变量,将变量类的实例化内存直接在栈里分配(无需进入堆),分配完成后,继续在调用栈内执行,最后线程结束,栈空间被回收,局部变量对象也被回收。
能在方法内创建对象,就不要再方法外创建对象。
1.什么是栈上分配?
栈上分配主要是指在java程序的执行过程中,在方法体中声明的变量以及创建的对象,将直接从该线程所使用的栈中分配空间。一般而言,创建对象都是从堆中来分配的,这里是指在栈上来分配空间给新创建的对象。
2.什么是逃逸?
逃逸是指在某个方法之内创建的对象,除了在方法体之内被引用之外,还在方法体之外被其它变量引用到;这样带来的后果是在该方法执行完毕之后,该方法中创建的对象将无法被GC回收,由于其被其它变量引用。正常的方法调用中,方法体中创建的对象将在执行完毕之后,将回收其中创建的对象;故由于无法回收,即成为逃逸。
3.6.1.1.方法逃逸
当方法创建了一个对象之后,这个对象被外部方法所调用,这个时候方法运行结束要进行GC时,本该方法的对象被回收,却发现该对象还存活着,没法回收,则称为"方法逃逸"
简单来说:就是当前方法创建的对象,本该是当前方法的栈帧所管理,却被调用方所使用,可以称之为内存逃逸。
3.6.1.2.线程分配
直接将对象进行返回出去,该对象很可能被外部线程所访问,如:赋值给变量等,则称为”线程逃逸”。
当我们创建一个对象的时候,会立马想到该对象是会存储到堆空间中的,而垃圾回收机制会在堆空间中回收不再使用的对象,但是筛选可回收对象,还有整理对象都需要消耗时间,如果能够通过逃逸分析确定某些对象不会逃出到方法外的话,那么就可以直接让这个对象在栈空间分配内存,这样该对象会随着方法的执行完毕自动进行销毁。
3.6.2.栈上分配
栈上分配主要是指在Java程序的执行过程中,在方法体中声明的变量以及创建的对象,将直接从该线程所使用的栈中分配空间。一般而言,创建对象都是从堆中来分配的,这里是指在栈上分配空间给新建的对象。
如果能够证明一个对象,不会进行逃逸到方法或线程外的话,则可以对该变量进行优化。
3.6.3.逃逸分析/栈上分配的优势分析
优势表现在以下两个方面:
消除同步:线程同步的代价是相当高的,同步的后果是降低并发性和性能。逃逸分析可以判断出某个对象是否始终只被一个线程访问,如果只被一个线程访问,那么对该对象的同步操作就可以转化成没有同步保护的操作,这样就能大大提高并发程度和性能。
矢量替代:逃逸分析方法如果发现对象的内存存储结构不需要连续进行的话,就可以将对象的部分甚至全部保存在CPU寄存器内,这样能大大提高访问速度。
劣势:
栈上分配受限于栈的空间大小,一般自我迭代类的需求以及大的对象空间需求操作,将导致栈的内存溢出;故只适用于一定范围之内的内存范围请求。
3.6.3.1.同步消除
线程同步本身比较耗时,若确定了一个变量不会逃逸出线程,无法被其他线程访问到,那这个变量的读写就不会存在竞争,则可以消除对该对象的同步锁。
3.6.4.标量替换
1、标量是指不可分割的量,如java中基本数据类型和引用类型,都不能够再进一步分解,他们就可以成为称为标量。
2、若一个数据可以继续分解,那就称之为聚合量,而对象就是典型的聚合量。
3、若逃逸分析证明一个对象不会逃逸出方法,不会被外部访问,并且这个对象是可以被分解的,那程序在真正执行的时候可能不创建这个对象,而是直接创建这个对象分解后的标量来代替。这样就无需在对对象分配空间了,只在栈上为分解出的变量分配内存即可。
注意:
逃逸分析是比较耗时的,所以性能未必提升很多,因为其耗时性,采用的算法都是不那么准确但是时间压力相对较小的算法来完成的,这就可能导致效果不稳定,要慎重。
由于HotSpot虚拟机目前的实现方法导致栈上分配实现起来比较复杂,所以HotSpot虚拟机中暂时还没有这项优化。
相关JVM参数:
-XX:+DoEscapeAnalysis 开启逃逸分析、
-XX:+PrintEscapeAnalysis 开启逃逸分析后,可通过此参数查看分析结果。
-XX:+EliminateAllocations 开启标量替换。
-XX:+EliminateLocks 开启同步消除。
-XX:+PrintEliminateAllocations 开启标量替换后,查看标量替换情况。
3.6.5.什么情况下会发生逃逸?
案例:
package com.toto.jvm.demo4;
public class StackAllocation {
public StackAllocation obj;
/**
* 方法返回StackAllocation对象,发生逃逸
* @return
*/
public StackAllocation getInstance() {
return obj == null ? new StackAllocation() : obj;
}
/**
* 为成员属性赋值,发生逃逸
*/
public void setObj() {
this.obj = new StackAllocation();
}
/**
* 对象的作用域仅在当前方法中有效,没有发生逃逸
*/
public void useStackAllocation() {
StackAllocation s = new StackAllocation();
}
/**
* 引用成员变量的值,发生逃逸
*/
public void useStackAllocation2() {
StackAllocation s = getInstance();
}
}
3.7.直接内存
查看一下什么是直接内存。
NIO中直接分配直接内存。
3.8.Java内存区域-直接内存和运行时常量池
3.8.1.运行时常量池简介
运行时常量池(Runtime Constant Pool),它是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到常量池中。
运行时常量是相对于常量来说的,它具备一个重要特征是:动态性。当然,值相同的动态常量与我们通常说的常量只是来源不同,但是都是储存在池内同一块内存区域。Java语言并不要求常量一定只能在编译期产生,运行期间也可能产生新的常量,这些常量被放在运行时常量池中。这里所说的常量包括:基本类型包装类(包装类不管理浮点型,整型只会管理-128到127)和String(也可以通过**String.intern()**方法可以强制将String放入常量池)
3.8.2.Class文件中的信息常量池
在Class文件结构中,最头的4个字节用于存储M
以上是关于3.内存分配逃逸分析与栈上分配直接内存和运行时常量池基本类型的包装类和常量池TLAB可达性分析算法(学习笔记)的主要内容,如果未能解决你的问题,请参考以下文章