├── .gitignore ├── .idea ├── IdeaJol.iml ├── codeStyles │ └── codeStyleConfig.xml ├── compiler.xml ├── dictionaries │ └── stokito.xml ├── encodings.xml ├── inspectionProfiles │ └── Project_Default.xml ├── kotlinc.xml ├── libraries │ └── openjdk_jol_core.xml ├── projectCodeStyle.xml ├── runConfigurations │ └── IdeaJol.xml ├── uiDesigner.xml └── vcs.xml ├── .run ├── Run IDE with Plugin.run.xml └── Run Plugin Tests.run.xml ├── LICENSE.txt ├── README.md ├── build.gradle.kts ├── screenshot.png ├── settings.gradle.kts └── src ├── main ├── java │ └── com │ │ └── github │ │ └── stokito │ │ └── IdeaJol │ │ ├── FieldLayoutGap.java │ │ ├── Layouters.java │ │ ├── PsiClassAdapter.java │ │ ├── ShowObjectLayoutAction.java │ │ ├── inspection │ │ ├── JolInspection.java │ │ └── ShowJolQuickFix.java │ │ └── toolwindow │ │ ├── FieldLayoutTableModel.java │ │ ├── JolForm.form │ │ ├── JolForm.java │ │ ├── JolToolWindowFactory.java │ │ └── JolView.java └── resources │ ├── META-INF │ ├── plugin.xml │ └── pluginIcon.svg │ ├── inspectionDescriptions │ └── jol.html │ └── ram-13x13.png └── test └── java └── com └── github └── stokito └── IdeaJol └── PsiClassAdapterTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/shelf 2 | .idea/workspace.xml 3 | .idea/usage.statistics.xml 4 | .idea/tasks.xml 5 | #.idea/libraries 6 | .idea/modules.xml 7 | .idea/misc.xml 8 | sandbox 9 | out 10 | IdeaJol.zip 11 | # Gradle 12 | .gradle 13 | gradle 14 | gradlew 15 | gradlew.bat 16 | build 17 | # Platform-specific 18 | # MacOS 19 | .DS_Store 20 | # Custom development files -------------------------------------------------------------------------------- /.idea/IdeaJol.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/dictionaries/stokito.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ccps 5 | exter 6 | hotspot 7 | layouter 8 | layouters 9 | ponomarev 10 | signum 11 | stokito 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 53 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/libraries/openjdk_jol_core.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/projectCodeStyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 278 | 280 | 281 | 282 | -------------------------------------------------------------------------------- /.idea/runConfigurations/IdeaJol.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 150 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.run/Run IDE with Plugin.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 24 | -------------------------------------------------------------------------------- /.run/Run Plugin Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2011 Sergey Ponomarev (stokito@gmail.com) 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java Object Layout (JOL) plugin for IntelliJ Idea 2 | 3 | [JOL](https://github.com/openjdk/jol/) (Java Object Layout) is the tool to analyze object layout schemes in JVMs. 4 | For example, in HotSpot VM on 64x processor an empty string takes 40 bytes i.e. 24 bytes for String object itself + 16 bytes for an internal empty char array. 5 | 6 | The plugin is a GUI for JOL and allows you to make an estimate how much memory the object takes. 7 | 8 | Set a cursor into a class name and then press `Code / Show Object Layout` and you'll see a right panel with layout info. 9 | 10 | ![screenshot.png](screenshot.png) 11 | 12 | Thus, you can perform simplest but most efficient performance improvements. 13 | Just check your DTOs if they fit into 64 bytes of processor's cache line. 14 | 15 | Only HotSpot VM is supported by JOL itself. 16 | The plugin supports only basic estimate of class layout in different VM modes i.e. the same as `jol-cli estimates` command. 17 | For more precise estimate use JOL library and estimate in run time on the real objects with `GraphLayout`: 18 | 19 | ```java 20 | import org.openjdk.jol.info.GraphLayout; 21 | import java.util.HashMap; 22 | 23 | public class JolTest { 24 | 25 | public static void main(String[] args) { 26 | HashMap hashMap = new HashMap<>(); 27 | hashMap.put("key", "value"); 28 | System.out.println(GraphLayout.parseInstance(hashMap).toFootprint()); 29 | } 30 | } 31 | ``` 32 | 33 | Output will be like: 34 | 35 | java.util.HashMap@7a79be86d footprint: 36 | COUNT AVG SUM DESCRIPTION 37 | 2 24 48 [B 38 | 1 80 80 [Ljava.util.HashMap$Node; 39 | 2 24 48 java.lang.String 40 | 1 48 48 java.util.HashMap 41 | 1 32 32 java.util.HashMap$Node 42 | 7 256 (total) 43 | 44 | So you can see the full size including inner objects. 45 | 46 | **NOTE:** Your app most likely will use the HotSpot with `64-bit VM, compressed references` mode. 47 | 48 | ## Installation 49 | 50 | - Using IDE built-in plugin system: 51 | 52 | Settings/Preferences > Plugins > Marketplace > Search for "JOL" > 53 | Install Plugin 54 | 55 | - Manually: 56 | 57 | Download the [latest release](https://github.com/stokito/IdeaJol/releases/latest) and install it manually using 58 | Settings/Preferences > Plugins > ⚙️ > Install plugin from disk... 59 | 60 | 61 | ### Inspection 62 | The plugin provides an inspection to see most big classes. It's enabled by default. 63 | You can find the inspection by path `Java | Memory | JOL: Class has too big memory footprint` to configure or disable it. 64 | 65 | Please leave a feedback for the [plugin in marketplace](https://plugins.jetbrains.com/plugin/10953-java-object-layout). 66 | 67 | 68 | ## Tutorials 69 | * [Java Objects Inside Out](https://shipilev.net/jvm/objects-inside-out/) from the JOL author. 70 | * Видео: [Алексей Шипилёв — Java-объекты наизнанку](https://www.youtube.com/watch?v=q2wtSR3kD_I) 71 | * Video: [Java Object Layout | Ordinary Object Pointer | Object Header | CompressedOops | OpenJdk HotSpot JVM](https://www.youtube.com/watch?v=MK4rcxpwiuc) 72 | * Video: [JVM Benchmarking with Aleksey Shipilev](https://www.youtube.com/watch?v=x3Vlze1mUj4) 73 | 74 | 75 | ## What is layouter 76 | 77 | Java VM and it's version. 78 | 79 | HotSpot is from OpenJDK. The Lilliput in development. 80 | The Raw is a layout by itself without real gaps and aligns. 81 | 82 | CPU word size 32 or 64 bits i.e. size of a pointer. 83 | 84 | COOPS is compressed references i.e. a trick to store 64 bits pointer in only 32 bits but all fields needs to be aligned. 85 | 86 | Align is typically 8-byte but for a very large RAM may need to be 16-byte. 87 | 88 | CCPS is Compressed Classes in an object header i.e. 4 bytes instead of 8. 89 | 90 | You may find most typical layouters here https://github.com/openjdk/jol/blob/master/jol-cli/src/main/java/org/openjdk/jol/operations/EstimatedModels.java 91 | 92 | 93 | ## Related projects 94 | 95 | Heap dump `*.hprof` files analysers: 96 | * [The Lightweight Java Visualizer (LJV)](https://github.com/atp-mipt/ljv) a tool for visualizing Java data structures 97 | * The IntelliJ IDEA has a built-in [heap dump analyser](https://www.jetbrains.com/help/idea/analyze-hprof-memory-snapshots.html#read-snapshot) 98 | * Eclipse [Memory Analyzer (MAT)](https://www.eclipse.org/mat/) 99 | * [VisualVM](https://visualvm.github.io/) can also monitor heap in real time. Based on NetBeans 100 | * [Java Mission Control](https://github.com/openjdk/jmc) 101 | * [JMH Micro benchmarks tool](https://github.com/openjdk/jmh) and the [Intellij JMH plugin](https://github.com/artyushov/idea-jmh-plugin) -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java") 3 | id("org.jetbrains.intellij") version "1.17.4" 4 | } 5 | 6 | group = "com.github.stokito.IdeaJol" 7 | version = "1.13.0" 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | dependencies { 14 | implementation("org.openjdk.jol:jol-core:0.17") 15 | } 16 | 17 | intellij { 18 | version.set("2022.3.1") 19 | type.set("IC") 20 | 21 | plugins.set(listOf("java")) 22 | } 23 | 24 | tasks { 25 | withType { 26 | sourceCompatibility = "17" 27 | targetCompatibility = "17" 28 | } 29 | 30 | patchPluginXml { 31 | sinceBuild.set("223") 32 | untilBuild.set("") 33 | } 34 | // runPluginVerifier { 35 | // ideVersions.set(properties("pluginVerifierIdeVersions").split(',').map(String::trim).filter(String::isNotEmpty)) 36 | // } 37 | signPlugin { 38 | certificateChain.set(System.getenv("CERTIFICATE_CHAIN")) 39 | privateKey.set(System.getenv("PRIVATE_KEY")) 40 | password.set(System.getenv("PRIVATE_KEY_PASSWORD")) 41 | } 42 | 43 | publishPlugin { 44 | token.set(System.getenv("PUBLISH_TOKEN")) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stokito/IdeaJol/c7561a5737b24bc06e28236878e814e027d3bf8d/screenshot.png -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "IdeaJol" 2 | -------------------------------------------------------------------------------- /src/main/java/com/github/stokito/IdeaJol/FieldLayoutGap.java: -------------------------------------------------------------------------------- 1 | package com.github.stokito.IdeaJol; 2 | 3 | import org.openjdk.jol.info.FieldData; 4 | import org.openjdk.jol.info.FieldLayout; 5 | 6 | /** Stub class which represents gaps between fields: "(object header)" "(alignment/padding gap)", "(loss due to the next object alignment)" */ 7 | public class FieldLayoutGap extends FieldLayout { 8 | private String description; 9 | public FieldLayoutGap(FieldData fieldData, long offset, long size) { 10 | super(fieldData, offset, size); 11 | } 12 | 13 | public FieldLayoutGap(long offset, long size, String description) { 14 | super(null, offset, size); 15 | this.description = description; 16 | } 17 | 18 | public String getDescription() { 19 | return description; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/github/stokito/IdeaJol/Layouters.java: -------------------------------------------------------------------------------- 1 | package com.github.stokito.IdeaJol; 2 | 3 | import org.openjdk.jol.datamodel.Model32; 4 | import org.openjdk.jol.datamodel.Model64; 5 | import org.openjdk.jol.datamodel.Model64_Lilliput; 6 | import org.openjdk.jol.layouters.HotSpotLayouter; 7 | import org.openjdk.jol.layouters.Layouter; 8 | import org.openjdk.jol.layouters.RawLayouter; 9 | 10 | public class Layouters { 11 | public static final Model32 MODEL_32 = new Model32(); 12 | public static final Model64 MODEL_64 = new Model64(false, false, 8); 13 | public static final Model64 MODEL_64_COOPS = new Model64(true, false, 8); 14 | public static final Model64 MODEL_64_COOPS_16 = new Model64(true, false, 16); 15 | public static final Model64 MODEL_64_COOPS_CCPS = new Model64(true, true, 8); 16 | public static final Model64 MODEL_64_COOPS_CCPS_16 = new Model64(true, true, 16); 17 | /** compressed class pointers */ 18 | public static final Model64 MODEL_64_CCPS = new Model64(false, true, 8); 19 | /** compressed class pointers with 16 bit offset */ 20 | public static final Model64 MODEL_64_CCPS_16 = new Model64(false, true, 16); 21 | 22 | public static final Model64_Lilliput MODEL_64_LILIPUT = new Model64_Lilliput(false, 8, true); 23 | public static final Model64_Lilliput MODEL_64_LILIPUT_COOPS = new Model64_Lilliput(true, 8, true); 24 | public static final Model64_Lilliput MODEL_64_LILIPUT_COOPS_16 = new Model64_Lilliput(true, 16, true); 25 | 26 | public static final Layouter[] LAYOUTERS = { 27 | new RawLayouter(MODEL_32), 28 | new RawLayouter(MODEL_64), 29 | new RawLayouter(MODEL_64_COOPS), 30 | new HotSpotLayouter(MODEL_32, 8), 31 | new HotSpotLayouter(MODEL_64, 8), 32 | new HotSpotLayouter(MODEL_64_COOPS_CCPS, 8), 33 | new HotSpotLayouter(MODEL_64_COOPS_CCPS_16, 8), 34 | // JDK 15 uses a new layout 35 | new HotSpotLayouter(MODEL_32, 15), 36 | new HotSpotLayouter(MODEL_64, 15), 37 | new HotSpotLayouter(MODEL_64_COOPS_CCPS, 15), 38 | new HotSpotLayouter(MODEL_64_COOPS_CCPS_16, 15), 39 | // CCPS only 40 | new HotSpotLayouter(MODEL_64_CCPS, 15), 41 | new HotSpotLayouter(MODEL_64_CCPS_16, 15), 42 | // Lilliput 43 | new HotSpotLayouter(MODEL_64_LILIPUT, 15), 44 | new HotSpotLayouter(MODEL_64_LILIPUT_COOPS, 15), 45 | new HotSpotLayouter(MODEL_64_LILIPUT_COOPS_16, 15), 46 | }; 47 | 48 | public static final String[] LAYOUTERS_NAMES = { 49 | "Raw 32-bit", 50 | "Raw 64-bit", 51 | "Raw 64-bit, COOPS", 52 | "HotSpot 32-bit", 53 | "HotSpot 64-bit", 54 | "HotSpot >= 8 64-bit, COOPS, CCPS", 55 | "HotSpot >= 8 64-bit, COOPS, CCPS, 16-byte align", 56 | // JDK 15 uses a new layout 57 | "HotSpot >= 15, 32-bit", 58 | "HotSpot >= 15, 64-bit", 59 | "HotSpot >= 15, 64-bit, COOPS, CCPS", 60 | "HotSpot >= 15, 64-bit, COOPS, CCPS, 16-byte align", 61 | // CCPS only 62 | "HotSpot >= 15, 64-bit, CCPS", 63 | "HotSpot >= 15, 64-bit, CCPS, 16-byte align", 64 | // Lilliput 65 | "HotSpot Lilliput, 64-bit", 66 | "HotSpot Lilliput, 64-bit, COOPS", 67 | "HotSpot Lilliput, 64-bit, COOPS, 16-byte align" 68 | }; 69 | 70 | /** 71 | * Default 5 is Hotspot 64 bit COOPS CCPS 72 | */ 73 | public static final int DEFAULT_LAYOUTER_INDEX = 5; 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/github/stokito/IdeaJol/PsiClassAdapter.java: -------------------------------------------------------------------------------- 1 | package com.github.stokito.IdeaJol; 2 | 3 | import com.intellij.psi.*; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | import org.openjdk.jol.info.ClassData; 7 | import org.openjdk.jol.info.FieldData; 8 | 9 | import static com.intellij.psi.PsiModifier.STATIC; 10 | 11 | public class PsiClassAdapter { 12 | /** 13 | * Reimplemented logic from org.openjdk.jol.info.ClassData#parse(java.lang.Object, java.lang.Class) 14 | */ 15 | @NotNull 16 | public static ClassData createClassDataFromPsiClass(@NotNull PsiClass psiClass) { 17 | assert psiClass.getQualifiedName() != null : "The class doesn't have a qualified name: " + psiClass; 18 | ClassData classData = new ClassData(psiClass.getQualifiedName()); 19 | if (psiClass.getSuperClass() != null && !psiClass.equals(psiClass.getSuperClass())) { 20 | ClassData supperClassData = createClassDataFromPsiClass(psiClass.getSuperClass()); 21 | classData.addSuperClassData(supperClassData); 22 | } 23 | addClassFields(psiClass, classData); 24 | return classData; 25 | } 26 | 27 | private static void addClassFields(@NotNull PsiClass psiClass, @NotNull final ClassData classData) { 28 | do { 29 | for (PsiField psiField : psiClass.getFields()) { 30 | if (psiField.hasModifierProperty(STATIC)) { // skip static fields 31 | continue; 32 | } 33 | String typeText = psiField.getType().getPresentableText(); 34 | String contendedGroup = determineContendedGroup(psiField); 35 | boolean isContended = contendedGroup != null; 36 | FieldData fieldData = FieldData.create(psiClass.getQualifiedName(), psiField.getName(), typeText, isContended, contendedGroup); 37 | classData.addField(fieldData); 38 | } 39 | assert psiClass.getQualifiedName() != null : "The class doesn't have a qualified name: " + psiClass; 40 | classData.addSuperClass(psiClass.getQualifiedName()); 41 | } while ((psiClass = psiClass.getSuperClass()) != null); 42 | } 43 | 44 | @Nullable 45 | private static String determineContendedGroup(@NotNull PsiField psiField) { 46 | String contendedGroup = fetchContendedGroup(psiField, "jdk.internal.vm.annotation.Contended"); 47 | if (contendedGroup == null) { 48 | contendedGroup = fetchContendedGroup(psiField, "sun.misc.Contended"); 49 | } 50 | return contendedGroup; 51 | } 52 | 53 | /** 54 | * @return null if field is not marked with contended annotation, empty string for default group or name of contended group 55 | */ 56 | @Nullable 57 | private static String fetchContendedGroup(@NotNull PsiField psiField, @NotNull String annotationClass) { 58 | PsiAnnotation annotation = psiField.getAnnotation(annotationClass); 59 | String contendedGroup = annotation != null ? getAnnotationStrValue(annotation) : null; 60 | return contendedGroup; 61 | } 62 | 63 | private static String getAnnotationStrValue(@NotNull PsiAnnotation annotation) { 64 | PsiAnnotationMemberValue annotationValue = annotation.findAttributeValue("value"); 65 | String contendedGroup = annotationValue != null ? (String) ((PsiLiteralExpression) annotationValue).getValue() : null; 66 | return contendedGroup; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/github/stokito/IdeaJol/ShowObjectLayoutAction.java: -------------------------------------------------------------------------------- 1 | package com.github.stokito.IdeaJol; 2 | 3 | import com.github.stokito.IdeaJol.toolwindow.JolView; 4 | import com.intellij.openapi.actionSystem.ActionUpdateThread; 5 | import com.intellij.openapi.actionSystem.AnAction; 6 | import com.intellij.openapi.actionSystem.AnActionEvent; 7 | import com.intellij.openapi.diagnostic.Logger; 8 | import com.intellij.openapi.project.Project; 9 | import com.intellij.pom.Navigatable; 10 | import com.intellij.psi.PsiClass; 11 | import com.intellij.psi.PsiElement; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | import org.jetbrains.uast.UClass; 15 | import org.jetbrains.uast.UElement; 16 | import org.jetbrains.uast.UastContextKt; 17 | 18 | import static com.intellij.openapi.actionSystem.CommonDataKeys.NAVIGATABLE; 19 | 20 | public class ShowObjectLayoutAction extends AnAction { 21 | private static final Logger LOG = Logger.getInstance("#com.github.stokito.IdeaJol.ShowObjectLayoutAction"); 22 | 23 | @Override 24 | public void update(@NotNull AnActionEvent event) { 25 | PsiClass selectedPsiClass = getSelectedPsiClass(event); 26 | event.getPresentation().setEnabledAndVisible(selectedPsiClass != null); 27 | } 28 | 29 | @Override 30 | public @NotNull ActionUpdateThread getActionUpdateThread() { 31 | return ActionUpdateThread.BGT; 32 | } 33 | 34 | @Override 35 | public void actionPerformed(AnActionEvent event) { 36 | Project project = event.getProject(); 37 | assert project != null; 38 | PsiClass psiClass = getSelectedPsiClass(event); 39 | if (psiClass == null) { //FIXME 40 | LOG.error("Can't show layout: unable to determine selected class. Did you selected the class name?"); 41 | return; 42 | } 43 | JolView.showJolToolWindow(project, psiClass); 44 | } 45 | 46 | @Nullable 47 | private PsiClass getSelectedPsiClass(AnActionEvent event) { 48 | Project project = event.getProject(); 49 | Navigatable navigatable = event.getData(NAVIGATABLE); 50 | if (project == null || !(navigatable instanceof PsiElement)) { 51 | return null; 52 | } 53 | // Plain Java class 54 | if (navigatable instanceof PsiClass) { 55 | return (PsiClass) navigatable; 56 | } 57 | // Kotlin classes 58 | UElement element = UastContextKt.toUElement((PsiElement) navigatable); 59 | if (element instanceof UClass) { 60 | return ((UClass)element); 61 | } 62 | return null; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/github/stokito/IdeaJol/inspection/JolInspection.java: -------------------------------------------------------------------------------- 1 | package com.github.stokito.IdeaJol.inspection; 2 | 3 | import com.github.stokito.IdeaJol.PsiClassAdapter; 4 | import com.intellij.codeInspection.*; 5 | import com.intellij.codeInspection.ui.ListTable; 6 | import com.intellij.codeInspection.ui.ListWrappingTableModel; 7 | import com.intellij.openapi.diagnostic.Logger; 8 | import com.intellij.openapi.ui.ComboBox; 9 | import com.intellij.psi.PsiClass; 10 | import com.intellij.psi.PsiElement; 11 | import com.intellij.ui.components.fields.IntegerField; 12 | import com.intellij.util.ui.FormBuilder; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | import org.jetbrains.uast.UClass; 16 | import org.openjdk.jol.datamodel.Model64; 17 | import org.openjdk.jol.info.ClassData; 18 | import org.openjdk.jol.info.ClassLayout; 19 | import org.openjdk.jol.layouters.Layouter; 20 | 21 | import javax.swing.*; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | import static com.github.stokito.IdeaJol.Layouters.DEFAULT_LAYOUTER_INDEX; 26 | import static com.github.stokito.IdeaJol.Layouters.LAYOUTERS; 27 | import static com.github.stokito.IdeaJol.Layouters.LAYOUTERS_NAMES; 28 | import static com.intellij.codeInspection.ProblemHighlightType.WEAK_WARNING; 29 | import static com.siyeh.ig.ui.UiUtils.createAddRemovePanel; 30 | import static java.util.Arrays.asList; 31 | import static org.jetbrains.uast.UElementKt.getSourcePsiElement; 32 | 33 | public class JolInspection extends AbstractBaseUastLocalInspectionTool { 34 | private static final Logger LOG = Logger.getInstance("#com.github.stokito.IdeaJol.inspection.JolInspection"); 35 | 36 | private static final LocalQuickFix SHOW_JOL_QUICK_FIX = new ShowJolQuickFix(); 37 | 38 | /** 39 | * Suffixes of ignored business logic classes 40 | */ 41 | @SuppressWarnings("WeakerAccess") 42 | public List businessLogicClassSuffixes = new ArrayList<>(asList( 43 | "Exception", "Test", "Spec", "Impl", "Dao", "Utils", 44 | "Controller", "Service", "Strategy", "Servlet", "Adapter", "Factory", "Provider", 45 | "Handler", "Registry", "Filter", "Interceptor", "Executor" 46 | )); 47 | 48 | /** 49 | * Default 5 is Hotspot 64 bit COOPS CCPS 50 | * @see com.github.stokito.IdeaJol.Layouters#MODEL_64_COOPS_CCPS 51 | */ 52 | @SuppressWarnings("WeakerAccess") 53 | public int selectedLayouter = DEFAULT_LAYOUTER_INDEX; 54 | 55 | /** 56 | * Class memory size threshold. 57 | * Default 4*64=256 e.g. four CPU cache lines to not annoying a user 58 | */ 59 | @SuppressWarnings("WeakerAccess") 60 | public int sizeThreshold = 256; 61 | 62 | @Nullable 63 | @Override 64 | public ProblemDescriptor[] checkClass(@NotNull UClass aClass, @NotNull InspectionManager manager, boolean isOnTheFly) { 65 | if (isNotUsualClass(aClass) || isBusinessLogicClass(aClass)) { 66 | return null; 67 | } 68 | // Workaround for #20 NPE 69 | if (aClass.getQualifiedName() == null) { 70 | // LOG.warn("The class doesn't have a qualified name: " + aClass); 71 | return null; 72 | } 73 | ClassData classData = PsiClassAdapter.createClassDataFromPsiClass(aClass); 74 | ClassLayout layout = getLayouter().layout(classData); 75 | if (layout.instanceSize() <= sizeThreshold) { 76 | return null; 77 | } 78 | PsiElement navigateTo = getSourcePsiElement(aClass); 79 | if (navigateTo == null) { 80 | // this shouldn't happen because we already have this check inside of isNotUsualClass() 81 | return null; 82 | } 83 | ProblemDescriptor problem = manager.createProblemDescriptor(navigateTo, "Class has too big memory footprint", SHOW_JOL_QUICK_FIX, WEAK_WARNING, isOnTheFly); 84 | return new ProblemDescriptor[]{problem}; 85 | } 86 | 87 | private Layouter getLayouter() { 88 | return LAYOUTERS[selectedLayouter]; 89 | } 90 | 91 | /** 92 | * @return true if class is anonymous, annotation, enum, interface, or is not exists in source 93 | */ 94 | private boolean isNotUsualClass(PsiClass aClass) { 95 | return aClass.getNameIdentifier() == null || 96 | aClass.isAnnotationType() || 97 | aClass.isEnum() || 98 | aClass.isInterface(); 99 | //FIXME: for kotlin this always true || !aClass.isPhysical(); 100 | } 101 | 102 | private boolean isBusinessLogicClass(PsiClass aClass) { 103 | return endsWithAny(aClass.getName(), businessLogicClassSuffixes); 104 | } 105 | 106 | private boolean endsWithAny(String className, List businessLogicClassSuffixes) { 107 | if (className == null) return false; 108 | for (String suffix : businessLogicClassSuffixes) { 109 | if (className.endsWith(suffix)) { 110 | return true; 111 | } 112 | } 113 | return false; 114 | } 115 | 116 | /* looks like this approach works slower, needs to be better investigated with JMH 117 | private static final Set BUSINESS_LOGIC_CLASS_SUFFIXES_SET = new HashSet<>(Arrays.asList(businessLogicClassSuffixes)); 118 | 119 | private int indexOfLastCamelCaseWord(String className) { 120 | for (int i = className.length() - 1; i >= 0; i--) { 121 | if (Character.isUpperCase(className.charAt(i))) { 122 | return i; 123 | } 124 | } 125 | return -1; 126 | } 127 | 128 | private boolean endsWithAnyWord(String className) { 129 | int pos = indexOfLastCamelCaseWord(className); 130 | if (pos == -1) { 131 | return false; 132 | } 133 | String lastWordOfClassName = className.substring(pos); 134 | return BUSINESS_LOGIC_CLASS_SUFFIXES_SET.contains(lastWordOfClassName); 135 | } 136 | */ 137 | 138 | @Nullable 139 | @Override 140 | public JComponent createOptionsPanel() { 141 | IntegerField sizeThresholdEditor = new IntegerField(null, 24, Integer.MAX_VALUE); 142 | sizeThresholdEditor.getValueEditor().addListener(newValue -> sizeThreshold = newValue); 143 | sizeThresholdEditor.setValue(sizeThreshold); 144 | sizeThresholdEditor.setColumns(4); 145 | sizeThresholdEditor.setToolTipText("Class memory size threshold (CPU cache line is 64)"); 146 | 147 | ComboBox layouterComboBox = new ComboBox<>(LAYOUTERS_NAMES); 148 | layouterComboBox.setSelectedIndex(selectedLayouter); 149 | layouterComboBox.addActionListener(e -> selectedLayouter = layouterComboBox.getSelectedIndex()); 150 | layouterComboBox.setToolTipText("Almost everywhere used HotSpot 64x COOPS. Raw layouter shows size of the fields themselves"); 151 | 152 | ListTable ignoredSuffixesTable = new ListTable(new ListWrappingTableModel(businessLogicClassSuffixes, "Suffix")); 153 | 154 | return new FormBuilder() 155 | .addLabeledComponent("Size threshold", sizeThresholdEditor) 156 | .addLabeledComponent("VM Layout", layouterComboBox) 157 | .addLabeledComponentFillVertically("Suffixes of ignored business logic classes", createAddRemovePanel(ignoredSuffixesTable)) 158 | .getPanel(); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/com/github/stokito/IdeaJol/inspection/ShowJolQuickFix.java: -------------------------------------------------------------------------------- 1 | package com.github.stokito.IdeaJol.inspection; 2 | 3 | import com.github.stokito.IdeaJol.toolwindow.JolView; 4 | import com.intellij.codeInspection.LocalQuickFix; 5 | import com.intellij.codeInspection.ProblemDescriptor; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.psi.PsiClass; 8 | import com.intellij.psi.PsiElement; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | public class ShowJolQuickFix implements LocalQuickFix { 12 | @NotNull 13 | @Override 14 | public String getFamilyName() { 15 | return "Show Object Layout"; 16 | } 17 | 18 | @Override 19 | public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor problemDescriptor) { 20 | PsiElement psiClass = problemDescriptor.getPsiElement(); 21 | if (!(psiClass instanceof PsiClass)) { 22 | return; 23 | } 24 | JolView.showJolToolWindow(project, (PsiClass) psiClass); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/github/stokito/IdeaJol/toolwindow/FieldLayoutTableModel.java: -------------------------------------------------------------------------------- 1 | package com.github.stokito.IdeaJol.toolwindow; 2 | 3 | import com.github.stokito.IdeaJol.FieldLayoutGap; 4 | import org.openjdk.jol.info.FieldLayout; 5 | 6 | import javax.swing.table.AbstractTableModel; 7 | import java.util.List; 8 | 9 | public class FieldLayoutTableModel extends AbstractTableModel { 10 | private static final String[] COLUMNS = {"Offset", "Size", "Type", "Class", "Field"}; 11 | private final List layouts; 12 | 13 | public FieldLayoutTableModel(List layouts) { 14 | this.layouts = layouts; 15 | } 16 | 17 | @Override 18 | public String getColumnName(int column) { 19 | return COLUMNS[column]; 20 | } 21 | 22 | @Override 23 | public int getColumnCount() { 24 | return COLUMNS.length; 25 | } 26 | 27 | @Override 28 | public int getRowCount() { 29 | return layouts.size(); 30 | } 31 | 32 | @Override 33 | public Object getValueAt(int row, int col) { 34 | FieldLayout fieldLayout = layouts.get(row); 35 | switch (col) { 36 | case 0: 37 | return fieldLayout.offset(); 38 | case 1: 39 | return fieldLayout.size(); 40 | case 2: 41 | return fieldLayout instanceof FieldLayoutGap ? null : fieldLayout.typeClass(); 42 | case 3: 43 | return fieldLayout instanceof FieldLayoutGap ? null : fieldLayout.classShortName(); 44 | case 4: 45 | return fieldLayout instanceof FieldLayoutGap ? ((FieldLayoutGap)fieldLayout).getDescription() : fieldLayout.name(); 46 | default: 47 | return null; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/github/stokito/IdeaJol/toolwindow/JolForm.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /src/main/java/com/github/stokito/IdeaJol/toolwindow/JolForm.java: -------------------------------------------------------------------------------- 1 | package com.github.stokito.IdeaJol.toolwindow; 2 | 3 | import com.intellij.ui.components.JBScrollPane; 4 | import com.intellij.ui.table.JBTable; 5 | 6 | import javax.swing.*; 7 | 8 | class JolForm { 9 | JComponent rootPanel; 10 | JPanel pnlToolbar; 11 | JLabel lblClassName; 12 | JButton copyButton; 13 | JPanel pnlLayouter; 14 | JComboBox cmbDataModel; 15 | JPanel pnlInstanceSize; 16 | JLabel lblInstanceSize; 17 | JLabel lblLossesInternal; 18 | JLabel lblLossesExternal; 19 | JLabel lblLossesTotal; 20 | JPanel pnlObjectLayout; 21 | JBScrollPane objectLayoutScrollPane; 22 | JBTable tblObjectLayout; 23 | JPanel pnlDocs; 24 | JLabel lblDocs; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/github/stokito/IdeaJol/toolwindow/JolToolWindowFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.stokito.IdeaJol.toolwindow; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.wm.ToolWindow; 5 | import com.intellij.openapi.wm.ToolWindowFactory; 6 | import com.intellij.ui.content.Content; 7 | import com.intellij.ui.content.ContentManager; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public class JolToolWindowFactory implements ToolWindowFactory { 11 | @Override 12 | public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { 13 | JolView jolView = new JolView(project); 14 | ContentManager contentManager = toolWindow.getContentManager(); 15 | Content content = contentManager.getFactory().createContent(jolView, null, false); 16 | contentManager.addContent(content); 17 | content.setDisposer(jolView); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/stokito/IdeaJol/toolwindow/JolView.java: -------------------------------------------------------------------------------- 1 | package com.github.stokito.IdeaJol.toolwindow; 2 | 3 | import com.github.stokito.IdeaJol.FieldLayoutGap; 4 | import com.github.stokito.IdeaJol.PsiClassAdapter; 5 | import com.intellij.openapi.Disposable; 6 | import com.intellij.openapi.diagnostic.Logger; 7 | import com.intellij.openapi.ide.CopyPasteManager; 8 | import com.intellij.openapi.project.Project; 9 | import com.intellij.openapi.ui.SimpleToolWindowPanel; 10 | import com.intellij.openapi.wm.ToolWindow; 11 | import com.intellij.openapi.wm.ToolWindowManager; 12 | import com.intellij.psi.PsiClass; 13 | import com.intellij.psi.PsiField; 14 | import com.intellij.psi.SmartPointerManager; 15 | import com.intellij.psi.SmartPsiElementPointer; 16 | import com.intellij.ui.content.Content; 17 | import org.jetbrains.annotations.NotNull; 18 | import org.jetbrains.annotations.Nullable; 19 | import org.openjdk.jol.info.ClassData; 20 | import org.openjdk.jol.info.ClassLayout; 21 | import org.openjdk.jol.info.FieldLayout; 22 | import org.openjdk.jol.layouters.Layouter; 23 | 24 | import javax.swing.*; 25 | import javax.swing.event.ListSelectionEvent; 26 | import javax.swing.event.ListSelectionListener; 27 | import javax.swing.table.TableColumnModel; 28 | import javax.swing.table.TableModel; 29 | import java.awt.*; 30 | import java.awt.datatransfer.StringSelection; 31 | import java.awt.event.ActionEvent; 32 | import java.awt.event.ActionListener; 33 | import java.awt.event.MouseAdapter; 34 | import java.awt.event.MouseEvent; 35 | import java.awt.font.TextAttribute; 36 | import java.net.URI; 37 | import java.util.ArrayList; 38 | import java.util.Map; 39 | 40 | import static com.github.stokito.IdeaJol.Layouters.DEFAULT_LAYOUTER_INDEX; 41 | import static com.github.stokito.IdeaJol.Layouters.LAYOUTERS; 42 | import static com.github.stokito.IdeaJol.Layouters.LAYOUTERS_NAMES; 43 | import static com.intellij.ui.JBColor.RED; 44 | import static java.awt.font.TextAttribute.STRIKETHROUGH; 45 | import static java.lang.Boolean.FALSE; 46 | import static java.lang.Boolean.TRUE; 47 | import static javax.swing.ListSelectionModel.SINGLE_SELECTION; 48 | 49 | public class JolView extends SimpleToolWindowPanel implements Disposable { 50 | private static final Logger LOG = Logger.getInstance("#com.github.stokito.IdeaJol.toolwindow.JolView"); 51 | private Project project; 52 | 53 | private SmartPsiElementPointer psiClass; 54 | private JolForm jolForm = new JolForm(); 55 | 56 | public JolView(@NotNull Project project) { 57 | super(true, true); 58 | this.project = project; 59 | setupUI(); 60 | } 61 | 62 | private void setupUI() { 63 | jolForm.tblObjectLayout.getEmptyText().setText("Select a class then press Code / Show Object Layout"); 64 | jolForm.tblObjectLayout.setSelectionMode(SINGLE_SELECTION); 65 | jolForm.tblObjectLayout.getSelectionModel().addListSelectionListener(navigateToFieldInEditor()); 66 | jolForm.lblClassName.addMouseListener(navigateToClassInEditor()); 67 | jolForm.copyButton.addMouseListener(copyObjectLayoutToClipboard()); 68 | DefaultComboBoxModel model = new DefaultComboBoxModel<>(LAYOUTERS_NAMES); 69 | jolForm.cmbDataModel.setModel(model); 70 | jolForm.cmbDataModel.setSelectedIndex(DEFAULT_LAYOUTER_INDEX); 71 | jolForm.cmbDataModel.addActionListener(layoutOptionsActionPerformed()); 72 | jolForm.lblDocs.addMouseListener(openDocumentation()); 73 | setContent(jolForm.rootPanel); 74 | } 75 | 76 | @NotNull 77 | private ListSelectionListener navigateToFieldInEditor() { 78 | return new ListSelectionListener() { 79 | @Override 80 | public void valueChanged(@NotNull ListSelectionEvent e) { 81 | if (e.getValueIsAdjusting()) { 82 | return; 83 | } 84 | int fieldIndex = jolForm.tblObjectLayout.getSelectionModel().getLeadSelectionIndex(); 85 | // first row is always object header 86 | if (fieldIndex == 0) { 87 | return; 88 | } 89 | // on reset of model the selected index can be more than new count of rows 90 | if (fieldIndex == -1 || fieldIndex > jolForm.tblObjectLayout.getModel().getRowCount() - 1) { 91 | return; 92 | } 93 | String className = (String) jolForm.tblObjectLayout.getModel().getValueAt(fieldIndex, 3); 94 | // for padding/gap the class name is null 95 | if (className == null) { 96 | return; 97 | } 98 | String fieldName = (String) jolForm.tblObjectLayout.getModel().getValueAt(fieldIndex, 4); 99 | PsiField psiField = findFieldInHierarchy(className, fieldName); 100 | if (psiField != null) { 101 | psiField.navigate(true); 102 | } 103 | } 104 | }; 105 | } 106 | 107 | @NotNull 108 | private MouseAdapter navigateToClassInEditor() { 109 | return new MouseAdapter() { 110 | @Override 111 | public void mouseClicked(MouseEvent e) { 112 | PsiClass psiClassElement = getPsiClass(); 113 | if (psiClassElement == null) { 114 | return; 115 | } 116 | psiClassElement.navigate(true); 117 | } 118 | }; 119 | } 120 | 121 | 122 | @NotNull 123 | private MouseAdapter copyObjectLayoutToClipboard() { 124 | return new MouseAdapter() { 125 | @Override 126 | public void mouseClicked(MouseEvent event) { 127 | PsiClass psiClass = getPsiClass(); 128 | if (psiClass == null) { 129 | return; 130 | } 131 | ClassLayout classLayout = calcClassLayout(psiClass); 132 | CopyPasteManager.getInstance().setContents(new StringSelection(classLayout.toPrintable())); 133 | } 134 | }; 135 | } 136 | 137 | @NotNull 138 | private ActionListener layoutOptionsActionPerformed() { 139 | return new ActionListener() { 140 | @Override 141 | public void actionPerformed(ActionEvent event) { 142 | showLayoutForSelectedClass(); 143 | } 144 | }; 145 | } 146 | 147 | @NotNull 148 | private MouseAdapter openDocumentation() { 149 | return new MouseAdapter() { 150 | @Override 151 | public void mouseClicked(MouseEvent event) { 152 | Desktop desktop = java.awt.Desktop.getDesktop(); 153 | try { 154 | URI oURL = new URI(jolForm.lblDocs.getToolTipText()); 155 | desktop.browse(oURL); 156 | } catch (Exception e) { 157 | LOG.error("Unable to open docs link", e); 158 | } 159 | } 160 | }; 161 | } 162 | 163 | @Override 164 | public void dispose() { 165 | project = null; 166 | psiClass = null; 167 | jolForm = null; 168 | } 169 | 170 | public void showLayoutForClass(@NotNull PsiClass psiClass) { 171 | this.psiClass = SmartPointerManager.getInstance(project).createSmartPsiElementPointer(psiClass); 172 | classLabelFontStrike(FALSE); 173 | jolForm.lblClassName.setText(psiClass.getName()); 174 | jolForm.lblClassName.setIcon(psiClass.getIcon(0)); 175 | jolForm.copyButton.setEnabled(true); 176 | showLayoutForSelectedClass(); 177 | } 178 | 179 | @NotNull 180 | private Layouter getSelectedLayouter() { 181 | int layouterIndex = jolForm.cmbDataModel.getSelectedIndex(); 182 | return LAYOUTERS[layouterIndex]; 183 | } 184 | 185 | private void showLayoutForSelectedClass() { 186 | PsiClass psiClass = getPsiClass(); 187 | if (psiClass == null) { 188 | return; 189 | } 190 | ClassLayout classLayout = calcClassLayout(psiClass); 191 | ArrayList objectLayouts = collectObjectLayouts(classLayout); 192 | 193 | TableModel model = new FieldLayoutTableModel(objectLayouts); 194 | jolForm.tblObjectLayout.setModel(model); 195 | TableColumnModel columnModel = jolForm.tblObjectLayout.getColumnModel(); 196 | columnModel.getColumn(0).setMinWidth(120); 197 | columnModel.getColumn(0).setMaxWidth(150); 198 | // columnModel.getColumn(0).setResizable(false); 199 | columnModel.getColumn(1).setMinWidth(90); 200 | columnModel.getColumn(1).setMaxWidth(120); 201 | // columnModel.getColumn(1).setResizable(false); 202 | } 203 | 204 | /** 205 | * Convert classLayout to rows of table. 206 | * TODO: This should be already done in classLayout so we shouldn't make any calculations 207 | */ 208 | @NotNull 209 | private ArrayList collectObjectLayouts(@NotNull ClassLayout classLayout) { 210 | ArrayList objectLines = new ArrayList<>(classLayout.fields().size() + 8); 211 | objectLines.add(new FieldLayoutGap(0, classLayout.headerSize(), "(object header)")); 212 | long nextFree = classLayout.headerSize(); 213 | for (FieldLayout fieldLayout : classLayout.fields()) { 214 | if (fieldLayout.offset() > nextFree) { 215 | long fieldLayoutSize = fieldLayout.offset() - nextFree; 216 | objectLines.add(new FieldLayoutGap(nextFree, fieldLayoutSize, "(alignment/padding gap)")); 217 | } 218 | objectLines.add(fieldLayout); 219 | nextFree = fieldLayout.offset() + fieldLayout.size(); 220 | } 221 | if (classLayout.instanceSize() != nextFree) { 222 | objectLines.add(new FieldLayoutGap(nextFree, classLayout.getLossesExternal(), "(loss due to the next object alignment)")); 223 | } 224 | 225 | showTotalInstanceSize(classLayout); 226 | return objectLines; 227 | } 228 | 229 | private void showTotalInstanceSize(ClassLayout classLayout) { 230 | jolForm.lblInstanceSize.setText(Long.toString(classLayout.instanceSize())); 231 | changeLabelInstanceSizeColorIfLargerThanCacheLine(classLayout.instanceSize()); 232 | jolForm.lblLossesInternal.setText(Long.toString( classLayout.getLossesInternal())); 233 | jolForm.lblLossesExternal.setText(Long.toString(classLayout.getLossesExternal())); 234 | jolForm.lblLossesTotal.setText(Long.toString(classLayout.getLossesTotal())); 235 | } 236 | 237 | /** Processor cache line is almost always 64 bytes */ 238 | private void changeLabelInstanceSizeColorIfLargerThanCacheLine(long sizeOf) { 239 | if (sizeOf > 64) { 240 | jolForm.lblInstanceSize.setForeground(RED); 241 | jolForm.lblInstanceSize.setToolTipText("More that 64 bytes of cache line and this is bad for performance"); 242 | } else { 243 | // copy default label color from another label 244 | jolForm.lblInstanceSize.setForeground(jolForm.lblLossesExternal.getForeground()); 245 | jolForm.lblInstanceSize.setToolTipText(null); 246 | } 247 | } 248 | 249 | @Nullable 250 | private PsiField findFieldInHierarchy(@NotNull String className, @Nullable String fieldName) { 251 | if (fieldName == null) { 252 | return null; 253 | } 254 | PsiClass psiClassElement = getPsiClass(); 255 | if (psiClassElement == null) { 256 | return null; 257 | } 258 | for (PsiField field : psiClassElement.getAllFields()) { 259 | PsiClass parentClass = (PsiClass) field.getParent(); 260 | String parentClassName = parentClass.getName(); 261 | assert parentClassName != null; 262 | if (parentClassName.equals(className) && field.getName().equals(fieldName)) { 263 | return field; 264 | } 265 | } 266 | return null; 267 | } 268 | 269 | /** Safely get a PsiClass - it can be already removed then we'll keep layout but strike out class name label */ 270 | @Nullable 271 | private PsiClass getPsiClass() { 272 | PsiClass psiClassElement = psiClass != null ? psiClass.getElement() : null; 273 | // QualifiedName == null is a workaround for #20 274 | if (psiClassElement == null || psiClassElement.getQualifiedName() == null) { 275 | classLabelFontStrike(TRUE); 276 | psiClass = null; 277 | return null; 278 | } 279 | return psiClassElement; 280 | } 281 | 282 | private void classLabelFontStrike(Boolean strikethroughOn) { 283 | @SuppressWarnings("unchecked") 284 | Map fontAttributes = (Map) jolForm.lblClassName.getFont().getAttributes(); 285 | fontAttributes.put(STRIKETHROUGH, strikethroughOn); 286 | Font strikethroughFont = new Font(fontAttributes); 287 | jolForm.lblClassName.setFont(strikethroughFont); 288 | } 289 | 290 | private ClassLayout calcClassLayout(@NotNull PsiClass psiClass) { 291 | Layouter layouter = getSelectedLayouter(); 292 | ClassData classData = PsiClassAdapter.createClassDataFromPsiClass(psiClass); 293 | return layouter.layout(classData); 294 | } 295 | 296 | public static void showJolToolWindow(@NotNull Project project, @NotNull PsiClass psiClass) { 297 | try { 298 | ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow("JOL"); 299 | if (toolWindow != null) { 300 | Content content = toolWindow.getContentManager().getContent(0); 301 | if (content != null) { 302 | JolView jolView = (JolView) content.getComponent(); 303 | jolView.showLayoutForClass(psiClass); 304 | toolWindow.activate(null); 305 | } 306 | } 307 | } catch (Exception ex) { 308 | LOG.warn("Unable to generate layout", ex); 309 | } 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | JOL Java Object Layout 3 | JOL 4 | 5 | Profiling 6 | Inspection 7 | 8 | 10 | Java Object Layout (JOL) is a tool to analyze in memory object layout schemes in JVMs. 11 | It allows you to make an estimate how much memory the object takes. This allows to make the simplest but most efficient performance improvements. 12 | Just check your DTOs if they fit into 64 bytes of processor's cache line. 13 |

14 |

For example, in HotSpot VM on 64x processor an empty string "" takes 40 bytes i.e. 24 bytes for String object itself + 16 bytes for an internal empty char array. 15 |

16 |

17 | Source code on GitHub 18 |

19 | ]]> 20 |
21 | 23 |
  • v1.13.0 Fixed large margins in between the fields and added documentation link
  • 24 |
  • v1.12.0 Remove deprecated API
  • 25 |
  • v1.11.1 Fix UI and #38
  • 26 |
  • v1.11.0 Update JOL to v0.17 with support of new Lilliput layouts. Change in UI
  • 27 |
  • v1.10.0 Update JOL to v0.16 and support new layouts
  • 28 |
  • v1.9.1 Fix bugs, avoid deprecated API
  • 29 |
  • v1.9.0 Update JOL to v0.14 and minor changes
  • 30 |
  • v1.8.0 Workaround for annoying NPE #20
  • 31 |
  • v1.7.0 Support of Kotlin classes. Converted to UAST and fixed minor bugs
  • 32 |
  • v1.6.1 Update JOL to v0.10 and fix of minor bugs
  • 33 |
  • v1.6.0 Added an inspection to report about to big classes.
  • 34 |
  • v1.5.0 show layout in table instead of raw text.
  • 35 |
  • v1.3.0 first published version.
  • 36 |
      37 | ]]> 38 | 39 | 1.13.0 40 | Sergey Ponomarev 41 | com.intellij.modules.lang 42 | com.intellij.modules.java 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/resources/inspectionDescriptions/jol.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

      Reports about data classes that have too big memory footprint.

      4 | 5 |

      Thus you can perform the simplest but most efficient performance improvements. Just check your DTOs if they fit into 64 bytes of processor's cache line.

      6 | 7 | -------------------------------------------------------------------------------- /src/main/resources/ram-13x13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stokito/IdeaJol/c7561a5737b24bc06e28236878e814e027d3bf8d/src/main/resources/ram-13x13.png -------------------------------------------------------------------------------- /src/test/java/com/github/stokito/IdeaJol/PsiClassAdapterTest.java: -------------------------------------------------------------------------------- 1 | package com.github.stokito.IdeaJol; 2 | 3 | import com.intellij.psi.PsiClass; 4 | import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase; 5 | import org.openjdk.jol.info.ClassData; 6 | import org.openjdk.jol.info.FieldData; 7 | 8 | import java.util.List; 9 | 10 | public class PsiClassAdapterTest extends LightJavaCodeInsightFixtureTestCase { 11 | public void testCreateClassDataFromPsiClass() { 12 | myFixture.configureByText("PackingFields.java", "public class PackingFields {\n" + 13 | " boolean bo1, bo2;\n" + 14 | " byte b1, b2;\n" + 15 | " char c1, c2;\n" + 16 | " double d1, d2;\n" + 17 | " float f1, f2;\n" + 18 | " int i1, i2;\n" + 19 | " long l1, l2;\n" + 20 | " short s1, s2;\n" + 21 | "}"); 22 | 23 | PsiClass psiClass = myFixture.findClass("PackingFields"); 24 | System.out.println(psiClass.getText()); 25 | ClassData classData = PsiClassAdapter.createClassDataFromPsiClass(psiClass); 26 | assertEquals(16, classData.fields().size()); 27 | assertField(classData, 0, "bo1", "boolean", "PackingFields"); 28 | assertField(classData, 1, "bo2", "boolean", "PackingFields"); 29 | assertField(classData, 2, "b1", "byte", "PackingFields"); 30 | assertField(classData, 3, "b2", "byte", "PackingFields"); 31 | assertField(classData, 4, "c1", "char", "PackingFields"); 32 | assertField(classData, 5, "c2", "char", "PackingFields"); 33 | assertField(classData, 6, "d1", "double", "PackingFields"); 34 | assertField(classData, 7, "d2", "double", "PackingFields"); 35 | assertField(classData, 8, "f1", "float", "PackingFields"); 36 | assertField(classData, 9, "f2", "float", "PackingFields"); 37 | assertField(classData, 10, "i1", "int", "PackingFields"); 38 | assertField(classData, 11, "i2", "int", "PackingFields"); 39 | assertField(classData, 12, "l1", "long", "PackingFields"); 40 | assertField(classData, 13, "l2", "long", "PackingFields"); 41 | assertField(classData, 14, "s1", "short", "PackingFields"); 42 | assertField(classData, 15, "s2", "short", "PackingFields"); 43 | } 44 | 45 | private void assertField(ClassData classData, int index, String name, String typeClass, String hostClass) { 46 | assertEquals(name, classData.fields().get(index).name()); 47 | assertEquals(typeClass, classData.fields().get(index).typeClass()); 48 | assertEquals(hostClass, classData.fields().get(index).hostClass()); 49 | } 50 | 51 | } 52 | --------------------------------------------------------------------------------