JVM方法区&堆&栈
方法区
运行时数据的存储区域,是可以进行线程资源共享。
元空间与永久代
方法区是用来存储:类信息,常量,字符串、静态变量、符号引用、方法代码。。。。。。
永久代和元空间都是对方法区的落地实现。
元空间 | 永久代 | |
---|---|---|
时代 | JDK1.8开始 | JDK1.7及之前 |
位置 | 内存中 | JVM中堆空间 |
空间 | 逻辑存在,实际在内存中 | 占用jvm空间 |
错误 | OutOfMemoryError:PermGen | OutOfMemoryError:PermGen |
栈(FILO 先进后出)
用来管理程序运行,程序的是指就是一个栈。
对象的引用才在栈中,实际的引用对象存在堆中,用栈的引用只想堆中。
栈的优势:存取数据快,不能数据共享
当栈满了在进行压栈就会报栈溢出异常(StackOverFlow)
栈存的是对象的引用,我们一直递归new对象就会出现异常。
public void a(){
a();
}
就会报出栈满了的异常。
栈帧
用来描述栈里面的各个数据的“样子”。
栈针里面存储:调用方法、参数、本地变量、父帧、子帧。。。。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QiU4ZBC2-1584066711004)(image\image-20200313011523106.png)]
栈里的引用对象指向堆中的实际对象,实际对象包括方法区的对象类型数据。
jvm类型
- SUN 公司 HotSpot
- BEA 公司 JRockit
- IBM 公司 J9VM
堆
Java7之前:
Heap 堆,一个JVM实例中只存在一个堆,堆的内存大小是可以调节的。
可以存的内容:类、方法、常量、保存了类型引用的真实信息;
结构:
- 新生区:Young (Eden-s0-s1)
- 养老区:Old Tenure
- 永久区:Perm
堆内存在逻辑上分为三个部分:新生、养老、永久(JDK1.8以后,叫元空间)
物理上只有 新生、养老;元空间在本地内存中,不在JVM中!
GC 垃圾回收主要是在 新生区和养老区,又分为 普通的GC 和 Full GC,如果堆满了,就会爆出 OutOfMemory;
新生区
新生区是和用户交互最多的,所有的对象都是在新生区诞生。
新生区的结构:伊甸园区(Eden)、幸存0区(s0)、幸存1区(s1)
新生区的GC:fullGC和普通GC(轻GC)
养老区
15次都幸存下来的对象进入养老区,养老区满了之后,触发 Full GC
默认是15次,可以修改!
永久区(Perm)
放一些 JDK 自身携带的 Class、Interface的元数据;
几乎不会被垃圾回收的;
OutOfMemoryError:PermGen
在项目启动的时候永久代不够用了?加载大量的第三方包!
JDK1.6之前: 有永久代、常量池在方法区;
JDK1.7:有永久代、但是开始尝试去永久代,常量池在堆中;
JDK1.8 之后:永久代没有了,取而代之的是元空间;常量池在元空间中;
过程:
- 所有的对象都是产生于伊甸园区,直到伊甸园区满了,调用轻GC对伊甸园区进行垃圾回收。剩下很少一部分(1%),进入幸存区。
- 当幸存区也满了的时候,触发一次FullGC,伊甸园区,幸存区都进行清理。
- 如果有对象可以活过一定数量(默认15次),则认为这个对象会经常使用,就会放入永久区(1.8以后叫元空间)
- 当元空间,幸存区,伊甸园区都满了,则会报出OOM错误
Sun HotSpot 虚拟机中,内存管理(分代管理机制:不同的区域使用不同的算法!)
内存调优
产看自己jvm的内存使用(作者电脑内存8G)
public static void main(String[] args) {
// 剩余剩余大小
long freeMemory = Runtime.getRuntime().freeMemory();
// jvm默认的内存大小
long totalMemory = Runtime.getRuntime().totalMemory();
// jvm最大内存大小
long maxMemory = Runtime.getRuntime().maxMemory();
//生成一个1MB的字节数组
byte[] b = new byte[1024*1024];
long totalMemory2 = Runtime.getRuntime().totalMemory();
long maxMemory2 = Runtime.getRuntime().maxMemory();
long freeMemory2 = Runtime.getRuntime().freeMemory();
System.out.println(totalMemory2 - totalMemory);
System.out.println(maxMemory2 - maxMemory);
System.out.println("使用了:"+(freeMemory - freeMemory2)/1024/(double)1024+"MB");
System.out.println("===================");
System.out.println("jvm默认的内存大小:"+totalMemory/1024/(double)1024+"MB");
System.out.println("jvm最大内存大小"+maxMemory/1024/(double)1024+"MB");
System.out.println("剩余剩余大小"+freeMemory/1024/(double)1024+"MB");
}
结果是:
0
0
使用了:1.0MB
===================
jvm默认的内存大小:121.0MB
jvm最大内存大小1787.0MB
剩余剩余大小117.125MB
我们发现totalMemory和maxMemory没有变化,生成一个字节数组只有freeMemory变化了。
其中maxMemory差不多是内存的1/4,totalMemory差不多为内存的1/64。都是在程序启动前定好的。
我们可以通过一下指令进行改变:
// -Xmx: 最大分配内存; 1/4 --->maxMemory -Xmx: 100m
// -Xms: 初始分配的内存大小; 1/64 --->totalMemory
-Xmx256m -Xms64M (M 不区分大小写) 最大内存256 初始内存64
结果是:
jvm默认的内存大小:61.5MB
jvm最大内存大小228.0MB
剩余剩余大小58.20703125MB
我们再打印一下垃圾回收的信息
PSYoungGen total 18944K, used 4723K [0x00000000fd580000,
....
ParOldGen total 44032K, used 0K [0x00000000f8000000
object space 44032K, 0% used [0x00000000f8000000,0x00000000f8000000,0x00000000fab00000)
Metaspace used 3283K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 353K, capacity 388K, committed 512K, reserved 1048576K
PSYoungGen+ParOldGen=恰好等于61.5MB。即养老区和年轻代加起来刚好等于默认大小。
就证明元空间并不在jvm内
我们看看刚才说的OOM,当默认满了就会造成堆溢出
/**
* -Xmx8m -Xms4m -XX:+PrintGCDetails
*/
public class Demo05 {
public static void main(String[] args) {
String str = " hello world";
while(true){
str += str + new Random().nextInt(Integer.parseInt("1999999999"));
}
}
}
一开始主要是普通GC,直到年轻代满了进行了一次清理,后面养老区满了进行了一次fullGC。直到最后一次:年轻代,养老区再也没有空间了,产生了OOM异常。
堆内存快照抓取
查看内存使用的情况。
我们可以用jdk自带的工具jconsole
E:\java1.8\bin\jconsole
测试代码:
public static void main(String[] args) throws InterruptedException {
int num = 0;
while(true){
num++;
TimeUnit.SECONDS.sleep(1);
num--;
}
}
还可以用idea的插件Jprofiler插件