由于还在不断的整理及发现中,更加详细的内容请查看下面的笔记链接;
——>笔记链接点这里<——
一、对象创建
1.1)类加载检查
1.2)分配内存
- 如何划分内存。
- 在并发情况下, 可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。
- “指针碰撞”(Bump the Pointer)(默认用指针碰撞)
- “空闲列表”(Free List)
解决并发问题的方法:
- CAS(compare and swap)
- 本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)
1.3)初始化
1.4)设置对象头
2、Klass Pointer类型指针,开启压缩占用4字节,关闭压缩占用8字节。类的元数据指针-D,就是方法区中的信息-隐藏的类对象指针,也就说明可以通过反射能获取到目标对象的所有开放信息,PS:类元信息是C++对象实现的;
锁状态
|
25bit
|
4bit
|
1bit
|
2bit
| |
23bit
|
2bit
|
是否偏向锁
|
锁标志位
| ||
无锁
|
对象的HashCode
|
分代年龄
长度决定了不能超过15
|
0
|
01
| |
轻量级
|
指向栈中锁记录的指针
|
00
| |||
重量级
|
指向互斥量-重量级锁的指针
|
10
| |||
GC标记
|
空
|
11
| |||
偏向锁
|
线程ID
|
Epoch
|
分代年龄
|
1
|
01
|
对象头在hotspot的C++源码里的说明如下:
Bit‐format of an object header (most significant first, big endian layout below):
//
// 32 bits:
// ‐‐‐‐‐‐‐‐
// hash:25 ‐‐‐‐‐‐‐‐‐‐‐‐>| age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
// size:32 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐>| (CMS free block)
// PromotedObject*:29 ‐‐‐‐‐‐‐‐‐‐>| promo_bits:3 ‐‐‐‐‐>| (CMS promoted object)
//
// 64 bits:
// ‐‐‐‐‐‐‐‐
// unused:25 hash:31 ‐‐>| unused:1 age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)
// PromotedObject*:61 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐>| promo_bits:3 ‐‐‐‐‐>| (CMS promoted object)
// size:64 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐>| (CMS free block)
//
// unused:25 hash:31 ‐‐>| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object)
// JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object)
// narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ‐‐‐‐‐>| (COOPs && CMS promoted object)
// unused:21 size:35 ‐‐>| cms_free:1 unused:7 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐>| (COOPs && CMS free block)
1.5)执行方法
二、对象的处理
2.1、查看对象大小
<!-- 查看对象大小及指针压缩所需的包 -->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
import org.openjdk.jol.info.ClassLayout;
/**
* 类描述: 计算对象大小
*
* @author XXSD
* @version 1.0.0
* @date 2020/6/27 0027 上午 11:25
*/
public class jolSample {
public static void main(String[] values){
ClassLayout classLayout = ClassLayout.parseInstance(new Object());
System.out.println(classLayout.toPrintable());
System.out.println();
classLayout = ClassLayout.parseInstance(new int[]{});
System.out.println(classLayout.toPrintable());
System.out.println();
classLayout = ClassLayout.parseInstance(new Data());
System.out.println(classLayout.toPrintable());
System.out.println();
}
/**
* 类描述: 测试使用的对象
* <br />
* ‐XX:+UseCompressedOops 默认开启的压缩所有指针
* <br />
* ‐XX:+UseCompressedClassPointers 默认开启的压缩对象头里的类型指针Klass Pointer
* @author : XXSD
* @date : 2020/6/27 0027 上午 11:29
*/
static class Data {
//4B Klass Pointer 如果关闭压缩‐XX:‐UseCompressedClassPointers或‐XX:‐UseCompressedOops,则占用8B
int dataId;
//4B Klass Pointer 如果关闭压缩‐XX:‐UseCompressedOops,则占用8B
String dataName;
//1B
byte dataByte;
//4B Klass Pointer 如果关闭压缩‐XX:‐UseCompressedOops,则占用8B
Object dataObject;
}
}
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
//对象头占用大小
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
//占用4个字节,但是满足对其填充的原则,所以这里加了4个字节;对其填充,是JVM在底层进行的优化,存取对象的时候效率是最高,涉及到最底层存取效率,必须是8或8的倍数;
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
//总大小
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[I object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
//对象头占用大小
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363)
12 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
16 0 int [I.<elements> N/A
//总大小
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
xinyan.jolcore.jolSample$Data object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
//对象头占用大小
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 63 cc 00 f8 (01100011 11001100 00000000 11111000) (-134165405)
12 4 int Data.dataId 0
16 1 byte Data.dataByte 0
17 3 (alignment/padding gap)
20 4 java.lang.String Data.dataName null
24 4 java.lang.Object Data.dataObject null
28 4 (loss due to the next object alignment)
//总大小
Instance size: 32 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
2.2、什么是java对象的指针压缩?
- jdk1.6 update14开始,在64bit操作系统中,JVM支持指针压缩;
- jvm配置参数:UseCompressedOops,compressed压缩、oop(ordinary object pointer)对象指针;
- 启用指针压缩:XX:+UseCompressedOops(默认开启),禁止指针压缩:XX:-UseCompressedOops;
2.3、为什么要进行指针压缩?
- 在64位平台的HotSpot中使用32位指针,内存使用会多出1.5倍左右,使用较大指针在主内存和缓存之间移动数据,占用较大宽带,同时GC也会承受较大压力;
- 为了减少64位平台下内存的消耗,启用指针压缩功能;
- 在jvm中,32位地址最大支持4G内存(2的32次方),可以通过对对象指针的压缩编码、解码方式进行优化,使得jvm只用32位地址就可以支持更大的内存配置(小于等于32G);
- 堆内存小于4G时,不需要启用指针压缩,jvm会直接去除高32位地址,即使用低虚拟地址空间;
- 堆内存大于32G时,压缩指针会失效,会强制使用64位(即8字节)来对java对象寻址,这就会出现1的问题,所以堆内存不要大于32G为好;
三、对象内存分配
3.1、对象栈上分配
public User test1() {
User user = new User();
user.setId(1);
user.setName("zhuge");
//TODO 保存到数据库
return user;
}
public void test2() {
User user = new User();
user.setId(1);
user.setName("zhuge");
//TODO 保存到数据库
}
**标量与聚合量:**标量即不可被进一步分解的量,而JAVA的基本数据类型就是标量(如:int,long等基本数据类型以及reference类型等),标量的对立就是可以被进一步分解的量,而这种量称之为聚合量。而在JAVA中对象就是可以被进一步分解的聚合量。
/**
* 类描述: 栈上分配,标量替换
* <br />
* 代码调用了1亿次alloc(),如果是分配到堆上,大概需要1GB以上堆空间,如果堆空间小于该值,必然会触发GC。
* <br />
* 使用如下参数不会发生GC
* -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
* <br />
* 使用如下参数都会发生大量GC
* -Xmx15m -Xms15m -XX:-DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
* -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations
*
* @author XXSD
* @version 1.0.0
* @date 2020/6/27 0027 下午 3:37
*/
public class AllotOnStack {
public static void main(String[] values) {
long start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
alloc();
}
final long end = System.currentTimeMillis();
System.out.println(end - start);
}
private static void alloc(){
final User user = new User();
user.setId(1);
user.setName("张三");
}
static class User{
Integer id;
String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
3.2、对象在Eden区分配
- Minor GC/Young GC:指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。
- Major GC/Full GC:一般会回收老年代 ,年轻代,方法区的垃圾,Major GC的速度一般会比Minor GC的慢10倍以上。
大量的对象被分配在eden区,eden区满了后会触发minor gc,可能会有99%以上的对象成为垃圾被回收掉,剩余存活的对象会被挪到为空的那块survivor区,下一次eden区满了后又会触发minor gc,把eden区和survivor区垃圾对象回收,把剩余存活的对象一次性挪动到另外一块为空的survivor区,因为新生代的对象都是朝生夕死的,存活时间很短,所以JVM默认的8:1:1的比例是很合适的,让eden区尽量的大,survivor区够用即可,JVM默认有这个参数-XX:+UseAdaptiveSizePolicy(默认开启),会导致这个8:1:1比例自动变化,如果不想这个比例有变化可以设置参数-XX:-UseAdaptiveSizePolicy
/**
* 类描述: GC测试
* <br />
* 添加运行JVM参数: ‐XX:+PrintGCDetails
*
* @author XXSD
* @version 1.0.0
* @date 2020/6/27 0027 下午 4:55
*/
public class GcTest {
public static void main(String[] values) {
byte[] allocation1, allocation2, allocation3, allocation4, allocation5, allocation6;
allocation1 = new byte[600000 * 1024];
allocation2 = new byte[8000 * 1024];
allocation3 = new byte[1000 * 1024];
allocation4 = new byte[1000 * 1024];
allocation5 = new byte[1000 * 1024];
allocation6 = new byte[1000 * 1024];
}
}
执行结果:
Heap
PSYoungGen total 76288K, used 16251K [0x000000076b400000, 0x0000000770900000, 0x00000007c0000000)
eden space 65536K, 24% used [0x000000076b400000,0x000000076c3ded70,0x000000076f400000)
from space 10752K, 0% used [0x000000076fe80000,0x000000076fe80000,0x0000000770900000)
to space 10752K, 0% used [0x000000076f400000,0x000000076f400000,0x000000076fe80000)
ParOldGen total 775168K, used 600000K [0x00000006c1c00000, 0x00000006f1100000, 0x000000076b400000)
object space 775168K, 77% used [0x00000006c1c00000,0x00000006e65f0010,0x00000006f1100000)
Metaspace used 3213K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 348K, capacity 388K, committed 512K, reserved 1048576K
给allocation2分配内存的时候eden区内存几乎已经被分配完了,上述代码了当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC,GC期间虚拟机又发现allocation1无法存入Survior空间,所以只好把新生代的对象提前转移到老年代中去,老年代上的空间足够存放allocation1,所以不会出现Full GC。执行Minor GC后,后面分配的对象如果能够存在eden区的话,还是会在eden区分配内存。
3.3、大对象直接进入老年代
比如设置JVM参数:-XX:PretenureSizeThreshold=1000000 (单位是字节) -XX:+UseSerialGC ,再执行下上面的第一个程序会发现大对象直接进了老年代,这么做是为了避免为大对象分配内存时候因进行的复制操作而降低效率;
3.4、长期存活的对象将进入老年代
如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor空间中,并将对象年龄设为1。对象在 Survivor 中每熬过一次 MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁,CMS收集器默认6岁,不同的垃圾收集器会略微有点不同),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold 来设置。
3.5、对象动态年龄判断
3.6、老年代空间分配担保机制
如果上一步结果是小于或者之前说的参数没有设置,那么就会触发一次Full gc,对老年代和年轻代一起回收一次垃圾,如果回收完还是没有足够空间存放新的对象就会发生"OOM";当然,如果minor gc之后剩余存活的需要挪动到老年代的对象大小还是大于老年代可用空间,那么也会触发full gc,full gc完之后如果还是没有空间放minor gc之后的存活对象,则也会发生“OOM”;
四、对象内存回收
4.1、引用计数法
public class ReferenceCountingGc {
Object instance = null;
public static void main(String[] args) {
ReferenceCountingGc objA = new ReferenceCountingGc();
ReferenceCountingGc objB = new ReferenceCountingGc();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
}
}
4.2、可达性分析算法
4.3、常见引用类型
public static User user = new User();
public static SoftReference<User> user = new SoftReference<User>(new User());
public static WeakReference<User> user = new WeakReference<User>(new User());
4.3、finalize()方法最终判定对象是否存活
1. 第一次标记并进行一次筛选。
2. 第二次标记
注意:一个对象的finalize()方法只会被执行一次,也就是说通过调用finalize方法自我救命的机会就一次。
public class OOMTest {
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
int i = 0;
int j = 0;
while (true) {
list.add(new User(i++, UUID.randomUUID().toString()));
new User(j‐‐, UUID.randomUUID().toString());
}
}
}
4.4、如何判断一个类是无用的类
- 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
- 加载该类的 ClassLoader 已经被回收。
- 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。