JVM的构成
jdk1.8之前
jdk1.8之后
最终PermGen的方法区移至 Metaspace;字符串常量池移至 Java Heap
程序计数器
普通Java方法和由其他语言实现的native方法。如果当前执行的是普通Java方法,则程序计数器记录的是虚拟机字节码指令的地址。如果当前执行的是native方法,则计数器的值为空(Undefined)。
本地方法栈
Java虚拟机栈是为了Java方法服务的,而本地方法栈是为了native方法服务的
栈
- 局部变量表
- 用于存放方法参数和方法内部定义的局部变量。还有this指针
- 操作数栈
- 动态连接(对象引用?)
- 字节码文件中有很多符号引用。这些符号引用一部分会在类加载的解析阶段或者第一次使用的时候转化为直接引用,这种转化称为静态解析。另一部分会在运行期间转化为直接引用,这部分称为动态连接
- 返回地址
栈溢出,其他线程是否继续工作
某团面试题:JVM 堆内存溢出后,其他线程是否可继续工作?_Java托尼的博客-CSDN博客_jvm内存溢出还能工作吗
堆
对象,GC算法都是针对堆的
类加载
对象头
方法区(永久代)&元空间(Metaspace)
Java虚拟机规范将方法区描述为堆的一个逻辑部分,但是它却有个别名叫做Non-Heap(非堆),目的就是和Java堆区分开来。(永久代/方法区也属于GC Heap的一部分)
java8 之前 方法区就是永久代Permanent Generation。由GC管理。
java8之后,变为Metaspace,使用native memory 不受GC管理。原来方法区中的常量池(字符串常量池)继续属于堆管理。
- 方法区(method area) 只是JVM规范中定义的一个概念,用于存储类信息、常量池、静态变量、JIT编译后的代码等数据,具体放在哪里,不同的实现可以放在不同的地方。
- 永久代是Hotspot虚拟机特有的概念,是方法区的一种实现,别的JVM都没有这个东西。
方法区的Class信息,又称为永久代,是否属于Java堆? - 知乎
MetaSpace是 堆外内存( Native Memory) 一部分。(NIO堆外内存:DirectByteBuffer分配)
- 合并HotSpot和JRockit的代码
- 通常使用PermSize和MaxPermSize设置永久代的大小就决定了永久代的上限
- 设置太小会内存溢出或频繁fullGC
- 设置太大会内存浪费
元空间在本地内存,不受限制。
Metaspace中存放了什么东西??方法区存放了什么东西?
- Class对象和instanceKlass互相引用。
- 对象头里面的指针—>方法区Klass-->堆Class对象
方法区:
类信息、常量、静态变量、即时编译器编译后的代码等数据
运行时常量池在Metaspace中。
Java7中永久代中存储的部分数据已经开始转移到Java Heap或Native Memory中了。比如,
- 符号引用(Symbols)转移到了Native Memory;?
- 字符串常量池(interned strings)转移到了Java Heap;
- 类的静态变量(class statics)转移到了Java Heap。
什么是符号引用?
Metaspace参数
metaspace在 native memory(本地内存)中,也触发gc。
-XX:MetaspaceSize 达到会触发gc
-XX:MaxMetaspaceSize,可以为class metadata分配的最大空间。默认是没有限制的。
Metaspace回收条件【三个条件同时满足】
- 该类所有的实例都已经被回收;【没有对象】
- 加载该类的ClassLoader已经被回收;【没有classloader】
- 该类对应的java.lang.Class对象没有任何地方被引用。【没有引用class对象】
堆外内存
优点:
能够在一定程度上减少垃圾回收对应用程序造成的影响。
缺点:
堆外内存的缺点就是内存难以控制
作为JAVA开发者我们经常用java.nio.DirectByteBuffer对象进行堆外内存的管理和使用堆外内存回收【其实也和gc有关】
Direct ByteBuffer分配出去的内存其实也是由GC负责回收的,而不像Unsafe是完全自行管理的,Hotspot在GC时会扫描Direct ByteBuffer对象是否有引用,如没有则同时也会回收其占用的堆外内存。
- JAVA堆外内存 - moonandstar08 - 博客园
ButeBuffer.allocateDirect(100Mb)
分配的direct memory,java代码可直接访问,不用切换内核状态使用系统调用。
Java直接内存对象的分配和回收是通过unsafe方法来处理的(不是gc回收)【借助了虚引用】
因为虚引用也是由于gc回收了虚引用对象,才触发的unsafe.freeMemory。所以,直接内存回收也和gc时间有关系
[黑马程序员JVM完整教程,全网超高评价,全程干货不拖沓哔哩哔哩 (゜-゜)つロ 干杯~-bilibili](https://www.bilibili.com/video/BV1yE411Z7AP?p=46)
堆外内存回收原理
Class文件常量池、运行时常量池、字符串常量池
Class常量池
class字节码文件结构中的 一个常量池(Constant Pool Table)
class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是 常量池(constant pool table) ,用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References);
并不是所有的字面量都会存储在类文件常量池中,比如对于方法内(注意是方法)整数字面量,如果值在-32768~32767(short.max )之间则会被直接嵌入JVM指令中去,不会保存在常量池中。
有常量池字面量那还要 IntegerCache干嘛?
Integer作为一个包装类和运行时常量池只能说毫不相干吧。
Integer通过IntegerCache来实现-128~127之间的缓存。
JVM是为每个类都创建了一个运行时常量池吗?运行时常量池中的内容都包括哪些? - 知乎
运行时常量池(1.6方法区,1.7Metaspace)
运行时常量池存在于内存中,也就是class常量池被加载到内存之后的版本(将符号引用变为直接引用?)
int超过short才放入常量池,不然和字节码耦合在一起。【注意区分Integer的缓存池】
运行时常量池还在Metaspace中么?
在
在jdk1.6及之前,字符串常量池是属于运行时常量池。在jdk1.7 ,字符串常量池从方法区中被单独拿到堆中字符串常量池StringTable(1.6方法区,1.7堆)
1)使用sun.jvm.hotspot.memory.StringTable类实现,extends Hashtable,使用线程安全的哈希表,在每个HotSpot VM实例中只有一份,被所有类共享。
2)JDK1.6以及之前:字符串常量池放在方法区中,StringTable固定1009个,当字符串比较多的时候,会hash冲突,导致链表过长。
3)JDK1.7:字符串常量池移至堆中。StringTable长度可以不固定,使用JVM参数-XX:StringTableSize=66666设置。
4)存放比较:JDK1.6中存放的都是字符串常量,JDK1.7中可以存放(位于堆中字符串对象的)引用,如使用String.intern()方法。字符串常量池
String s = new String(“a”) + new String(“b”)
“a” “b” 放入字符串常量池
- new String(“a”) new String(“b”) new String(“ab”) 对象放在堆中
String a = “a” + “b” 【编译器优化为”ab”】
- “a” “b” “ab”放入字符串常量池
String.intern()
问:下面程序的输出是什么?
public static void main(String[] args) {
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
}
答案:
jdk1.6 : false false
jdk1.6+: false true
String s = new Stirng(“a”);
s.intern();
- JDK6:当调用intern()方法时,
- 如果字符串常量池先前已创建出该字符串对象,则返回池中的该字符串的引用。
- 否则,将此字符串对象复制到字符串常量池中,并且返回该字符串的引用。
- JDK6+:当调用intern()方法时,
- 如果字符串常量池先前已创建出该字符串对象,则返回池中的该字符串的引用。
- 否则,如果该字符串对象已经存在于Java堆中,则将堆中此对象的引用添加到字符串常量池中,并且返回该引用;如果堆中不存在,则在池中创建该字符串并返回其引用。
注:在JDK1.6的时候,字符串常量池是存放在Perm Space中的(Perm Space和堆是相隔而开的),在1.6+的时候,移到了堆内存中
jdk6+ 和 jdk6的区别就是当s已经在堆中创建了,当常量池中没有字符串时,intern()方法不会再在运行时常量池中创建新的字符串对象,而是直接借用s对象的引用。【只能“池”引用堆,不能堆引用池】
也就是1.6,字符串常量和对象永远不相等。
public static void main(String[] args) {
//步骤解析,
// 1. 先根据"1"在常量池中创建String(1)字符串对象,
// 2. 再根据new String在堆中创建String(1)。
// 创建了两个对象,一个在堆中,一个在常量池中(虽然java6之后常量池也在堆中)。
String s = new String("1");
//intern 如果常量池中没有s则放s到常量池,返回s引用。如果有则返回常量池中对象的引用。
String s_pool = s.intern();
//“1”回去常量池中取
String s2_pool = "1";
System.out.println(s == s2_pool); //一个在堆中,一个在常量池中,false
System.out.println(s_pool == s2_pool); //两个都是池中对象,true
//往池中放“1”
String s3 = new String("1") + new String("1");
//因为池中没有“11”,往池中放“11”->s3的引用。从池中获取"11"对象就返回堆中11的对象。
String s3_pool = s3.intern();
//获取池中11
String s4_pool = "11";
//(1.6之后)池中堆中都是一个对象
System.out.println(s3 == s4_pool); //true
System.out.println(s3_pool == s3); //true
}
StringTable性能调优
Class对象放在堆中还是元数据区?
对象都在堆中,注意Class对象和元信息的区别。
JDK 1.8 :Class对象&静态变量 都位于堆(Heap),
- 且static 成员变量位于 Class对象内。
方法区(method area)只是JVM规范中定义的一个概念,用于存储类信息、常量池、静态变量、JIT编译后的代码等数据,具体放在哪里,不同的实现可以放在不同的地方
Static变量放在哪?
Oracle JDK7 / OpenJDK 7的HotSpot VM是把Symbol的存储从PermGen移动到了native memory,并且把静态变量从instanceKlass末尾(位于PermGen内)移动到了java.lang.Class对象的末尾(位于普通Java heap内)。【参考上图】
Static变量赋值是在哪个阶段?
- 普通static变量:分配空间在准备阶段,赋值在初始化阶段
- final static:static+final修饰的String类型或者基本类型常量,JVM规范是在初始化阶段赋值,但是HotSpot VM直接在准备阶段就赋值了。
- final static = new Object():赋值在初始化阶段
你知道Java中final和static修饰的变量是在什么时候赋值的吗?_CoderW的博客-CSDN博客
静态代码块只能访问之前的static变量
区别【类的初始化&对象的初始化】
答案:1a2b2b
static代码块在类初始化阶段执行
内存分配并发问题TLAB
- cas,失败重试
- TLAB,thread local allocation buffer,预先按线程分配