毕业以来技术上一直没有太大进步,仔细一想可能是没有做技术分享,我喜欢把学习总结记录在印象笔记中,那么理解的是对是错也就没人能评判一下。为了技术进步,接下来将陆续把一些学习总结迁移到博客园中,欢迎大家多多指正!

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学习思考的更多相关文章

  1. JVM学习第一篇思考:一个Java代码是怎么运行起来的-上篇

    JVM学习第一篇思考:一个Java代码是怎么运行起来的-上篇 作为一个使用Java语言开发的程序员,我们都知道,要想运行Java程序至少需要安装JRE(安装JDK也没问题).我们也知道我们Java程序 ...

  2. java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)

    java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessCo ...

  3. java之jvm学习笔记十三(jvm基本结构)

    java之jvm学习笔记十三(jvm基本结构) 这一节,主要来学习jvm的基本结构,也就是概述.说是概述,内容很多,而且概念量也很大,不过关于概念方面,你不用担心,我完全有信心,让概念在你的脑子里变成 ...

  4. 【转载】JVM 学习——垃圾收集器与内存分配策略

    本文主要是对<深入理解java虚拟机 第二版>第三章部分做的总结,文章中大部分内容都来自这章内容,也是博客 JVM 学习的第二部分. 简述 说到垃圾收集(Garbage Collectio ...

  5. JVM学习——G1垃圾回收器(学习过程)

    JVM学习--G1垃圾回收器 把这个跨时代的垃圾回收器的笔记独立出来. 新生代:适用复制算法 老年代:适用标记清除.标记整理算法 二娃本来看G1的时候觉得比较枯燥,但是后来总结完之后告诉我说,一定要慢 ...

  6. JVM学习——学习方法论&学习大纲

    2020年02月06日22:25:51 完成了Springboot系列的学习和Kafka的学习,接下来进入JVM的学习阶段 深入理解JVM 学习方法论 如何去学习一门课程--方法论 多讨论,从别人身上 ...

  7. JVM学习笔记——内存结构篇

    JVM学习笔记--内存结构篇 在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的内存结构部分 我们会分为以下几部分进行介绍: JVM整体介绍 程序计数器 虚拟机栈 本地方法栈 堆 方法 ...

  8. JVM学习(4)——全面总结Java的GC算法和回收机制

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 一些JVM的跟踪参数的设置 Java堆的分配参数 -Xmx 和 –Xms 应该保持一个什么关系,可以让系统的 ...

  9. JVM学习(3)——总结Java内存模型

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 为什么学习Java的内存模式 缓存一致性问题 什么是内存模型 JMM(Java Memory Model)简 ...

随机推荐

  1. 自助BI工具是BI行业发展的趋势吗?

    自助BI和分析通过提供交互式数据可视化,图表,图形,报告和分析,帮助业务用户做出决策.将大量数据导出到电子表格以转换为图表和数据透视表的日子现在已经结束.自助BI工具提供基于浏览器的客户端界面,适用于 ...

  2. 你真的会做数据分析吗?如果不会我推荐思迈特软件Smartbi

    你是否还在被以下问题所困扰? 辛苦辛苦地拿到了一堆数据,却不知道从何下手分析? 因为不会统计数据分析伤透脑筋,而打消考博的梦想? 数据分析求助无门,涌现出想要放弃学位的念头? 突然开天眼般的想到了一个 ...

  3. Java的诞生历史

    Java帝国的诞生 1.1972年C诞生 贴近硬件,运行极快,效率极高 操作系统,编译器,数据库,网络系统等都采用C语言 但是,它的指针和内存管理给程序员安上了"枷锁".它的指针没 ...

  4. 【windows 操作系统】什么是窗口?|按钮也是窗口

    起因 在看操作系统消息机制的时候,看到一句化:全局消息队列把消息发送到窗口所在的线程消息队列.突然就怀疑起了窗口的意思.于是就有这边基类. 文章来源:https://docs.microsoft.co ...

  5. windows系统如何远程桌面连接

    转至:https://jingyan.baidu.com/article/67662997adb46a54d51b84bd.html 远程桌面连接是一种通过网络技术,远程操作另一台电脑的过程,随着wi ...

  6. 路径修改后cmd命令行窗口仍然没有变化的原因

    修改环境变量后,要重启cmd再输入才有用

  7. openpyxl模块的读写使用及mongodb概念

    今日内容概要 openpyxl模块 MongoDB数据库 今日内容详细 openpyxl模块 # 下载 pip3 install openpyxl # 使用 '''openpyxl内部读写也是分离的 ...

  8. 穿透、击穿、雪崩…Redis这么多问题,如何解决?

    摘要:什么是缓存穿透?什么是缓存击穿,又什么是缓存雪崩呢?它们是如何造成的?又该如何解决呢?今天,我们就一起来探讨这些问题. 本文分享自华为云社区<[高并发]什么是缓存穿透?击穿?雪崩?如何解决 ...

  9. iCloud开发: key-value Storage,CloudKit,iCloud Documents

    目录 iCloud开发 iCloud三种类型的存储方式 项目配置 1.iCloud 官网配置 2.本地Xcode配置 注意事项 一.key-value storage 1.获取默认store 2.写入 ...

  10. CentOS 7.5关闭FireWall 和SELinux图文详解

    CentOS 7.5关闭FireWall 和SELinux图文详解 1. 环境 CentOS 7.5 2. 关闭FireWall和SELinux 2.1 FireWall 使用systemctl st ...