天天看点

深入理解JAVA虚拟机第一章&第二章

五大部分总览

  • 宏观介绍整个JAVA技术体系(Java和JVM的发展历程、模块化)、JDK的编译
  • 介绍JVM自动内存管理:JVM内存区域的划分,Stackoverflow和OOM现象及其原因、 常见的垃圾收集算法以及垃圾回收器的工作原理
  • 虚拟机执行子系统:类文件结构、虚拟类加载机制、虚拟机字节码执行引擎
  • 程序的编译以及代码的优化:泛型、自动装箱、条件编译等语法糖的原理、虚拟机热点探测方法、Hotspot即时编译
  • Java高效并发的原理:虚拟机内存模型以及线程安全

JAVA历史&JVM历史(一笔带过,感兴趣的可以详细看下。对于把握整个JAVA技术脉络还是有些帮助的。

  • JAVA历史

    1997 JDK1.1 版的技术代表:JAR、JDBC ;JAVA语法:内部类、反射

    1998 JDK1.2版 首次把JAVA技术体系拆成三个方向,JAVA虚拟机,JIT编译器

    1999 Hotspot虚拟机被Sun公司收购并在JDK1.3之后成为SunJDK的默认虚拟机

    2002 JDK1.4发布,正则表达式、NIO,

    同年微软.NET Framework发布

    2004 JDK1.5 发布提供 java.util.concurrent并发包
  • JVM历史
    Sun Classic VM 远古虚拟机

    世界第一台商用Java虚拟机

    特点:只能使用纯解释器方式执行Java代码如果使用JIT必须外挂,假如外挂,JIT编译器就完全接管了虚拟机的执行系统,解释器就不再工作了。缺点:解释器和编译器不能协同工作导致‘Java语言很慢’。

    Exact VM

    Hotspot VM

自己编译JDK

  1. 环境: centos 7
  2. 安装Mercurial代码版本管理工具:yum -y install mercurial
  3. hg clone https://hg.openjdk.java.net/jdk7u/jdk7u-dev/
  4. cd jdk7u-dev 进入jdk7u-dev目录
    深入理解JAVA虚拟机第一章&第二章
  5. chmod +x get_source.sh 赋予可执行权限
  6. 运行脚本获得代码,网络不稳定(代码如缺失retry)
    深入理解JAVA虚拟机第一章&第二章

编译前的环境准备

安装gcc、gcc-c++

  yum install -y gcc gcc-c++

安装cups-devel (unix打印系统)

  yum install -y cups-devel

安装alsa-lib-devel

  yum install -y alsa-lib-devel

安装X图形库

  yum install -y libXrender libXrender-devel libXi-devel libXt-devel libXtst-devel

安装freetype字体库

  yum install -y freetype freetype-devel

安装bootstrap JDK

  yum install -y java-1.6.0-openjdk java-1.6.0-openjdk-devel

安装ant

  yum install -y ant ant-nodeps

设置环境变量并做编译前的环境检查make sanity

  • vim setenv.sh
#!/bin/bash

#clear settings if you have ever setup 
unset CLASSPAHT
unset JAVA_HOME

#select language, required 
export LANG=C

#bootsrap JDK path installed, required
export ALT_BOOTDIR=/usr/local/jdk1.7.0_79

#setup freetype
export ALT_FREETYPE_LIB_PATH=/usr/local/lib
export ALT_FREETYPE_HEADERS_PATH=/usr/local/include

#setup ant path
export ANT_HOME=/root/apache-ant-1.9.7

#dowload dependencies automatically
export ALLOW_DOWNLOADS=ture

#setup number of compiled threads same to cpus
export HOTSPOT_BUILD_JOBS=1
export ALT_PARALLEL_COMPILE_JOBS=1

export SKIP_COMPARE_IMAGES=true

export USE_PRECOMPILED_HEADER=true

#setup what you want to compile
export BUILD_LANGTOOLS=true
#export BUILD_JAXP=false
#export BUILD_JAXWS=false
#export BUILD_CORBA=false
export BUILD_HOTSPOT=true
export BUILD_JDK=true

#setup arch=64 if your cpu is 64 or arch=32
export ARCH_DATA_MODEL=64

#setup version
#export SKIP_DEBUG_BUILD=false
#export SKIP_FASTDEBUG_BUILD=true
#export DEBUG_NAME=debug

BUILD_DEPLOY=false

#cancel build install package
BUILD_INSTALL=false

#setup output DIR
export ALT_OUTPUTDIR=/usr/local/jdk7-dev/build
           
  • 执行脚本 最好用source
  • 环境检查 make sanity
[[email protected] jdk7u-dev]$ sudo make sanity
( cd  ./jdk/make && \
 make sanity HOTSPOT_IMPORT_CHECK=false JDK_TOPDIR=/data/work/jdk7u-dev/jdk JDK_MAKE_SHARED_DIR=/data/work/jdk7u-dev/jdk/make/common/shared EXTERNALSANITYCONTROL=true SOURCE_LANGUAGE_VERSION=7 TARGET_CLASS_VERSION=7 MILESTONE=internal BUILD_NUMBER=b00 JDK_BUILD_NUMBER=b00 FULL_VERSION=1.7.0-internal-root_2019_10_29_18_25-b00 PREVIOUS_JDK_VERSION=1.6.0 JDK_VERSION=1.7.0 JDK_MKTG_VERSION=7 JDK_MAJOR_VERSION=1 JDK_MINOR_VERSION=7 JDK_MICRO_VERSION=0 PREVIOUS_MAJOR_VERSION=1 PREVIOUS_MINOR_VERSION=6 PREVIOUS_MICRO_VERSION=0 ARCH_DATA_MODEL=64 COOKED_BUILD_NUMBER=0 ALT_OUTPUTDIR=/data/work/jdk7u-dev/build/linux-amd64 ALT_LANGTOOLS_DIST=/data/work/jdk7u-dev/build/linux-amd64/langtools/dist ALT_CORBA_DIST=/data/work/jdk7u-dev/build/linux-amd64/corba/dist ALT_JAXP_DIST=/data/work/jdk7u-dev/build/linux-amd64/jaxp/dist ALT_JAXWS_DIST=/data/work/jdk7u-dev/build/linux-amd64/jaxws/dist ALT_HOTSPOT_IMPORT_PATH=/data/work/jdk7u-dev/build/linux-amd64/hotspot/import BUILD_HOTSPOT=true ; )
INFO: ENABLE_FULL_DEBUG_SYMBOLS=1
INFO: /usr/bin/objcopy cmd found so will create .debuginfo files.
INFO: STRIP_POLICY=min_strip
INFO: ZIP_DEBUGINFO_FILES=1
/bin/sh: line 0: [: /bin/sh:: integer expression expected
/bin/sh: line 0: [: /bin/sh:: integer expression expected
/bin/sh: line 0: [: /NO_BOOTDIR/bin/java:: integer expression expected
/bin/sh: line 0: [: /NO_BOOTDIR/bin/java:: integer expression expected
/bin/sh: line 0: [: No: integer expression expected
/bin/sh: line 0: [: No: integer expression expected
make[1]: Entering directory `/data/work/jdk7u-dev/jdk/make'
INFO: ENABLE_FULL_DEBUG_SYMBOLS=1
INFO: /usr/bin/objcopy cmd found so will create .debuginfo files.
INFO: STRIP_POLICY=min_strip
INFO: ZIP_DEBUGINFO_FILES=1
make[1]: Leaving directory `/data/work/jdk7u-dev/jdk/make'

Build Machine Information:
  build machine = ES-077-031.bigdata.ly

Build Directory Structure:
  CWD = /data/work/jdk7u-dev
  TOPDIR = .
  LANGTOOLS_TOPDIR = ./langtools
  JAXP_TOPDIR = ./jaxp
  JAXWS_TOPDIR = ./jaxws
  CORBA_TOPDIR = ./corba
  HOTSPOT_TOPDIR = ./hotspot
  JDK_TOPDIR = ./jdk

Build Directives:
  BUILD_LANGTOOLS = true 
  BUILD_JAXP = true 
  BUILD_JAXWS = true 
  BUILD_CORBA = true 
  BUILD_HOTSPOT = true 
  BUILD_JDK    = true 
  DEBUG_CLASSFILES =  
  DEBUG_BINARIES =  

Hotspot Settings: 
     HOTSPOT_BUILD_JOBS  =  
     HOTSPOT_OUTPUTDIR   = /data/work/jdk7u-dev/build/linux-amd64/hotspot/outputdir 
     HOTSPOT_EXPORT_PATH = /data/work/jdk7u-dev/build/linux-amd64/hotspot/import 




Bootstrap Settings:
 BOOTDIR = /NO_BOOTDIR
   ALT_BOOTDIR = 
 BOOT_VER = /bin/sh: /NO_BOOTDIR/bin/java: No such file or directory [requires at least 1.6]
 OUTPUTDIR = /data/work/jdk7u-dev/build/linux-amd64
   ALT_OUTPUTDIR = /data/work/jdk7u-dev/build/linux-amd64
 ABS_OUTPUTDIR = /data/work/jdk7u-dev/build/linux-amd64

Build Tool Settings:
 SLASH_JAVA = /NOT-SET
   ALT_SLASH_JAVA = 
 VARIANT = OPT
 JDK_DEVTOOLS_DIR = /NOT-SET/devtools
   ALT_JDK_DEVTOOLS_DIR = 
 ANT_HOME = 
 UNIXCOMMAND_PATH = /bin/
   ALT_UNIXCOMMAND_PATH = 
 COMPILER_PATH = /usr/bin/
   ALT_COMPILER_PATH = 
 DEVTOOLS_PATH = /usr/bin/
   ALT_DEVTOOLS_PATH = 
 UNIXCCS_PATH = /usr/ccs/bin/
   ALT_UNIXCCS_PATH = 
 USRBIN_PATH = /usr/bin/
   ALT_USRBIN_PATH = 
 COMPILER_NAME = GCC4
 COMPILER_VERSION = GCC4
 CC_VER = 4.8.5 [requires at least 4.3.0]
 ZIP_VER = 3.0 [requires at least 2.2]
 UNZIP_VER = 6.00 [requires at least 5.12]
 ANT_VER = 1.9.4 [requires at least 1.7.1]
 TEMPDIR = /data/work/jdk7u-dev/build/linux-amd64/tmp

Build Directives:
 OPENJDK = true
 USE_HOTSPOT_INTERPRETER_MODE = 
 PEDANTIC = 
 DEV_ONLY = 
 NO_DOCS = 
 NO_IMAGES = 
 TOOLS_ONLY = 
 INSANE = 
 COMPILE_APPROACH = parallel
 PARALLEL_COMPILE_JOBS = 2
   ALT_PARALLEL_COMPILE_JOBS = 
 FASTDEBUG = 
 COMPILER_WARNINGS_FATAL = false
 COMPILER_WARNING_LEVEL = 
 SHOW_ALL_WARNINGS = 
 INCREMENTAL_BUILD = false
 CC_HIGHEST_OPT = 
 CC_HIGHER_OPT = 
 CC_LOWER_OPT = 
 CXXFLAGS =  -O2 -fPIC -DCC_NOEX -W -Wall  -Wno-unused -Wno-parentheses -fno-omit-frame-pointer -D_LITTLE_ENDIAN  
 CFLAGS =  -O2   -fno-strict-aliasing -fPIC -W -Wall  -Wno-unused -Wno-parentheses -pipe -fno-omit-frame-pointer -D_LITTLE_ENDIAN  
 BOOT_JAVA_CMD = /NO_BOOTDIR/bin/java -XX:-PrintVMOptions -XX:+UnlockDiagnosticVMOptions -XX:-LogVMOutput -Xmx512m -Xms512m -XX:PermSize=32m -XX:MaxPermSize=160m
 BOOT_JAVAC_CMD = /NO_BOOTDIR/bin/javac  -J-XX:ThreadStackSize=1536 -J-XX:-PrintVMOptions -J-XX:+UnlockDiagnosticVMOptions -J-XX:-LogVMOutput -J-Xmx512m -J-Xms512m -J-XX:PermSize=32m -J-XX:MaxPermSize=160m -encoding ascii -source 6 -target 6 -XDignore.symbol.file=true
 BOOT_JAR_CMD = /NO_BOOTDIR/bin/jar
 BOOT_JARSIGNER_CMD = /NO_BOOTDIR/bin/jarsigner
 JAVAC_CMD = /NOT-SET/re/jdk/1.7.0/promoted/latest/binaries/linux-amd64/bin/javac  -J-XX:ThreadStackSize=1536 -J-XX:-PrintVMOptions -J-XX:+UnlockDiagnosticVMOptions -J-XX:-LogVMOutput -J-Xmx512m -J-Xms512m -J-XX:PermSize=32m -J-XX:MaxPermSize=160m  -source 7 -target 7 -encoding ascii -Xbootclasspath:/data/work/jdk7u-dev/build/linux-amd64/classes 
 JAVAH_CMD = /NOT-SET/re/jdk/1.7.0/promoted/latest/binaries/linux-amd64/bin/javah -bootclasspath /data/work/jdk7u-dev/build/linux-amd64/classes
 JAVADOC_CMD = /NOT-SET/re/jdk/1.7.0/promoted/latest/binaries/linux-amd64/bin/javadoc -J-XX:-PrintVMOptions -J-XX:+UnlockDiagnosticVMOptions -J-XX:-LogVMOutput -J-Xmx512m -J-Xms512m -J-XX:PermSize=32m -J-XX:MaxPermSize=160m -bootclasspath /data/work/jdk7u-dev/build/linux-amd64/classes

Build Platform Settings:
 USER = root
 PLATFORM = linux
 ARCH = amd64
 LIBARCH = amd64
 ARCH_FAMILY = amd64
 ARCH_DATA_MODEL = 64
 ARCHPROP = amd64
 ALSA_VERSION = 1.1.8
 OS_VERSION = 4.11.1-1.el7.elrepo.x86_64 [requires at least 2.6]
 OS_VARIANT_NAME = RedHat
 OS_VARIANT_VERSION = 
 MB_OF_MEMORY = 257102

GNU Make Settings:
 MAKE = make
 MAKE_VER = 3.82 [requires at least 3.81]
 MAKECMDGOALS = sanity
 MAKEFLAGS = w
 SHELL = /bin/sh

Target Build Versions:
 JDK_VERSION = 1.7.0
 MILESTONE = internal
 RELEASE = 1.7.0-internal
 FULL_VERSION = 1.7.0-internal-root_2019_10_29_18_25-b00
 BUILD_NUMBER = b00

External File/Binary Locations:
 USRJDKINSTANCES_PATH = /opt/java
 BUILD_JDK_IMPORT_PATH = /NOT-SET/re/jdk/1.7.0/promoted/latest/binaries
   ALT_BUILD_JDK_IMPORT_PATH = 
 JDK_IMPORT_PATH = /NOT-SET/re/jdk/1.7.0/promoted/latest/binaries/linux-amd64
   ALT_JDK_IMPORT_PATH = 
 LANGTOOLS_DIST = 
   ALT_LANGTOOLS_DIST = /data/work/jdk7u-dev/build/linux-amd64/langtools/dist
 CORBA_DIST = 
   ALT_CORBA_DIST = /data/work/jdk7u-dev/build/linux-amd64/corba/dist
 JAXP_DIST = 
   ALT_JAXP_DIST = /data/work/jdk7u-dev/build/linux-amd64/jaxp/dist
 JAXWS_DIST = 
   ALT_JAXWS_DIST = /data/work/jdk7u-dev/build/linux-amd64/jaxws/dist
 HOTSPOT_DOCS_IMPORT_PATH = /NO_DOCS_DIR
   ALT_HOTSPOT_DOCS_IMPORT_PATH = 
 HOTSPOT_IMPORT_PATH = /data/work/jdk7u-dev/build/linux-amd64/hotspot/import
   ALT_HOTSPOT_IMPORT_PATH = /data/work/jdk7u-dev/build/linux-amd64/hotspot/import
 HOTSPOT_SERVER_PATH = /data/work/jdk7u-dev/build/linux-amd64/hotspot/import/jre/lib/amd64/server
   ALT_HOTSPOT_SERVER_PATH = 
 CACERTS_FILE = ./../src/share/lib/security/cacerts
   ALT_CACERTS_FILE = 
 CUPS_HEADERS_PATH = /usr/include
   ALT_CUPS_HEADERS_PATH = 

OpenJDK-specific settings:
 FREETYPE_HEADERS_PATH = /usr/include
   ALT_FREETYPE_HEADERS_PATH = 
 FREETYPE_LIB_PATH = /usr/lib
   ALT_FREETYPE_LIB_PATH = 

Previous JDK Settings:
 PREVIOUS_RELEASE_PATH = 
   ALT_PREVIOUS_RELEASE_PATH = 
 PREVIOUS_JDK_VERSION = 1.6.0
   ALT_PREVIOUS_JDK_VERSION = 
 PREVIOUS_JDK_FILE = 
   ALT_PREVIOUS_JDK_FILE = 
 PREVIOUS_JRE_FILE = 
   ALT_PREVIOUS_JRE_FILE = 
 PREVIOUS_RELEASE_IMAGE = 
   ALT_PREVIOUS_RELEASE_IMAGE = 


WARNING: LANG has been set to en_US.UTF-8, this can cause build failures. 
        Try setting LANG to 'C'. 

Sanity check passed.
           

最后

sudo make ALT_BOOTDIR=${jdk路径} (编译openjdk源码不仅需要jre环境也需要jdk环境)

关于编译过程中几个问题

  • 刚开始我是直接使用make命令,export ALT_BOOTDIR=${jdk路径} 但在centos7 系统中编译openjdk-7u40,执行make sanity通过,但是在实行make时报了Error running /NO_BOOTDIR/bin/javac compiler错误
BUILD FAILED
/data/work/jdk7u-dev/langtools/make/build.xml:860: Error running /NO_BOOTDIR/bin/javac compiler
           

由于openJDK默认值安装了jre,也就是java运行环境,并没有安装java开发环境,所以导致打包失败。

执行一下命令:

yum install java-1.7.0-openjdk-devel
           

然后又定位了java环境、查看环境变量配置,因为我里面同时有多个版本的java环境。

深入理解JAVA虚拟机第一章&第二章

发现并不是问题的直接原因

,最终直接在编译的时候直接指定同版本的jdk进行编译: sudo make ALT_BOOTDIR=${jdk路径} 解决了问题

  • 另外一些问题都可以通过错误提示进行针对性的解决
缺少哪个包直接yum install 就行了,然后make clean make sanity 再编译。 有些可能是之前下载C和C++语言相关的包(gcc g++)没下载全导致的,重新下载下多试几次。

编译成功结果

深入理解JAVA虚拟机第一章&第二章
深入理解JAVA虚拟机第一章&第二章

接下来,你就可以执行下简单的java代码尝试下了:)

/**
 * @author LD
 * @create time 2019-10-30 15:13
 */
public class HelloJAVA {
    public static void main(String[] args) {
        System.out.println("Hello OpenJDK");
    }
}

           
深入理解JAVA虚拟机第一章&第二章

参考文献

在定位问题、解决问题中,筛选并参考了几篇帮助较大的文章,觉得有必要Mark一下。ps:在此过程中粉了

rednaxelafx

大牛,C++造轮子有轮子哥JAVA造轮子的是rednaxelafx,我更欣赏低调的大牛。

https://www.cnblogs.com/xidongyu/p/5722122.html
http://www.voidcn.com/article/p-zgthnemz-bpb.html
http://www.linxiaoming.me/2016/10/12/OpenJDK%E7%BC%96%E8%AF%91/
https://www.linuxidc.com/Linux/2018-10/155041.htm
https://hllvm-group.iteye.com/group/topic/35385
https://www.iteye.com/blog/rednaxelafx-1549577
https://axboy.github.io/java/2018/12/21/openjdk7-centos/
https://www.jianshu.com/p/eaa2756e93d0
           

第二章 自动内存管理机制

  • Java虚拟机运行时的内存分布
深入理解JAVA虚拟机第一章&第二章
  • 需要明确几个问题
    • 哪些区域是伴随虚拟机进程的生命周期,哪些区域是依赖用户线程启动
    • 哪些区域是线程共享的,哪些区域是线程私有的

    线程私有的

    : 程序计数器、虚拟机栈 (局部变量)

    线程共享的

    : 堆(新生代、老年代)、方法区(静态变量)
  • Java虚拟机栈
    在这个区域有一个异常情况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出

    StackOverflowError

    • 实践
      设置运行时栈的大小:

      -Xss256k

      代码
      深入理解JAVA虚拟机第一章&第二章
  • Java堆
    Java 虚拟机管理的内存中最大的一块,该区域唯一目的就是存放对象实例。
    • 该区域是垃圾收集器管理的主要区域因此也被称为‘GC堆’,Java堆可以细分为:新生代和老年代;再细致分:Eden区、From survivor区、To survivor区。
    • 根据Java虚拟机规范规定,Java堆只要逻辑连续即可,如果堆中没有内存了将会抛出

      OOM

    • 实践
    设置运行时堆的大小:

    -Xmx5m

    代码
    深入理解JAVA虚拟机第一章&第二章
  • 方法区

    和堆一样是线程共享区,存储加载类信息、常量(运行常量池)、静态变量等数据

HotSpot虚拟机在Java堆中创建对象全过程

  • 当虚拟机接收到new一个对象的指令时,首先去常量池定位这个类的符号引用并且检查该类是否被加载、解析初始化过。没有则执行类加载过程。

继续阅读