jre与jdk的区别
jre, java runtime enviriment,运行java程序的环境(普通用户)
jdk, java develop kite, java开发环境。
java内存布局
总体:程序计数器,虚拟机栈,本地方法栈,堆,方法区(包含运行时常量池),直接内存。
- 程序计数器
唯一没有内存逸出的区域,记录程序的分支跳转等信息。占用内存特别小,可忽略不计。当前现成的代码执行行号记录,线程上下文切换就是通过计数器回复现场继续执行的,各线程独立存储,互不影响。只记录java方法的计数,native方法程序计数器为空。
- 虚拟机栈
虚拟机栈主要包含java方法执行时哥哥方法的栈贞。栈贞里面主要包含局部变量表,操作数栈,动态连接,方法出口等信息。
平时大家说的栈,其实就是局部变量表,java八大基本类型以及引用类型数据都存在这里,局部变量表以slot为单位存储,一个方法局部变量表需要多少个slot在编译期间就能确定咯,循环类的块级结构slot会复用~~~
此外,long和double会占用两个slot,在java并发编程实战中有提到这样会可能导致并发情况下读double和long数据高地位错乱问题,其实本书作者建议知道就行,忘记这回事吧,不会出现的。。。。
栈这个区域,有栈深度,一直递归或者方法调用深度确实太深,会stackoverflowexception, 栈将内存不足,会报ooom异常。传说一般服务器1000-2000的栈深度没啥问题,对了斐波那契数列f(n)=f(n-1)+f(n-2);用最原始的地鬼解,不用map缓存或者改为非递归实现,这栈深度一般扛不住啊、、
-Xss 128k 一个栈的大小
虽然栈定义了两种情况下的异常,但是到底是栈深度太深了导致栈空间不足还是栈的局部变量表太大了导致的也说不清楚,书中作者做了实验,设置栈空间为128k,第一种方法是直接无限递归调用自己,大概可以调用2000次,然后跑stackoverflow异常,然后他在方法里定义大量的局部变量,使得一个栈就非常大,递归调用800次后也报了stackoverflow异常。。。。。。
tips:栈中有可能直接分配对象数据的,即java6之后的栈上分配优化,一些只会在当前线程中使用的变量直接在栈上分配内存,就不需要gc回收了,随着栈帧出栈就全部清除了。
- 本地方法栈
sun hotpot把本地方法栈和虚拟机栈合在一起的。。。
- 堆
以前 所有的对象,数组都一定是分配在堆上,栈上只存引用和基本类型。而后面虚拟机的优化技术出现,,栈上分配,标量替换这两个逼玩意儿有可能一些对象不会放在堆里啦。
java的gc主要就是针对这个区域。
堆逸出模拟特别简单,一直创建新对象就好了。。。
jvm调节(-Xmx -Xms)
- 方法区
这个模块在java8中已经被metaspace替代,一部分数据放到了堆中,类信息等放在metaspace中。
方法区中主要存虚拟机加载的类的信息,及时编辑加载的的类信息等。它还包含运行时常量池。
方法去从jdk1.6到1.7到1.8慢慢被完全取代为metasqpace,在jdk1.7中,字面量和类的静态变量都转移到了堆,符号引用转移到了本地堆,方法区中只有加载的类的基本信息了。到了jdk1.8,字面量和静态变量等信息都全部转移到了java堆中,类的基本信息全部转移到metaspace,方法区被彻底移除。java堆相比之前大了不少。所以以下代码:
public class StringOomMock {
static String base = "string";
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i=0;i< Integer.MAX_VALUE;i++){
String str = base + base;
base = str;
list.add(str.intern()); // 放进常量池
}
}
}
在就java6中会报OOM:Pergen Space
, java7中会报OOM:java heap space
, java8中会报OOM:java heap space
。
移除方法区的主要原因有:1.方法区经常包内存溢出,放到本地内存中,就只受本地内存大小限制了,相对来说不会那么容易就溢出。2.hotspot虚拟机的方法区是比较特别的,其他的虚拟机一般都没有持久代这一说,为了方便融合,必须去掉方法区。
如果Metaspace的空间占用达到了设定的最大值,那么就会触发GC来收集死亡对象和类的加载器。根据JDK 8的特性,G1和CMS都会很好地收集Metaspace区(一般都伴随着Full GC)。 为了减少垃圾回收的频率及时间,控制吞吐量,对Metaspace进行适当的监控和调优是非常有必要的。
- 运行时常量池
在java6及之前,属于方法区,之后被划分到了java堆中。
主要存放class文件常量池中的字面量和符号引用。
* class常量池存的是,常量的字面量,符号引用。。。
* 运行时常量池会在类加载之后,对字面量和符号引用进行解析,然后把值存入运行时常量池
* 运行时常量池在运行期间也是可以动态再加入常量的,比如说 String.intern()方法
所以,各个方法中的字符串,类变量里的static final修饰的int,String,float等基本类型也都属于常量。但是数值类常量也没有必要写到常量池中,因为它本身就是一个跟指针一样大小的东西,装入常量池,再用一个指针读出,再写入目标变量,与直接把值写入目标变量相比,消耗大,结果一样,所以没有意义。
看以下代码:
1 String s1 = "Hello";
2 String s2 = "Hello";
3 String s3 = "Hel" + "lo";
4 String s4 = "Hel" + new String("lo");
5 String s5 = new String("Hello");
6 String s6 = s5.intern();
7 String s7 = "H";
8 String s8 = "ello";
9 String s9 = s7 + s8;
10
11 System.out.println(s1 == s2); // true,在class编译时,s1和s2都是已知的固定字符串,编译器会直接把"Hello"放入class常量池中,运行时将其放入运行时常量池,此时s1和s2都同时指向同一个常量池中的字符串。
12 System.out.println(s1 == s3); // true, s3将两个已知的固定字符串进行+操作,编译器在编译期间会进行优化直接当做`Hello`处理,所以结果和 s1==s2一样.
13 System.out.println(s1 == s4); // false, s4中`new String("lo")`这个操作编译器不会进行优化,所以s4的具体指需要等到运行期间动态在堆中生成字符串`lo`后再进行拼接,而运行时形成的字符串如果没有手动`intern`将不会自动进入运行时常量池,所以不会像等
14 System.out.println(s1 == s9); // false, 这里虽然s7和s8在编译期间其实已经确定了值,但是在做拼接时,因为s7和s8是变量,编译器目前没有如此智能,所以会等到运行时再作拼接,所以不相等。
15 System.out.println(s4 == s5); // false,不多解释,两个值都是在运行时分配的
16 System.out.println(s1 == s6); // true,手动intern到常量池,肯定相等。
tips:
运行时常量池中的常量,基本来源于各个class文件中的常量池。
程序运行时,除非手动向常量池中添加常量(比如调用intern方法),否则jvm不会自动添加常量到常量池
- 直接内存
主要是 nio使用,具体还没有去研究。。。
-XX;MaxDirectoryMemorySize
java对象访问
- 句柄方式,多一步寻址,指针变了只用改变句柄。
- 直接指针,快,改变了地址不灵活。目前sunhotpot采用、、、