程序计数器(Program Counter Register)是一块较小的内存空间,可以看作是当前线程所执行字节码的行号指示器,指向下一个将要执行的指令代码,由执行引擎来读取下一条指令。更确切的说,一个线程的执行,是通过字节码解释器改变当前线程的计数器的值,来获取下一条需要执行的字节码指令,从而确保线程的正确执行。
为了确保线程切换后(上下文切换)能恢复到正确的执行位置,每个线程都有一个独立的程序计数器,各个线程的计数器互不影响,独立存储。也就是说程序计数器是线程私有的内存。
如果线程执行 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是 Native 方法,计数器值为Undefined。
程序计数器不会发生内存溢出(OutOfMemoryError即OOM)问题。
————————————————
原文链接:https://blog.csdn.net/rongtaoup/article/details/89142396
方法区同java堆一样是所有线程共享的,用于存放已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码。更具体的说,**静态变量(static)+常量(final)+类信息(版本、方法、字段等)+运行时常量池。**运行时常量池是方法区的一部分。
JDK1.8使用元空间MetaSpace替代方法区,元空间并不在JVM虚拟机中,而是使用本地内存。元空间有两个参数。
这个区域常驻内存,用来存放JDK自身携带的Class对象。Interface元数据,存储的是java运行时一些环境或一些类信息,这个区域不存在垃圾回收。,关闭JVM虚拟机就会释放这个区域的内存。
1.MetaSpaceSize:元空间初始化大小,控制发生GC阈值
2.MaxMetaSpaceSize:元空间内存上限容量,防止异常占用过多物理内存
元空间什么时候会发生内存不足?
一个启动类,加载了大量的第三方jar包,Tomcat部署了太多的应用,大量动态生成反射类,不断的加载,就会出现OOM;
JDK1.8以后
常量池中存储编译器生成的各种字面量和符号引用。字面量就是Java中常量的意思。比如文本字符串,final修饰的常量等。方法引用则包括类和接口的全限定名,方法名和描述符,字段名和描述符等。
优点:常量池避免了频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
举例说明:Integer常量池、String常量池
==对于基本数据类型比较的是数值,对于引用数据类型比较的是内存地址。
public void TestIntegerCache() { public static void main(String[] args) { Integer i1 = new Integer(66); Integer i2 = new integer(66); Integer i3 = 66; Integer i4 = 66; Integer i5 = 150; Integer i6 = 150; System.out.println(i1 == i2);//false System.out.println(i3 == i4);//true System.out.println(i5 == i6);//false } }
i1 和 i2 使用 new 关键字,每new一个对象就会在堆中创建一个对象空间,并且在方法区中创建对这个空间的引用。所以两个new出来的对象所指向的引用地址是不同的,所以==为false。
i3==i4就为true,因为Integer有一个静态内部类IntegerCache,会在常量池中缓存[-128,127]的cache数组,创建i3,i4时就会调用Integer.valueOf()方法进行查看,当我们赋值的Integer类型在这个数组范围内时,就会直接将变量的引用指向这个数组,就不需要再创建新的对象了,所以i3和i4的指向对象是同一个,所以返回true。
i5==i6为false,因为当超出[-128,127]这个范围时,就会在堆中new对象,使引用直接指向堆中的对象,所以两个对象的引用地址不同,返回false。
IntegerCache 源码
private static class IntegerCache { static final int low = -128;//最小值 static final int high;//最大值 static final Integer cache[];//缓存数组 //私有化构造方法,不让别人创建它。单例模式的思想 private IntegerCache() {} //类加载的时候,执行静态代码块。作用是将-128到127之间的数缓冲在cache[]数组中 static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1];//初始化cache数组,根据最大最小值确定 int j = low; for(int k = 0; k < cache.length; k++)//遍历将数据放入cache数组中 cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } }
拆箱代码:
Integer是对象,对象不能使用+运算符的,所以需要将Integer类先拆箱为int类,再进行运算。i3也是拆箱后在进行数值比较。所以i3==i1+i2其实是值比较。
public static void main(String[] args){ Integer i1 = new Integer(4); Integer i2 = new Integer(6); Integer i3 = new Integer(10); System.out.print(i3 == i1+i2);//true }
String new是在堆中创建,=是在字符串常量池中创建。
String 是由 *final* 修饰的类,是不可以被继承的。通常有两种方式来创建对象。
//1、 String str = new String("abcd"); //2、 String str = "abcd";
第一种使用 new 创建的对象,存放在堆中。每次调用都会创建一个新的对象。
第二种先在栈上创建一个 String 类的对象引用变量 str,然后通过符号引用去字符串常量池中找有没有 “abcd”,如果没有,则将“abcd”存放到字符串常量池中,并将栈上的 str 变量引用指向常量池中的“abcd”。如果常量池中已经有“abcd”了,则不会再常量池中创建“abcd”,而是直接将 str 引用指向常量池中的“abcd”。
对于 String 类,equals 方法用于比较字符串内容是否相同; == 号用于比较内存地址是否相同,即是否指向同一个对象。通过代码验证上面理论。
首先在栈上存放变量引用 str1,然后通过符号引用去常量池中找是否有 abcd,没有,则将 abcd 存储在常量池中,然后将 str1 指向常量池的 abcd。当创建 str2 对象,去常量池中发现已经有 abcd 了,就将 str2 引用直接指向 abcd 。所以str1 == str2,指向同一个内存地址。
public static void main(String[] args){ String str1 = "abcd"; String str2 = "abcd"; System.out.print(str1 == str2);//true }
public static void main(String[] args){ String str1 = new String("abcd"); String str2 = new String("abcd"); System.out.print(str1 == str2);//false }
str1 和 str2 使用 new 创建对象,分别在堆上创建了不同的对象。两个引用指向堆中两个不同的对象,所以为 false。
关于字符串 + 号连接问题:
对于字符串常量的 + 号连接,在程序编译期,JVM就会将其优化为 + 号连接后的值。所以在编译期其字符串常量的值就确定了。
String a = "a1"; String b = "a" + 1; System.out.println((a == b)); //result = true String a = "atrue"; String b = "a" + "true"; System.out.println((a == b)); //result = true String a = "a3.4"; String b = "a" + 3.4; System.out.println((a == b)); //result = true
关于字符串引用 + 号连接问题:
对于字符串****引用*的 + 号连接问题,由于字符串引用在编译期是无法确定下来的,在程序的运行期动态分配并创建新的地址存储对象*。
public static void main(String[] args){ String str1 = "a"; String str2 = "ab"; String str3 = str1 + "b"; System.out.print(str2 == str3);//false }
对于上边代码,str3 等于 str1 引用 + 字符串常量“b”,在编译期无法确定,在运行期动态的分配并将连接后的新地址赋给 str3,所以 str2 和 str3 引用的内存地址不同,所以 str2 == str3 结果为 false
通过 jad 反编译工具,分析上述代码到底做了什么。编译指令如下:
javac Test.java:java文件编译成字节码class文件
**jad Test.class:**class文件反编译成java文件
经过 jad 反编译工具反编译代码后,代码如下
public class TestDemo{ public TestDemo() { } public static void main(String args[]) { String s = "a"; String s1 = "ab"; String s2 = (new StringBuilder()).append(s).append("b").toString(); System.out.print(s1 = s2); }}
发现 new 了一个 StringBuilder 对象,然后使用 append 方法优化了 + 操作符。new 在堆上创建对象,而 String s1=“ab”则是在常量池中创建对象,两个应用所指向的内存地址是不同的,所以 s1 == s2 结果为 false。
我们已经知道了字符串引用的 + 号连接问题,其实是在运行期间创建一个 StringBuilder 对象,使用其 append 方法将字符串连接起来。这个也是我们开发中需要注意的一个问题,就是尽量不要在 for 循环中使用 + 号来操作字符串。看下面一段代码:
public static void main(String[] args){ String s = null; for(int i = 0; i < 100; i++){ s = s + "a"; } }
在 for 循环中使用 + 连接字符串,每循环一次,就会新建 StringBuilder 对象,append 后就“抛弃”了它。如果我们在循环外创建StringBuilder 对象,然后在循环中使用 append 方法追加字符串,就可以节省 n-1 次创建和销毁对象的时间。所以在循环中连接字符串,一般使用 StringBuilder 或者 StringBuffer,而不是使用 + 号操作。
public static void main(String[] args){ StringBuilder s = new StringBuilder(); for(int i = 0; i < 100; i++){ s.append("a"); } }
**总结一句话:**当+左右都是字符串时,就可以判断字符串常量池中是否有+以后的结果,而当+左右有字符串的引用时,则会在java堆中创建新地址而让字符串引用,所以当有引用用+拼接是会获得新的地址。
使用final修饰的字符串
public static void main(String[] args){ final String str1 = "a"; String str2 = "ab"; String str3 = str1 + "b"; System.out.print(str2 == str3);//true }
final 修饰的变量是一个常量,编译期就能确定其值。所以 str1 + "b"就等同于 “a” + “b”,所以结果是 true。
String对象的intern方法。
public static void main(String[] args){ String s = "ab"; String s1 = "a"; String s2 = "b"; String s3 = s1 + s2; System.out.println(s3 == s);//false System.out.println(s3.intern() == s);//true }
通过前面学习我们知道,s1+s2 实际上在堆上 new 了一个 StringBuilder 对象,而 s 在常量池中创建对象 “ab”,所以 s3 == s 为 false。**但是 s3 调用 intern 方法,返回的是s3的内容(ab)在常量池中的地址值。**所以 s3.intern() == s 结果为 true。
原文链接:https://blog.csdn.net/rongtaoup/article/details/89142396