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

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. 【windows 操作系统】协程

    ◆ 协程 由程序员手动切换. 属于线程,是在线程里面跑的,因此协程又称微线程和线程等.由于不用进行线程上下文切换,因此效率会更高. 资源: 拥有自己的栈空间,大小动态调整. 安全性: 拥有自己的寄存器 ...

  2. Ansible安装及初始化-从零到无

    --时间:2019年1月12日 --作者:飞翔的小胖猪 前言 说明 文档指导读者在Redhat系列操作系统上安装Ansible软件及初始化配置,包括服务端及被控端的配置文件设置. 以下所有操作都在An ...

  3. el-dialog设置为点击弹窗以外的区域不自动关闭弹窗

    两种方法:单个设置或者全局设置 第一种:(单个设置) 在el-dialog标签中添加:close-on-click-modal="false"即可 <el-dialog ti ...

  4. QUIC协议详解

    声明 本文可以自由转载但需注明原始链接.本文为本人原创,作者LightningStar,原文发表在博客园.本文主体内容参考论文[1]完成. 介绍 QUIC,发音同quick,是"Quick ...

  5. VM虚拟机 Ubuntu配置与ssh连接

    VMware安装ubuntu 自定义 不作更改 选择稍后安装操作系统,相当于裸机,没装系统. 选择ubuntu64 选择虚拟机名字与保存路径 配置情况 2G即可 网络类型,选择NAT 可以了解一下这几 ...

  6. Java 类方法和类变量

    目录 一.类变量 1.如果定义类变量 2.如何访问类变量 3.类变量的使用注意事项和细节 二.类方法 1.类方法的形式 2.类方法的调用 3.类方法经典使用场景 4.类变量和类方法 三.main方法 ...

  7. Pycharm新建Python项目

    关于新建项目时配置项目环境(最好是每个项目单独的虚拟Python环境): Python为什么要使用虚拟环境-Python虚拟环境的安装和配置-virtualenv Pycharm创建Python项目 ...

  8. 如何从头到脚彻底解决一个MySQL Bug

    摘要:为了保障华为云GaussDB产品的可靠性,每一款产品发布前都要通过多轮严苛的测试用例. 说明:本文中的MySQL,如果不做特殊说明,指的是开源社区版MySQL. 华为云数据库新版本在发布之前,会 ...

  9. JUC知识点总结(知识点见内部目录)

    目录 JUC是什么 锁 Synchronized VS Lock 实现差异 Synchronized & Lock 总结 Synchronized锁的对象是什么 生产者&消费者 只有两 ...

  10. linux tr命令实现windows文本格式与linux文本格式间的转换

    tr 命令 转换和删除字符 选项: -d --delete:删除字符 -s --squeeze-repeats:把连续重复的字符以一个字符表示,即去重 -c –C --complement:取字符集的 ...