前言
各种知识多而且容易遗忘,还不容易复习。最好的方法当然是自己给自己提问,不断补缺查漏,缺什么补什么。本文将各类知识归类,并将全文知识点浓缩在自问自查中,并且都写好目录,自问自查时可以随时跳转过去,方便大家系统的学习复习知识。 水平有限,有错误敬请指正食用方法
自问自查—阅读原文—自问自查–阅读原文…
无限循环
自查自问
1. JDK,JRE,JVM 关系
2. 类加载过程 双亲委派 对象创建过程
3. 垃圾回收
4. 回收算法
5. 反射 反射创建对象
6. 垃圾回收器
7. JVM和JMM
8. 1..7,1.8方法区的区别
9. java 再不同环境下面运行的原理
10. OOM和内存泄漏
11. java对象头
12. young gc和full gc触发条件
文章目录
JDK,JVM
JDK? java Development Toolkit (java开发工具包)
JRE?
JVM?
JDK中包含JRE,在JDK的安装目录下有一个名为jre的目录,里面有两个文件夹bin和lib,在这里
可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib和起来就称为jre。
javac在 通常在C:\Program Files\Java\jdk1.6.0_10\jre\lib\rt.jar下 编译
JDK是整个JAVA的核心,包括了Java运行环境JRE(Java Runtime Envirnment)、一堆Java工具
(javac/java/jdb等)和Java基础的类库(即Java API 包括rt.jar)。
JRE是运行环境 包含了Java virtual machine(JVM),runtime class libraries和Java application
launcher,这些是运行Java程序的必要组件。
JVM是java虚拟机所有的java程序会首先被编译为.class的类文件,这种类文件可以在虚拟机上执行
只有JVM还不能成class的执行,因为在解释class的时候JVM需要调用解释所需要的类库lib,而
jre包含lib类库。
加载过程
加载
1、通过一个类的全限定名来获取其定义的二进制字节流。 //全限定名有绝对路径的意思
2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。
连接过程
1>.验证
是否有正确的内部结构(构造器,方法,成员变量等等),并和其他类协调一致
2>.准备
负责为类的静态成员(包括静态成员变量和静态方法)分配内存(这些数据被放在方法区的数据共享区中,还会给这些变量做一个标记,即这些变量属于哪个类),并设置默认初始化值
3>.解析
将类的二进制数据中的符号引用替换为直接引用 比如调用类方法就是直接引用
https://www.cnblogs.com/shinubi/articles/6116993.html
下面我们解释一下符号引用和直接引用的概念:
符号引用就是加载到方法区的一些字符串,他们有一定的规范,直接引用就是栈里面的引用指向堆里的对象。
初始化过程
初始化阶段是执行类构造器方法的过程。方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证方法执行之前,父类的方法已经执行完毕。p.s: 如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成()方法。
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
使用双亲委派模型来组织类加载器之间的关系,有一个很明显的好处,就是Java类随着它的类加载器(说白了,就是它所在的目录)一起具备了一种带有优先级的层次关系,这对于保证Java程序的稳定运作很重要。例如,类java.lang.Object类存放在JDK\jre\lib下的rt.jar之中,因此无论是哪个类加载器要加载此类,最终都会委派给启动类加载器进行加载,这边保证了Object类在程序中的各种类加载器中都是同一个类。
init是对象构造器方法
自定义类加载器:
垃圾回收
老年代 : 三分之二的堆空间
年轻代 : 三分之一的堆空间
eden区: 8/10 的年轻代空间
survivor0 : 1/10 的年轻代空间
survivor1 : 1/10 的年轻代空间
一般情况下,新生代中的对象大多生命周期很短,也就是说当进行垃圾收集时,大部分对象都是垃圾,只有一小部分对象会存活下来,所以只要保 留一小部分内存保存存活下来的对象就行了。
强引用回收: obj作为句柄保持着对对象的引用,如果当前线程执行完毕,虚拟机栈被回收,栈中引用的obj句柄被回收,对象没有句柄保持引用,也会被回收
可达性分析: https://www.jianshu.com/p/8f5fa8288d9b
GC管理的主要区域是Java堆,一般情况下只针对堆进行垃圾回收。方法区、栈和本地方法区不被GC所管理,因而选择这些区域内的对象作为GC roots,被GC roots引用的对象不被GC回收。
在Java语言里,可作为GC Roots对象的包括如下几种:
https://www.jb51.net/article/162593.htm 本地变量表
a.虚拟机栈(栈桢中的本地变量表)中的引用的对象
b.方法区中的类静态属性引用的对象
c.方法区中的常量引用的对象
d.本地方法栈中JNI的引用的对象
JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植。
Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。我们需要注意的一点就是,通过用户自定义的类加载器加载的类,除非相应的java.lang.Class实例以其它的某种(或多种)方式成为roots,否则它们并不是roots。
https://blog.csdn.net/bolg_hero/article/details/79344745
https://mp.weixin.qq.com/s/8vXENzg580R7F2iNjSdHFw
回收算法
https://blog.csdn.net/linhu007/article/details/48897597?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
使用复制清除算法(Copinng算法),原因是年轻代每次GC都要回收大部分对象。新生代里面分成一份较大的Eden空间和两份较小的Survivor空间。每次只使用Eden和其中一块Survivor空间,然后垃圾回收的时候,把存活对象放到未使用的Survivor空间中,清空Eden和刚才使用过的Survivor空间。 新生代通常占JVM堆内存的1/3,分为Eden、S0、S1,比例默认为8:1:1
反射
在运行时 通过通过Class信息获取People类的信息,比如属性,方法,构造函数等等
反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加 载进来。
Java中每个类都有一个相对应的Class类(堆中),JVM加载类时会创建一个该类的Class类对象,此Class类对象用于保存该类的类型信息
获取Class对象的三种方法:
Class.forName(“java.lang.String”); // 类没有被加载
String str = “123”; Class cls = str.getClass(); //类已经被加载
所谓反射其实是获取类的字节码文件,也就是.class文件,那么我们就可以通过Class这个对象进行获取
动态加载
https://www.jianshu.com/p/eead1c447753
收集器
CMS
G1收集器
G1将新生代,老年代的物理空间划分取消了
G1算法将堆划分为若干个区域(Region),它仍然属于分代收集器。不过,这些区域的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。老年代也分成很多区域,G1收集器通过将对象从一个区域复制到另外一个区域(标记整理),完成了清理工作。
JVM,JMM结构
线程私有,每个线程对应一个Java虚拟机栈,其生命周期与线程同进同退。每个Java方法在被调用的时候都会创建一个栈帧,并入栈。一旦完成调用,则出栈。所有的的栈帧都出栈后,线程也就完成了使命。
1、程序计数器:指向当前线程正在执行的字节码指令。线程私有的。
2、虚拟机栈:虚拟机栈是Java执行方法的内存模型。每个方法被执行的时候,都会创建一个栈帧,把栈帧压人栈,当方法正常返回或者抛出未捕获的异常时,栈帧就会出栈。
(1)栈帧:栈帧存储方法的相关信息,包含局部变量数表、返回值、操作数栈、动态链接
a、局部变量表:包含了方法执行过程中的所有变量。局部变量数组所需要的空间在编译期间完成分配,在方法运行期间不会改变局部变量数组的大小。
b、返回值:如果有返回值的话,压入调用者栈帧中的操作数栈中,并且把PC的值指向 方法调用指令 后面的一条指令地址。
c、操作数栈:操作变量的内存模型。操作数栈的最大深度在编译的时候已经确定(写入方法区code属性的max_stacks项中)。操作数栈的的元素可以是任意Java类型,包括long和double,32位数据占用栈空间为1,64位数据占用2。方法刚开始执行的时候,栈是空的,当方法执行过程中,各种字节码指令往栈中存取数据。
d、动态链接:每个栈帧都持有在运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。
(2)线程私有
3、本地方法栈:
(1)调用本地native的内存模型
(2)线程独享。
4、方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据
(1)线程共享的
(2)运行时常量池:
A、是方法区的一部分
B、存放编译期生成的各种字面量和符号引用
C、Class文件中除了存有类的版本、字段、方法、接口等描述信息,还有一项是常量池,存有这个类的 编译期生成的各种字面量和符号引用,这部分内容将在类加载后,存放到方法区的运行时常量池中。
5、堆(Heap):Java对象存储的地方
(1)Java堆是虚拟机管理的内存中最大的一块
(2)Java堆是所有线程共享的区域
(3)在虚拟机启动时创建
(4)此内存区域的唯一目的就是存放对象实例,几乎所有对象实例都在这里分配内存。存放new生成的对象和数组
(5)Java堆是垃圾收集器管理的内存区域,因此很多时候称为“GC堆”
JMM Java内存模型:
1、 Java的并发采用“共享内存”模型,线程之间通过读写内存的公共状态进行通讯。多个线程之间是不能通过直接传递数据交互的,它们之间交互只能通过共享变量实现。
2、 主要目的是定义程序中各个变量的访问规则。
3、 Java内存模型规定所有变量都存储在主内存中,每个线程还有自己的工作内存。
(1) 线程的工作内存中保存了被该线程使用到的变量的拷贝(从主内存中拷贝过来),线程对变量的所有操作都必须在工作内存中执行,而不能直接访问主内存中的变量。
(2) 不同线程之间无法直接访问对方工作内存的变量,线程间变量值的传递都要通过主内存来完成。
(3) 主内存主要对应Java堆中实例数据部分。工作内存对应于虚拟机栈中部分区域。
1.7,1.8方法区
1.7把字符串常量池从永久代中剥离出来,存放在堆空间中。
Jdk7将常量池从PermGen区移到了Java堆区,执行intern操作时,如果常量池已经存在该字符串,则直接返回字符串引用,否则复制该字符串对象的引用到常量池中并返回。堆区的大小一般不受限,所以将常量池从PremGen区移到堆区使得常量池的使用不再受限于固定大小。除此之外,位于堆区的常量池中的对象可以被垃圾回收。当常量池中的字符串不再存在指向它的引用时,JVM就会回收该字符串。可以使用 -XX:StringTableSize 虚拟机参数设置字符串池的map大小。字符串池内部实现为一个HashMap,所以当能够确定程序中需要intern的字符串数目时,可以将该map的size设置为所需数目*2(减少hash冲突),这样就可以使得String.intern()每次都只需要常量时间和相当小的内存就能够将一个String存入字符串池中。
tring str1 = new String(“1”);
解析:首先此行代码创建了两个对象,在执行前会在常量池中创建一个"1"的对象,然后执行该行代码时new一个"1"的对象存放在堆区中;然后str1指向堆区中的对象;
str1.intern();
解析:该行代码首先查看"1"字符串有没有存在在常量池中,此时存在则直接返回该常量,这里返回后没有引用接受他,【假如不存在的话在 jdk1.6中会在常量池中建立该常量,在jdk1.7以后会把堆中该对象的引用放在常量池中】
在jdk1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域
这里介绍的是JDK1.8 JVM运行时内存数据区域划分。1.8同1.7比,最大的差别就是:元数据区取代了永久代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存。
JAVA不同环境运行,区别
OOM和内存泄漏
内存泄露 (memory leak):申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。 比如TheadLocal 里面的value
内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。
https://www.cnblogs.com/ThinkVenus/p/6805495.html
java.lang.OutOfMemoryError: Java heap space ------>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。
java.lang.OutOfMemoryError: PermGen space ------>java永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,过多的常量尤其是字符串也会导致方法区溢出。
java.lang.StackOverflowError ------> 不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。
OOM 内存溢出 out of memory
java对象头
其中各部分的含义如下:
lock:2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了lock标记。该标记的值不同,整个mark word表示的含义不同。
biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
age:4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。
identity_hashcode:25位的对象标识Hash码,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象被锁定时,该值会移动到管程Monitor中。
thread:持有偏向锁的线程ID。
epoch:偏向时间戳。
ptr_to_lock_record:指向栈中锁记录的指针。
ptr_to_heavyweight_monitor:指向管程Monitor的指针。
Klass Point:
对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
这一部分用于存储对象的类型指针,该指针指向它的类元数据
young gc和full gc触发条件
young gc :eden空间不足
full gc :显示调用System.GC、旧生代空间不足、Permanet Generation空间满、CMS GC时出现promotion failed和concurrent mode failure、 RMI等的定时触发、YGC时的悲观策略、dump live的内存信息时
参考文献:http://blog.csdn.net/vernonzheng/article/details/8460128
http://blog.sina.com.cn/s/blog_7581a4c301019hsc.html
文章目录
自查自问
1. JDK,JRE,JVM 关系
2. 类加载过程 双亲委派 对象创建过程
3. 垃圾回收
4. 回收算法
5. 反射 反射创建对象
6. 垃圾回收器
7. JVM和JMM
8. 1..7,1.8方法区的区别
9. java 再不同环境下面运行的原理
10. OOM和内存泄漏
11. java对象头
12. young gc和full gc触发条件