JVM学习思考
毕业以来技术上一直没有太大进步,仔细一想可能是没有做技术分享,我喜欢把学习总结记录在印象笔记中,那么理解的是对是错也就没人能评判一下。为了技术进步,接下来将陆续把一些学习总结迁移到博客园中,欢迎大家多多指正!
JVM的定义
Jvm
Java虚拟机。一次编译,到处运行的前提
Jre
JVM+核心类库
Jdk
JVM+核心类库+扩展类库
JMM
Java内存模型。主要用于多线程共享数据
子模块
自动内存管理(分配、回收内存)、虚拟机执行子系统(类加载机制、虚拟机字节码执行引擎)
JVM运行时数据区
JDK8以后:
(图摘自java3y)
程序计数器:
当前线程所执行的字节码的行号指示器。
Java虚拟机栈:
Java方法执行的内存模型:每个方法执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等。
本地方法栈:
为虚拟机执行的native方法服务。
Java堆:
存放对象实例。
常量池:常量池记录的是代码出现过的常量、类名、成员变量等以及符号引用(类引用、方法引用,成员变量引用等)。
方法区(元空间):
存储已被虚拟机加载的类元数据信息。
内存溢出:
内存不够用。OutOfMemoryError: Java heap space、StackOverFlowError、OutOfMemoryError: Metaspace
内存泄漏:
无用内存未及时回收。最终可能导致内存溢出。
示例:
1、各种内存溢出。2、创建String对象时的内存分配
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List; /**
* jvm内存溢出示例
* -Xms 堆初始值
* -Xmx 堆最大值
* -Xmn 新生代大小
* -Xss 每个线程的栈大小
* -server -Xmx20m -Xms20m -Xmn10m -Xss1m -XX:+HeapDumpOnOutOfMemoryError
* -server -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC
*/
public class MemErrorDemo {
int depth = 0; /**
* 内存溢出错误(OOM)
*/
public void OOMError() {
List<byte[]> list = new ArrayList<>();
int i = 0;
while (true) {
list.add(new byte[5 * 1024 * 1024]);
System.out.println("loop count: " + (++i));
}
} /**
* 栈溢出错误(StackOverFlowError)
*/
public void SOFError() {
try {
depth++;
SOFError();
} finally {
System.out.println("递归count: " + depth);
}
} /**
* 元空间错误
* 使用cglib生成动态代理类
*/
public void MetaSpaceError() {
int i = 0;
try {
while (true) {
i++;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
}
} catch (Exception e) {
System.out.println("generate class count" + i);
e.printStackTrace();
}
} static class OOMObject { } public static void main(String args[]) {
MemErrorDemo memErrorDemo = new MemErrorDemo();
//memErrorDemo.OOMError();
//memErrorDemo.SOFError();
memErrorDemo.MetaSpaceError();
}
}
/**
* 字符串对象创建时内存分配
* 字符串常量池(内容在编译期确定)、堆
* 编译期、运行期
*/
public class StringObjDemo {
public static void main(String args[]) {
String a = "he";
String b = "llo"; String c = "hello";
String d = "he" + "llo";
String e = new String("hello");
String f = a + "llo";
String g = a + b;
String h = e.intern(); System.out.println(c == d);
System.out.println(c == e);
System.out.println(c == f);
System.out.println(c == g);
System.out.println(f == g);
System.out.println(c == h);
}
}
JVM垃圾回收
回收的内容和区域
内容:废弃的对象、常量和无用的类;区域:堆、元空间
判断对象是否生存
引用记数法:
难解决两个或多个对象的循环引用状况。
可达性分析算法:
可作为GCRoots的对象包括:
1、 虚拟机栈中(栈帧中本地变量表)中引用的对象。
2、 元空间中静态属性引用的对象
3、 常量池中常量引用的对象
4、 本地方法栈中native方法引用的对象。
强引用:平时使用最多的的引用,若对象有强引用,并且从GCRoots到其可达(可达性分析),则不会被GC回收。
软引用:在堆内存未发生溢出时不会回收有软引用的对象,在内存溢出将要发生前,先对软引用关联的对象进行回收,回收后仍溢出,则抛OOM异常。
弱引用:对象只有弱引用,会在下一次GC进行回收时被回收
虚引用:不能通过虚引用获取对象实例,不影响对象生存时间,唯一作用是在对象回收时收到一个系统通知
垃圾收集算法
标记-清除算法:标记出需要回收的对象,然后清楚有标记的对象。
标记-整理算法:标记出需要回收的对象,然后移动到一端,直接清理边界外的内存。
复制算法:将内存容量分两部分,保持在某一时刻始终有一部分是空的,将仍然存活的对象复制到此区域。
分代收集算法:
新生代:复制算法。朝生夕死,对象存活率低,将区域分3份,eden:survivor0:survivor1=8:1:1
老年代:标记-清楚/标记-整理算法。
回收事件:
新生代回收事件:minor gc
老年代回收事件:major gc
全部回收(包括MetaSpace):full gc(Stop the world),默认堆空间使用到达80%(可调整)的时候会触发fgc
内存分配与回收策略
1、 对象优先在Eden分配
2、 大对象直接进入老年代
3、 长期存活的对象进入老年代。每熬过一次minor gc年龄增加1岁,默认15岁进入老年代。
4、 动态对象年龄判断。Survivor空间中相同年龄所有对象大小的和大于survivor空间的一半,大于或等于该年龄的对象直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。
5、 空间分配担保。新生代进行复制回收时,survivor空间不够用,survivor无法容纳的对象进入老年代。
垃圾收集器
JVM类加载机制
类加载时机
加载、验证、准备、初始化和卸载顺序确定,解析不一定,有时会在初始化之后——java运行时绑定(动态绑定/晚期绑定)。
解析阶段是虚拟机将常量池内的符号引用替换为直接引用。
初始化的5种情况,如果类未初始化则立刻初始化:
1、 遇到new、getstatic、putstatic、或invokestatic4条字节码指令。这4条指令常见场景:使用new关键字实例化对象、读取一个类的静态字段(被final修饰、已在编译期将结果放入常量池的静态字段除外)、调用一个类的静态方法。
2、 使用java.lang.reflect包的方法对类反射调用。
3、 初始化一个类的时候,如果父类未初始化,则优先初始化父类(接口除外,只在使用时初始化)。
4、 虚拟机启动时,用户需要指定一个要执行的主类(main方法所在类),虚拟机会先初始化这个主类。
5、 使用动态语言时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,其所对应的类未初始化,则先触发其初始化。
这5类是对一个类进行主动引用,除此之外所有引用类的方式都不会触发初始化,称为被动引用。
示例:类的被动引用
/**
* 类加载阶段,被动引用示例
* 1、通过子类引用父类静态字段,不会导致子类初始化
* 2、通过数组定义引用类不会触发此类的初始化。
* 数组本身不通过类加载器创建,由java虚拟机直接创建。
* 3、常量在编译期存入调用类的常量池,本质上没有直接引用到定义常量的类
*/
public class ClassLoadingDemo {
public static void main(String[] args) {
//System.out.println(SubClass.h);
//SubClass[] scs = new SubClass[10];
System.out.println(SubClass.w);
}
} class SuperClass {
static {
System.out.println("SuperClass init");
} public static String h = "hello";
public static final String w = "world";
} class SubClass extends SuperClass{
static {
System.out.println("SubClass init");
}
}
类加载器
即使两个类来自同一个class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类必定不相等。
双亲委派模型
对类的加载请求,优先让父加载器加载,只有父加载器反馈无法加载时,子加载器才会尝试加载。
类加载器之间的父子关系并不是通过继承来实现的,而是通过组合关系。
JVM字节码执行引擎
运行时帧栈结构——动态连接
Class文件的常量池中存在大量的符号引用,这些符号引用一部分会在类加载阶段或者第一次使用时转化为直接引用,这种转化称为静态解析。另一部分将在每一次运行期间转化为直接引用,称为动态连接。
方法调用
目的:唯一任务是确定方法调用的版本即调用哪一个方法。
理解概念:静态类型、实际类型
解析调用:
静态过程,在编译阶段就完全确定,在类装载的解析阶段就会把涉及的符号引用全部转化为直接引用,不会延迟到运行期。
在解析阶段确定唯一的版本,此类方法包括:静态方法、私有方法、实例构造器、父类方法4类。
分派:
理解概念:多态
方法的宗量:方法的接收者与方法的参数
静态分派通过静态类型确定方法的执行版本,发生在编译阶段。典型应用:方法的重载
示例:方法的重载
/**
* 静态分派示例
* 根据静态类型确定方法版本
*/
public class MethodOverLoadDemo {
public static class SuperClass {
} public static class Sub1 extends SuperClass {
} public static class Sub2 extends SuperClass {
} public void sayHello(SuperClass s) {
System.out.println("hello super");
} public void sayHello(Sub1 s) {
System.out.println("hello sub1");
} public void sayHello(Sub2 s) {
System.out.println("hello sub2");
} public static void main(String[] args) {
SuperClass s1 = new Sub1();
SuperClass s2 = new Sub2();
MethodOverLoadDemo ml= new MethodOverLoadDemo();
ml.sayHello(s1);
ml.sayHello(s2);
}
}
动态分派
Java语言是一门静态多分派,动态单分派的语言。静态分派时通过方法接受者即静态类型、方法参数两个宗量确定方法版本,动态分派时则在已有版本(静态分派时确定)中只通过方法接收者即实际类型确定最终方法版本。
典型应用:方法的重写
示例:方法的重写
/**
* 动态分派示例
* 方法重写-jvm选择方法版本:
* 1、在编译期间根据静态类型选择一个方法版本
* 2、在运行期间根据实际类型和编译期间已选的版本选择最终版本
*/
public class MethodOverrideDemo { public static class O {
public void m1(O o) {
System.out.println("O-m1");
}
} public static class A extends O {
public void m1(A a) {
System.out.println("A-m1");
}
} public static class B extends A {
@Override
public void m1(A a) {
System.out.println("B-m1");
} public void m1(B b) {
System.out.println("B-m2");
} public void m1(O b) {
System.out.println("B-m3");
}
} public static void main(String[] args) {
A a = new B();
a.m1(a); B b = new B();
a.m1(b); b.m1(b);
}
}
参考资料:
《深入理解Java虚拟机——JVM高级特性与最佳实践》 周志明 著
Java3y: https://www.cnblogs.com/Java3y/p/9296496.html
JVM学习思考的更多相关文章
- JVM学习第一篇思考:一个Java代码是怎么运行起来的-上篇
JVM学习第一篇思考:一个Java代码是怎么运行起来的-上篇 作为一个使用Java语言开发的程序员,我们都知道,要想运行Java程序至少需要安装JRE(安装JDK也没问题).我们也知道我们Java程序 ...
- java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)
java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessCo ...
- java之jvm学习笔记十三(jvm基本结构)
java之jvm学习笔记十三(jvm基本结构) 这一节,主要来学习jvm的基本结构,也就是概述.说是概述,内容很多,而且概念量也很大,不过关于概念方面,你不用担心,我完全有信心,让概念在你的脑子里变成 ...
- 【转载】JVM 学习——垃圾收集器与内存分配策略
本文主要是对<深入理解java虚拟机 第二版>第三章部分做的总结,文章中大部分内容都来自这章内容,也是博客 JVM 学习的第二部分. 简述 说到垃圾收集(Garbage Collectio ...
- JVM学习——G1垃圾回收器(学习过程)
JVM学习--G1垃圾回收器 把这个跨时代的垃圾回收器的笔记独立出来. 新生代:适用复制算法 老年代:适用标记清除.标记整理算法 二娃本来看G1的时候觉得比较枯燥,但是后来总结完之后告诉我说,一定要慢 ...
- JVM学习——学习方法论&学习大纲
2020年02月06日22:25:51 完成了Springboot系列的学习和Kafka的学习,接下来进入JVM的学习阶段 深入理解JVM 学习方法论 如何去学习一门课程--方法论 多讨论,从别人身上 ...
- JVM学习笔记——内存结构篇
JVM学习笔记--内存结构篇 在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的内存结构部分 我们会分为以下几部分进行介绍: JVM整体介绍 程序计数器 虚拟机栈 本地方法栈 堆 方法 ...
- JVM学习(4)——全面总结Java的GC算法和回收机制
俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 一些JVM的跟踪参数的设置 Java堆的分配参数 -Xmx 和 –Xms 应该保持一个什么关系,可以让系统的 ...
- JVM学习(3)——总结Java内存模型
俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 为什么学习Java的内存模式 缓存一致性问题 什么是内存模型 JMM(Java Memory Model)简 ...
随机推荐
- json系列(二)cjson,rapidjson,yyjson大整数解析精度对比
前言上一篇介绍了3种json解析工具的使用方法,对于基础数据的解析没有任何问题.我们传输的json数据里有unsigned long型数据,需要借助json解析工具得到正确的unsigned long ...
- C# 使用NPOI处理Excel模板-【前面部分固定,中间是动态的几行,尾部是固定的部分】
今天同组的兄弟问我,他有一个导出的模板,大概如下: [前面部分固定,中间是动态的几行,尾部是固定的部分].其实这个很像单链表往单链表在指定插入数据. 他问我怎么做才好,他想到的做法是:因为这些动态列的 ...
- js根据ClassName来删除元素(有坑误入)
今天,被一个很简单的问题坑了一下午,基础不扎实.(js根据class名称来删除Dom元素) 但是结果却不是这样的.弄了好久还不知道怎么回事.最后找到了答案. 结果如下:为啥还有test2,4,6呢. ...
- Java课程设计---添加学生
1.创建添加窗体 package com.student.view; import java.awt.EventQueue; import javax.swing.ButtonGroup; impor ...
- AcWing 215. 破译密码
传送门 思路:gcd(a,b)=k<=>gcd(a/k,b/k)=1,令x=a/k,y=b/k,则问题变为问x<=a/d,y<=b/d有多少(x,y)满足gcd(x,y)=1. ...
- MongoDB聚合查询及Python连接MongoDB操作
今日内容概要 聚合查询 Python操作MongoDB 第三方可视化视图工具 今日内容详细 聚合查询 Python操作MongoDB 数据准备 from pymongo import MongoCli ...
- 任意文件夹打开CMD命令窗口
1:打开任意文件夹 2:按住Shift键,鼠标右键单击 3:该文件夹下的命令窗口已打开,方便快捷
- 安装CentOS时,推荐的分区方案
最佳分区设置取决于 Linux 系统的用途.
- 解决矩池云GPU显存未释放问题
很多用户反馈说终止程序之后,显存依然被占用,这里我们提供了两种解决方案,帮助用户解决这个问题. nvidia-smi查看 我们可以先用如下命令 nvidia-smi 查看一下当前GPU进程情况. _ ...
- ASP.NET Core框架探索(一)
今天我们来结合源码来探究一下ASP.NET CORE Web框架的运行原理. 可以先整体看一下下面这张基于源码分析过程的一个总结大纲,包含各环节完成的关键步骤: 下面我们将一起来结合源码探索启动一个A ...