JVM内存模型简谈
作者:互联网
JVM内存模型
JVM的主要组成
作用:首先通过编译器将.java文件编译成.class文件,类加载器再把字节码文件加载到内存中。将其放在运行时数据区的方法区中,而字节码文件只是jvm的一套指令规范,并不能直接交由底层操作系统去执行,而是需要特定的命令解析器执行引擎将字节码文件翻译成操作系统认识的二进制形式,而这个过程需要调用其他语言的本地库接口来实现;
一句话解释:类的加载指的是将类的.class文件的二进制数据加载到内存中,将其放在运行时数据区的方法区中,然后在堆区创建一个java.lang.class对象,用来封装方法区内的数据结构;
JVM内存模型图
jvm参数设置举例
-Xss:每个线程的栈大小
-Xms:初始堆大小,默认物理内存的1/64
-Xmx:最大堆大小,默认物理内存的1/4
-Xmn:新生代大小
-XX:NewSize:设置新生代初始大小
-XX:NewRatio:默认2表示新生代占年老代的1/2,占整个堆内存的1/3。
-XX:SurvivorRatio:默认8表示一个survivor区占用1/8的Eden内存,即1/10的新生代内存。
关于元空间的JVM参数有两个:-XX:MetaspaceSize=N和 -XX:MaxMetaspaceSize=N
-XX:MaxMetaspaceSize: 设置元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存大小。
-XX:MetaspaceSize: 指定元空间触发Fullgc的初始阈值(元空间无固定初始大小), 以字节为单位,默认是21M左右,达到该值就会触发full gc进行类型卸载, 同时收集器会对该值进行调整: 如果释放了大量的空间, 就适当降低该值; 如果释放了很少的空间, 那么在不超过-XX:MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值。这个跟早期jdk版本的-XX:PermSize参数意思不一样,-XX:PermSize代表永久代的初始容量。
由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大,对于8G物理内存的机器来说,一般我会将这两个值都设置为256M。
为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?
- 字符串存在永久代中,容易出现性能问题和内存溢出
- 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出
- 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低
- 将 HotSpot 与 JRockit 合二为一
java会出现内存泄漏?
内存泄漏指的是不再使用的对象或者变量一直被占据在内存中,理论上来说,java是有GC垃圾回收机制的,但是如果大量长生命周期的对象持有短生命周期对象的引用,就可能发生
什么情况下会发生栈内存溢出?
- 栈是线程私有的,他的生命周期和方法一样,每个方法执行的时候都会创建一个栈帧,用来存储局部变量表、操作数栈、动态链接、方法出口等信息;
- 如果线程请求的栈的深度大于虚拟机运行的最大深度,将抛出Stack Overflow Error异常,一般是方法递归导致的;
- -XSS 参数去设置;
栈溢出StackOverflowError示例
// JVM设置 -Xss128k(默认1M)
public class StackOverflowTest {
static int count = 0;
static void redo() {
count++;
redo();
}
public static void main(String[] args) {
try {
redo();
} catch (Throwable t) {
t.printStackTrace();
System.out.println(count);
}
}
}
运行结果:
java.lang.StackOverflowError
at com.tuling.jvm.StackOverflowTest.redo(StackOverflowTest.java:12)
at com.tuling.jvm.StackOverflowTest.redo(StackOverflowTest.java:13)
at com.tuling.jvm.StackOverflowTest.redo(StackOverflowTest.java:13)
字符串常量池
字符串常量池位置
Jdk1.6及之前: 有永久代, 运行时常量池在永久代,运行时常量池包含字符串常量池
Jdk1.7:有永久代,但已经逐步“去永久代”,字符串常量池从永久代里的运行时常量池分离到堆里
Jdk1.8及之后: 无永久代,运行时常量池在元空间,字符串常量池里依然在堆里
三种字符串操作(Jdk1.7 及以上版本)
直接赋值字符串
String s = "zhuge"; // s指向常量池中的引用
这种方式创建的字符串对象,只会在常量池中。
因为有"zhuge"这个字面量,创建对象s的时候,JVM会先去常量池中通过 equals(key) 方法,判断是否有相同的对象
如果有,则直接返回该对象在常量池中的引用;
如果没有,则会在常量池中创建一个新对象,再返回引用。
- new String();
String s1 = new String(“zhuge”); // s1指向内存中的对象引用
这种方式会保证字符串常量池和堆中都有这个对象,没有就创建,最后返回堆内存中的对象引用。
步骤大致如下:
因为有"zhuge"这个字面量,所以会先检查字符串常量池中是否存在字符串"zhuge"
不存在,先在字符串常量池里创建一个字符串对象;再去内存中创建一个字符串对象"zhuge";
存在的话,就直接去堆内存中创建一个字符串对象"zhuge";
最后,将内存中的引用返回。
- intern方法
String s1 = new String("zhuge");
String s2 = s1.intern();
System.out.println(s1 == s2); //false
String中的intern方法是一个 native 的方法,当调用 intern方法时,如果池已经包含一个等于此String对象的字符串(用equals(oject)方法确定),则返回池中的字符串。否则,将intern返回的引用指向当前字符串 s1(jdk1.6版本需要将 s1 复制到字符串常量池里)。
举例:
//字符串常量池:"计算机"和"技术" 堆内存:str1引用的对象"计算机技术"
//堆内存中还有个StringBuilder的对象,但是会被gc回收,StringBuilder的toString方法会new String(),这个String才是真正返回的对象引用
String str2 = new StringBuilder("计算机").append("技术").toString(); //没有出现"计算机技术"字面量,所以不会在常量池里生成"计算机技术"对象
System.out.println(str2 == str2.intern()); //true
//"计算机技术" 在池中没有,但是在heap中存在,则intern时,会直接返回该heap中的引用
//字符串常量池:"ja"和"va" 堆内存:str1引用的对象"java"
//堆内存中还有个StringBuilder的对象,但是会被gc回收,StringBuilder的toString方法会new String(),这个String才是真正返回的对象引用
String str1 = new StringBuilder("ja").append("va").toString(); //没有出现"java"字面量,所以不会在常量池里生成"java"对象
System.out.println(str1 == str1.intern()); //false
//java是关键字,在JVM初始化的相关类里肯定早就放进字符串常量池了
String s1=new String("test");
System.out.println(s1==s1.intern()); //false
//"test"作为字面量,放入了池中,而new时s1指向的是heap中新生成的string对象,s1.intern()指向的是"test"字面量之前在池中生成的字符串对象
String s2=new StringBuilder("abc").toString();
System.out.println(s2==s2.intern()); //false
//同上
八种基本类型的包装类和对象池
java中基本类型的包装类的大部分都实现了常量池技术(严格来说应该叫对象池,在堆上),这些类是Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用对象池,也即对象不负责创建和管理大于127的这些类的对象。因为一般这种比较小的数用到的概率相对较大。
public class Test {
public static void main(String[] args) {
//5种整形的包装类Byte,Short,Integer,Long,Character的对象,
//在值小于127时可以使用对象池
Integer i1 = 127; //这种调用底层实际是执行的Integer.valueOf(127),里面用到了IntegerCache对象池
Integer i2 = 127;
System.out.println(i1 == i2);//输出true
//值大于127时,不会从对象池中取对象
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4);//输出false
//用new关键词新生成对象不会使用对象池
Integer i5 = new Integer(127);
Integer i6 = new Integer(127);
System.out.println(i5 == i6);//输出false
//Boolean类也实现了对象池技术
Boolean bool1 = true;
Boolean bool2 = true;
System.out.println(bool1 == bool2);//输出true
//浮点类型的包装类没有实现对象池技术
Double d1 = 1.0;
Double d2 = 1.0;
System.out.println(d1 == d2);//输出false
}
}
标签:java,常量,对象,简谈,内存,JVM,字符串,String 来源: https://blog.csdn.net/weixin_42261037/article/details/120432139