一、堆区:
1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
栈区:
1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中
2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
方法区:
1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
实际情况推荐你用jstat和jmap查看分配情况。不同虚拟机在定义和分配上有些差异。但有一点,字符串常量的对象最新还是在堆创建,跟方法区在一起的只是一些基本类型数据包括引用地址
jvm的基本结构:
jvm由三个主要的子系统构成:
1:类加载系统。2:运行时数据区(内存结构)。3:执行引擎
内存结构图:
栈、本地方法栈、程序计数器是线程私有的,随着线程的产生而产生。
public class Demo {
public int math(){
int a = 1;
int b = 2;
int c = a + b;
return c;
}
public static void main(String[] args) {
Demo de = new Demo();
de.math();
}
}
栈:存放局部变量
栈里面存放的都是栈帧,栈帧可以理解为一个方法所占的区域。区域中分为局部变量表、操作数栈、动态链接、方法出口。
局部变量表:存放的是我们定义的局部变量。
操作数栈:用来做操作的一块内存区域。
程序计数器:就是一个指针,用来存储指向下一条执行代码的地址。
方法出口:记录了调用方法的地址,所以math能够返回到main方法。
动态链接:暂且理解为栈指向堆内存的指针。
方法区:静态变量+常量+类信息(构造方法/接口定义)+运行时常量池都存在方法区中
元空间使用的是本地的物理内存。以前是放在堆里面的。
private Static User user = new User();user引用放在方法区,new出来的对象放在堆里面
元空间--堆外内存,以前叫永久代。
堆:eden:伊甸区,from suvivor + to suvivor 幸存者区,元空间。
eden:伊甸区
from suvivor:
to suvivor:
垃圾收集算法:
标记-清除算法:
它是最基础的的收集算法,这个算法分为两个阶段,标记和清除。首先标记处所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它有两个不足的地方:
1:效率问题,标记和清除的两个过程的效率不高。
2:空间问题,标记清除之后会产生大量不连续的碎片。
复制算法:使用与新生代,因为每次都会浪费一半的内存,老年代里面的对象都比较大,如果在浪费一半内存会moner gc。
标记-整理算法:先移动在清理。适合老年代
日志收集:
-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
-Xloggc:../logs/gc.log 日志文件的输出路径
-xx:+PrintGCDetails -xx:+PrintGCDateStamps -xloggc ./gc.log
使用gceasy图形化界面进行分析。
啥是oom :https://blog.csdn.net/paopaoandguoer/article/details/76206586
垃圾收集器:
1:serial 串行收集器 年轻代:复制算法,老年代:标记整理算法
2: paraller 并行收集器 年轻代:复制算法,老年代:标记整理算法
3: parNew 并行收集器 年轻代:复制算法,老年代:标记整理算法
4: cms 并行收集器 只适用老年代:标记-清理算法
初始标记,并发标记,重新标记,并发清理,并发重置
5: g1 复制算法
g1收集器会将堆分为很多个独立的区域-->region,region最多有2048个,region的大小就是堆内存/2048=region,这个大小可以设置,-XX:G1HeapRegionSize 2M,
region主要分为4种类型,Eden -- 伊甸区,Survivor,Old -- 老年代,Humongous -- 大对象
Edon区对堆的占比就是5%,但是Survivor的比例还是8:1:1 Eden:s0:s1,主要的区别就是对大对象的收集,当一个对象的大小超过了Humongous的50%,就是直接发到Humongous区域,当超过了多个Hm就是放入到连续几个hm,这样就避免了大对象进 入老年代,引起频繁的FullGC。
发生一次GC运行的几个步骤:
1:初始标记(init mark,swt) 暂停所有线程,并记录下GCRoot能直接引用的对象
2:并发标记(conc mark) 开始寻找GCROOT间接引用的对象---可达性分析。用户线程和垃圾回收线程一起运行,就可能造成刚才标记的对象发生变化
3:最终标记(remark,swt)停止用户线程,重新标记因为并发标记影响的的对象。
4:筛选回收(clean up,swt) 根据用户设置的停顿时间来筛选需要最需要回收的Region,回收算法主要用的是复制算法, 将一个region中的存活对象复制到另一个region中, 这种不会像CMS那样 回收完因为有很多内存碎片还需要整理一次, G1采用复制算法回收几乎不会有太多内存碎片。
GCRoot可达性分析:https://blog.csdn.net/weixin_41910694/article/details/90706652
垃圾回收实战:
ThreadLocal详解:
强引用:A a = new A();直接引用,在gc的时候不会回收。
软引用:SoftReference<byte[]> m = new SoftReference<>(new byte[1024 * 1024 *10]); gc的时候内存不够的时候被回收。
弱引用: WeakReference<Object> wr = new WeakReference<Object>(new Object()); gc的时候不管内存够不够都会被回收。
虚引用:https://www.cnblogs.com/mfrank/p/9837070.html 当一个对象和GCROOT对象直接存在一个虚引用,这个对象就叫 虚可达,这个对象在垃圾回收的时候,gc会先判断如果有虚引用就会把它放到一个引用队列里面。
threadLocal的内存泄露:
threadLocal介绍:ThreadLocal是JDK提供的一个保证线程安全的工具类,代表一个线程的局部变量。把数据放在ThreadLocal中,每个线程只能操作自己的数据。实现了数据的隔离性,底层用的map,key是当前线程。
threadLocal造成的内存泄露:底层是一个threadLocalMap key是weakRefence弱引用,gc的时候key会回收,value不会回收,所以用调用remove。
什么是内存泄漏:当一个程序申请内存之后,无法去释放这个内存,多次之后内存空间占用的越来越大,就会造成内存溢出,比如说一个对象申请内存之后,一直被GCROOT对象强引用着,jvm就不会去回收这个对象。
三、线程栈
栈(JVM Stack)存放主要是栈帧( 局部变量表, 操作数栈 , 动态链接 , 方法出口信息 )的地方。注意区分栈和栈帧:栈里包含栈帧。
与线程栈相关的内存异常有两个:
a)、StackOverflowError(方法调用层次太深,内存不够新建栈帧)
b)、OutOfMemoryError(线程太多,内存不够新建线程)
1、java.lang.StackOverflowError
栈溢出抛出java.lang.StackOverflowError错误,出现此种情况是因为方法运行的时候,请求新建栈帧时,
栈所剩空间小于战帧所需空间。
例如,通过递归调用方法,不停的产生栈帧,一直把栈空间堆满,直到抛出异常 :
jvm调优
使用jstat -gc 进程号,
使用jstat -gc 12345 1000 10,每隔1000毫秒执行以下这个命令,共执行10次。